import ManaCore import Observation import SwiftUI /// Account-Sheet: Email-Adresse ändern. /// /// Schickt eine Verifikations-Mail an die **neue** Adresse. Bis der /// User klickt, bleibt die alte Email aktiv. /// /// **Server-Limitation (v0.1.0):** funktioniert erst nach Phase-3- /// Server-PR (`mana-auth` braucht Bearer-Plugin). Die UI ist fertig, /// der Wire ist fertig, der Server muss nachziehen. public struct ManaChangeEmailView: View { @Environment(\.manaBrand) private var brand @State private var model: ChangeEmailViewModel private let onDone: () -> Void public init(auth: AuthClient, callbackUniversalLink: URL? = nil, onDone: @escaping () -> Void) { _model = State(initialValue: ChangeEmailViewModel( auth: auth, callbackUniversalLink: callbackUniversalLink )) self.onDone = onDone } public var body: some View { switch model.status { case .done: doneView default: formView } } @ViewBuilder private var formView: some View { ManaAuthScaffold(showsHeader: false) { VStack(spacing: 16) { Text("Email ändern") .font(.title2) .fontWeight(.semibold) .foregroundStyle(brand.foreground) .frame(maxWidth: .infinity, alignment: .leading) Text( "Wir schicken eine Bestätigungs-Mail an die neue Adresse. " + "Bis du klickst, bleibt die alte Email aktiv." ) .font(.subheadline) .foregroundStyle(brand.mutedForeground) .frame(maxWidth: .infinity, alignment: .leading) ManaTextField("Neue Email", text: $model.newEmail) .manaEmailField() ManaPrimaryButton( "Email ändern", isLoading: model.isSubmitting, isEnabled: model.canSubmit ) { Task { await model.submit() } } if case let .error(message) = model.status { Text(message) .font(.footnote) .foregroundStyle(brand.error) .multilineTextAlignment(.center) .padding(.top, 4) } } .padding(.top, 16) Button("Abbrechen", action: onDone) .font(.subheadline) .foregroundStyle(brand.mutedForeground) .padding(.top, 12) } } @ViewBuilder private var doneView: some View { ManaAuthScaffold(showsHeader: false) { VStack(spacing: 16) { Image(systemName: "envelope.fill") .font(.system(size: 56, weight: .light)) .foregroundStyle(brand.primary) Text("Bestätigungs-Mail verschickt") .font(.title2) .fontWeight(.semibold) .foregroundStyle(brand.foreground) Text( "Klicke den Link in der Mail, um die Änderung zu bestätigen." ) .font(.subheadline) .foregroundStyle(brand.mutedForeground) .multilineTextAlignment(.center) ManaPrimaryButton("Fertig") { onDone() } .padding(.top, 16) } } } } @MainActor @Observable final class ChangeEmailViewModel { enum Status: Equatable { case idle case submitting case done case error(String) } var newEmail: String = "" private(set) var status: Status = .idle private let auth: AuthClient private let callbackUniversalLink: URL? init(auth: AuthClient, callbackUniversalLink: URL?) { self.auth = auth self.callbackUniversalLink = callbackUniversalLink } var canSubmit: Bool { guard !newEmail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false } if case .submitting = status { return false } return true } var isSubmitting: Bool { if case .submitting = status { return true } return false } func submit() async { let trimmed = newEmail.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } status = .submitting do { try await auth.changeEmail(newEmail: trimmed, callbackUniversalLink: callbackUniversalLink) status = .done } catch let error as AuthError { status = .error(error.errorDescription ?? "Änderung fehlgeschlagen") } catch { status = .error(String(describing: error)) } } }