import ManaCore import Observation /// State-Maschine für ``ManaLoginView``. Wraps `AuthClient.signIn` und /// merkt sich, ob ein vorheriger Sign-In mit `.emailNotVerified` /// gescheitert ist — die UI schaltet dann auf den Resend-Mail-Gate /// um. @MainActor @Observable public final class LoginViewModel { public enum Status: Equatable, Sendable { case idle case signingIn /// Sign-In ist gescheitert mit klassifiziertem Fehler. /// `.emailNotVerified` ist ein wichtiger Sonderfall — die UI /// schaltet darauf den Resend-Mail-Gate frei. case error(String) /// Sign-In ist gescheitert weil die Email noch nicht bestätigt /// ist. UI zeigt den Resend-Gate für die zuletzt eingegebene /// Email-Adresse. case emailNotVerified(email: String) } public var email: String = "" public var password: String = "" public private(set) var status: Status = .idle private let auth: AuthClient public init(auth: AuthClient) { self.auth = auth } /// Wahrheit über "kann der Submit-Button klicken?". public var canSubmit: Bool { guard !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, !password.isEmpty else { return false } if case .signingIn = status { return false } return true } public var isSigningIn: Bool { if case .signingIn = status { return true } return false } /// Zurück auf Idle — wird vom ``ManaEmailVerifyGateView`` aufgerufen, /// wenn der User "Zurück zum Login" drückt. public func resetToIdle() { status = .idle password = "" } /// Führt den Sign-In aus. Bei Erfolg setzt `AuthClient.status` auf /// `.signedIn` — die App reagiert darauf über die Observation des /// AuthClients selbst (z.B. Root-View switcht von Login zu Dashboard). public func submit() async { let trimmed = email.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty, !password.isEmpty else { return } status = .signingIn await auth.signIn(email: trimmed, password: password) switch auth.status { case .signedIn: status = .idle password = "" // nicht im Memory lassen case .error: // Strukturierten Fehler aus AuthClient.lastError lesen statt // den String der Status-Maschine zu re-parsen. if case .emailNotVerified = auth.lastError { status = .emailNotVerified(email: trimmed) } else { status = .error(auth.lastError?.errorDescription ?? "Login fehlgeschlagen") } default: status = .idle } } }