98 lines
4.2 KiB
React
98 lines
4.2 KiB
React
|
|
import { useState } from 'react';
|
|||
|
|
import { toast } from 'sonner';
|
|||
|
|
import { ShieldAlert, Loader2, Trash2 } from 'lucide-react';
|
|||
|
|
import { api } from '@/api';
|
|||
|
|
import { Button } from '@/components/ui/button';
|
|||
|
|
import { Input } from '@/components/ui/input';
|
|||
|
|
import {
|
|||
|
|
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|||
|
|
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger,
|
|||
|
|
} from '@/components/ui/alert-dialog';
|
|||
|
|
import { SectionCard } from './dataShared';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Danger zone: permanently erase the user's financial data. Type-to-confirm, and
|
|||
|
|
* makes clear that the account/login/2FA are preserved. Reuses POST
|
|||
|
|
* /api/user/erase-data (transactional, audited, re-seeds default categories).
|
|||
|
|
*/
|
|||
|
|
export default function EraseDataSection({ onErased, cardProps = {} }) {
|
|||
|
|
const [open, setOpen] = useState(false);
|
|||
|
|
const [confirm, setConfirm] = useState('');
|
|||
|
|
const [busy, setBusy] = useState(false);
|
|||
|
|
const canErase = confirm.trim().toUpperCase() === 'ERASE';
|
|||
|
|
|
|||
|
|
async function handleErase() {
|
|||
|
|
if (!canErase) return;
|
|||
|
|
setBusy(true);
|
|||
|
|
try {
|
|||
|
|
const r = await api.eraseMyData(confirm.trim());
|
|||
|
|
toast.success(`Your data was erased — ${r.erased} record${r.erased === 1 ? '' : 's'} removed.`);
|
|||
|
|
setOpen(false);
|
|||
|
|
setConfirm('');
|
|||
|
|
onErased?.();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error(err.message || 'Erase failed.');
|
|||
|
|
} finally {
|
|||
|
|
setBusy(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<SectionCard {...cardProps}>
|
|||
|
|
<div className="space-y-4 px-6 py-5">
|
|||
|
|
<div className="rounded-lg border border-rose-300/60 bg-rose-50 p-4 dark:border-rose-800/50 dark:bg-rose-950/20">
|
|||
|
|
<div className="flex items-start gap-3">
|
|||
|
|
<ShieldAlert className="mt-0.5 h-5 w-5 shrink-0 text-rose-600 dark:text-rose-400" />
|
|||
|
|
<div className="min-w-0 text-sm">
|
|||
|
|
<p className="font-medium text-rose-700 dark:text-rose-300">This permanently deletes your financial data.</p>
|
|||
|
|
<p className="mt-1 text-muted-foreground">
|
|||
|
|
Bills, payments, transactions, categories, bank connections, imports and rules will be erased and
|
|||
|
|
can’t be recovered. Your <span className="font-medium text-foreground">account, login and 2FA are kept</span>.
|
|||
|
|
Consider downloading a backup first.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<AlertDialog open={open} onOpenChange={(v) => { setOpen(v); if (!v) setConfirm(''); }}>
|
|||
|
|
<AlertDialogTrigger asChild>
|
|||
|
|
<Button
|
|||
|
|
variant="outline"
|
|||
|
|
className="gap-2 border-rose-300 text-rose-600 hover:bg-rose-50 dark:border-rose-800/60 dark:text-rose-400 dark:hover:bg-rose-950/30"
|
|||
|
|
>
|
|||
|
|
<Trash2 className="h-4 w-4" /> Erase my data…
|
|||
|
|
</Button>
|
|||
|
|
</AlertDialogTrigger>
|
|||
|
|
<AlertDialogContent>
|
|||
|
|
<AlertDialogHeader>
|
|||
|
|
<AlertDialogTitle>Erase all your data?</AlertDialogTitle>
|
|||
|
|
<AlertDialogDescription>
|
|||
|
|
This permanently deletes your bills, payments, transactions, categories, bank connections and imports.
|
|||
|
|
It cannot be undone. Type <span className="font-mono font-semibold text-foreground">ERASE</span> to confirm.
|
|||
|
|
</AlertDialogDescription>
|
|||
|
|
</AlertDialogHeader>
|
|||
|
|
<Input
|
|||
|
|
autoFocus
|
|||
|
|
value={confirm}
|
|||
|
|
onChange={(e) => setConfirm(e.target.value)}
|
|||
|
|
placeholder="Type ERASE"
|
|||
|
|
className="font-mono"
|
|||
|
|
aria-label="Type ERASE to confirm"
|
|||
|
|
/>
|
|||
|
|
<AlertDialogFooter>
|
|||
|
|
<AlertDialogCancel disabled={busy}>Cancel</AlertDialogCancel>
|
|||
|
|
<AlertDialogAction
|
|||
|
|
disabled={!canErase || busy}
|
|||
|
|
onClick={(e) => { e.preventDefault(); handleErase(); }}
|
|||
|
|
className="bg-rose-600 text-white hover:bg-rose-700"
|
|||
|
|
>
|
|||
|
|
{busy ? <><Loader2 className="mr-1.5 h-4 w-4 animate-spin" />Erasing…</> : 'Permanently erase'}
|
|||
|
|
</AlertDialogAction>
|
|||
|
|
</AlertDialogFooter>
|
|||
|
|
</AlertDialogContent>
|
|||
|
|
</AlertDialog>
|
|||
|
|
</div>
|
|||
|
|
</SectionCard>
|
|||
|
|
);
|
|||
|
|
}
|