feat: add iOS support and harden receipt scanning
This commit is contained in:
121
app/ios/ScanReceipts/Features/Settings/DeleteAccountView.swift
Normal file
121
app/ios/ScanReceipts/Features/Settings/DeleteAccountView.swift
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user