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() {