feat(settings): Preferences delete-account action

Danger subsection, confirmation dialog before calling users.delete
(deleteAccount()), signs out locally on success. Closes out the
Preferences page.
This commit is contained in:
2026-08-15 20:31:50 +01:00
parent cac11f0a22
commit 384dfe3cc1
@@ -36,6 +36,9 @@ struct SettingsView: View {
@State private var languageErrorMessage: String?
@State private var isSavingPreferences = false
@State private var preferencesErrorMessage: String?
@State private var isShowingDeleteAccountConfirmation = false
@State private var isDeletingAccount = false
@State private var deleteAccountErrorMessage: String?
/// Full Local Sync and cache-clearing both need a real connection to be
/// safe clearing while offline (or letting Full Local Sync think it
@@ -302,9 +305,49 @@ struct SettingsView: View {
.font(.caption)
.foregroundStyle(.red)
}
preferencesSubsection("Danger") {
deleteAccountRow
}
}
.frame(maxWidth: 480, alignment: .leading)
.task { await refreshProfile() }
.confirmationDialog(
"Delete Account?",
isPresented: $isShowingDeleteAccountConfirmation,
titleVisibility: .visible
) {
Button("Delete Account", role: .destructive) { Task { await deleteAccount() } }
Button("Cancel", role: .cancel) {}
} message: {
Text("This is unrecoverable — everything associated with your account will be permanently deleted.")
}
}
private var deleteAccountRow: some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text("Delete account")
Text("You may delete your account at any time, note that this is unrecoverable.")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if isDeletingAccount {
ProgressView().controlSize(.small)
} else {
Button("Delete…", role: .destructive) { isShowingDeleteAccountConfirmation = true }
.buttonStyle(.bordered)
.tint(.red)
}
}
if let deleteAccountErrorMessage {
Text(deleteAccountErrorMessage)
.font(.caption)
.foregroundStyle(.red)
}
}
}
private func preferencesSubsection<Content: View>(
@@ -407,6 +450,18 @@ struct SettingsView: View {
)
}
private func deleteAccount() async {
guard let apiClient = session.apiClient else { return }
isDeletingAccount = true
defer { isDeletingAccount = false }
do {
try await apiClient.deleteAccount()
session.signOut()
} catch {
deleteAccountErrorMessage = outlineErrorMessage(error, fallback: "Couldn't delete your account.")
}
}
private func saveLanguage(_ code: String) async {
guard code != session.userLanguage, let apiClient = session.apiClient, let userId = session.userId else { return }
isSavingLanguage = true