122 lines
4.7 KiB
Swift
122 lines
4.7 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|