import SwiftUI /// "Anmelden" screen — the default screen of `AuthFlowView`. Talks to auth /// state exclusively through `AppState.login(email:password:)`; never calls /// `AuthAPI` directly (see `AppState`'s own doc comment for why). struct LoginView: View { @EnvironmentObject private var appState: AppState /// Switches `AuthFlowView` to the signup screen. var onSwitchToSignup: () -> Void @State private var email = "" @State private var password = "" @State private var isSubmitting = false private var canSubmit: Bool { !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !password.isEmpty && !isSubmitting } var body: some View { ScrollView { VStack(spacing: ZenithSpacing.md) { VStack(spacing: ZenithSpacing.xs) { Image(systemName: "doc.text.viewfinder") .font(.system(size: 40)) .foregroundStyle(Color.zenithBlack) Text("ScanReceipts") .zenithHeadlineLgStyle() } .padding(.top, ZenithSpacing.lg) VStack(spacing: ZenithSpacing.sm) { ZenithTextField( label: "E-Mail", text: $email, keyboardType: .emailAddress, textContentType: .username, autocapitalization: .never, autocorrectionDisabled: true ) ZenithTextField( label: "Passwort", text: $password, isSecure: true, textContentType: .password ) } if let error = appState.lastError { Text(error) .zenithBodySmStyle(color: .zenithError) .multilineTextAlignment(.center) } Button { Task { await submit() } } label: { Group { if isSubmitting { ProgressView() .tint(.white) } else { Text("Anmelden") } } .frame(maxWidth: .infinity) } .buttonStyle(.zenithPrimary) .disabled(!canSubmit) HStack(spacing: ZenithSpacing.sm) { ZenithDivider() Text("oder") .zenithBodySmStyle() ZenithDivider() } // No outer `.frame(height:)` here — AppleSignInButton sizes // its own button (minHeight: 50) and needs the flexibility // to grow when it shows its own inline error text below it. AppleSignInButton() Button("Noch kein Konto? Registrieren") { appState.lastError = nil onSwitchToSignup() } .buttonStyle(.zenithPlain) .padding(.top, ZenithSpacing.xs) } .padding(.horizontal, ZenithSpacing.md) .padding(.bottom, ZenithSpacing.lg) } .scrollDismissesKeyboard(.interactively) .background(Color.zenithBg) } private func submit() async { guard canSubmit else { return } isSubmitting = true defer { isSubmitting = false } await appState.login( email: email.trimmingCharacters(in: .whitespacesAndNewlines), password: password ) } }