From 79aed97f7df3c4d05a0283a2a3e044ac4d67add2 Mon Sep 17 00:00:00 2001 From: psavarmattas Date: Sat, 15 Aug 2026 20:31:50 +0100 Subject: [PATCH] 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. Co-Authored-By: Claude Sonnet 5 --- Outpost/Features/Account/SettingsView.swift | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Outpost/Features/Account/SettingsView.swift b/Outpost/Features/Account/SettingsView.swift index 5bdb771..b68cede 100644 --- a/Outpost/Features/Account/SettingsView.swift +++ b/Outpost/Features/Account/SettingsView.swift @@ -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( @@ -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