import SwiftUI /// Permanent account deletion. Requires typing the account's email exactly /// (compared against `appState.currentUser?.email`) plus the password before /// the destructive action becomes enabled, then a second confirmation /// dialog before the call actually fires. struct DeleteAccountView: View { @EnvironmentObject private var appState: AppState @State private var confirmEmail = "" @State private var password = "" @State private var isSubmitting = false @State private var errorMessage: String? @State private var showConfirmDialog = false // TODO: `Models/User.swift` doesn't currently expose whether an account // is Google-only (i.e. has no password to check). // `AccountAPI.deleteAccount(password:confirmEmail:)` accepts `password: // nil` specifically for that case, but there's no signal here to detect // it, so this screen always shows and requires the password field. Once // the `User` model exposes something like `hasPassword`, make this field // optional/hidden for Google-only accounts and pass `password: nil` // instead of always requiring input here. private var emailMatches: Bool { guard let accountEmail = appState.currentUser?.email, !accountEmail.isEmpty else { return false } return confirmEmail == accountEmail } private var canSubmit: Bool { emailMatches && !password.isEmpty && !isSubmitting } var body: some View { ScrollView { VStack(alignment: .leading, spacing: ZenithSpacing.md) { Text("Diese Aktion ist unwiderruflich. Dein Konto sowie alle Belege und Ordner werden dauerhaft gelöscht.") .zenithBodyStyle(color: .zenithError) ZenithDivider() VStack(alignment: .leading, spacing: ZenithSpacing.sm) { Text("Bestätigung").zenithLabelCapsStyle() if let email = appState.currentUser?.email { Text("Gib zur Bestätigung deine E-Mail-Adresse ein: \(email)") .zenithBodySmStyle() } ZenithTextField( label: "E-Mail-Adresse", text: $confirmEmail, placeholder: "E-Mail-Adresse", keyboardType: .emailAddress, textContentType: .emailAddress, autocapitalization: .never, autocorrectionDisabled: true ) ZenithTextField( label: "Passwort", text: $password, placeholder: "Passwort", isSecure: true, textContentType: .password ) } if let errorMessage { Text(errorMessage) .zenithBodySmStyle(color: .zenithError) } Button(role: .destructive) { showConfirmDialog = true } label: { if isSubmitting { ProgressView() .tint(.white) } else { Text("Konto endgültig löschen") } } .buttonStyle(.zenithPrimaryDestructive) .disabled(!canSubmit) .padding(.top, ZenithSpacing.sm) } .padding(ZenithSpacing.sm) } .background(Color.zenithBg) .navigationTitle("Konto löschen") .navigationBarTitleDisplayMode(.inline) .confirmationDialog( "Konto wirklich endgültig löschen?", isPresented: $showConfirmDialog, titleVisibility: .visible ) { Button("Endgültig löschen", role: .destructive) { Task { await deleteAccount() } } Button("Abbrechen", role: .cancel) {} } message: { Text("Diese Aktion kann nicht rückgängig gemacht werden.") } } private func deleteAccount() async { errorMessage = nil isSubmitting = true defer { isSubmitting = false } do { try await AccountAPI.deleteAccount(password: password, confirmEmail: confirmEmail) // The account and its session are already gone server-side — // this just resets local state. RootView (App/RootView.swift) // switches to the login screen automatically once // AppState.phase flips to .signedOut. await appState.logout() } catch { errorMessage = error.localizedDescription } } }