diff --git a/app.json b/app.json index 22115fb..d7665d7 100644 --- a/app.json +++ b/app.json @@ -10,7 +10,7 @@ "ios": { "supportsTablet": true, "bundleIdentifier": "com.beenvoice.app", - "buildNumber": "27", + "buildNumber": "28", "icon": "./assets/beenvoice.icon", "infoPlist": { "ITSAppUsesNonExemptEncryption": false, diff --git a/app/(app)/more/settings.tsx b/app/(app)/more/settings.tsx index 9fa3c39..297bab1 100644 --- a/app/(app)/more/settings.tsx +++ b/app/(app)/more/settings.tsx @@ -2,7 +2,15 @@ import { useState } from "react"; import Constants from "expo-constants"; import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; -import { Alert, Platform, Pressable, StyleSheet, Switch, Text, View } from "react-native"; +import { + Alert, + Platform, + Pressable, + StyleSheet, + Switch, + Text, + View, +} from "react-native"; import { TabPage } from "@/components/TabPage"; import { TabScrollView } from "@/components/TabScrollView"; @@ -20,7 +28,10 @@ import { useAppLock } from "@/contexts/AppLockContext"; import { useAuthClient, useSession } from "@/contexts/AuthContext"; import { type ColorMode, useAppTheme } from "@/contexts/ThemeContext"; import { startAdditionalAccountSignIn } from "@/lib/add-account"; -import { confirmRemoveAccount, finishAccountRemoval } from "@/lib/account-actions"; +import { + confirmRemoveAccount, + finishAccountRemoval, +} from "@/lib/account-actions"; import { performAuthReset } from "@/lib/auth-session"; import { api } from "@/lib/trpc"; @@ -61,6 +72,7 @@ export default function SettingsScreen() { lock, } = useAppLock(); const profileQuery = api.settings.getProfile.useQuery(); + const deleteAccountMutation = api.settings.deleteAccount.useMutation(); const [pinPrompt, setPinPrompt] = useState< | { mode: "create" } @@ -110,10 +122,54 @@ export default function SettingsScreen() { function confirmSignOut() { Alert.alert("Sign out", "Sign out of this account on this device?", [ { text: "Cancel", style: "cancel" }, - { text: "Sign out", style: "destructive", onPress: () => void handleSignOut() }, + { + text: "Sign out", + style: "destructive", + onPress: () => void handleSignOut(), + }, ]); } + async function handleDeleteAccount() { + if (!activeAccountId) return; + + try { + await deleteAccountMutation.mutateAsync({ + confirmText: "DELETE MY ACCOUNT", + }); + const result = await removeAccount(activeAccountId); + await finishAccountRemoval({ + result, + authClient, + clearActiveAccount, + activeAccountId, + }); + if (result.remainingCount > 0) { + router.replace("/(auth)/select-account"); + } + } catch (error) { + Alert.alert( + "Could not delete account", + error instanceof Error ? error.message : "Please try again.", + ); + } + } + + function confirmDeleteAccount() { + Alert.alert( + "Permanently delete account?", + "This deletes your account, invoices, clients, businesses, expenses, time entries, uploaded files, and sign-in data. This cannot be undone.", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete Account", + style: "destructive", + onPress: () => void handleDeleteAccount(), + }, + ], + ); + } + function confirmInstanceChange() { Alert.alert( "Server updated", @@ -145,7 +201,10 @@ export default function SettingsScreen() { await enableLock(pin); setPinPrompt(null); } catch (err) { - Alert.alert("Could not enable lock", err instanceof Error ? err.message : "Try again"); + Alert.alert( + "Could not enable lock", + err instanceof Error ? err.message : "Try again", + ); } return; } @@ -169,7 +228,10 @@ export default function SettingsScreen() { if (pinPrompt?.mode === "change-next") { const success = await changePin(pendingPin, pin); if (!success) { - Alert.alert("Could not change PIN", "Check your current PIN and try again."); + Alert.alert( + "Could not change PIN", + "Check your current PIN and try again.", + ); return; } setPendingPin(""); @@ -207,9 +269,13 @@ export default function SettingsScreen() { : "Enter your current PIN." } confirmLabel={ - pinPrompt?.mode === "create" || pinPrompt?.mode === "change-next" ? "Save" : "Continue" + pinPrompt?.mode === "create" || pinPrompt?.mode === "change-next" + ? "Save" + : "Continue" + } + requireConfirmation={ + pinPrompt?.mode === "create" || pinPrompt?.mode === "change-next" } - requireConfirmation={pinPrompt?.mode === "create" || pinPrompt?.mode === "change-next"} onCancel={() => { setPendingPin(""); setPinPrompt(null); @@ -218,7 +284,10 @@ export default function SettingsScreen() { /> + } keyboardShouldPersistTaps="handled" > @@ -253,21 +322,43 @@ export default function SettingsScreen() { void switchAccount(account.id)} - style={({ pressed }) => [styles.accountMain, pressed && styles.pressed]} + style={({ pressed }) => [ + styles.accountMain, + pressed && styles.pressed, + ]} > - + {account.name || account.email} - + {account.email} - + {account.instanceUrl.replace(/^https?:\/\//, "")} {isActive ? ( - Active + + Active + ) : null} - handleRemoveAccount(account.id, account.name || account.email) + handleRemoveAccount( + account.id, + account.name || account.email, + ) } - style={({ pressed }) => [styles.removeButton, pressed && styles.pressed]} + style={({ pressed }) => [ + styles.removeButton, + pressed && styles.pressed, + ]} > - + ); @@ -293,10 +394,13 @@ export default function SettingsScreen() {