'use client'; import { useEffect, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AlertTriangle, Loader2, Mail } from 'lucide-react'; import { toast } from 'sonner'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { apiFetch } from '@/lib/api/client'; interface Props { open: boolean; onOpenChange: (open: boolean) => void; clientIds: string[]; onDeleted?: (deletedCount: number) => void; } type Stage = 'intent' | 'confirm'; export function BulkHardDeleteDialog({ open, onOpenChange, clientIds, onDeleted }: Props) { const qc = useQueryClient(); const [stage, setStage] = useState('intent'); const [code, setCode] = useState(''); const [typedPhrase, setTypedPhrase] = useState(''); const [maskedEmail, setMaskedEmail] = useState(null); const expectedPhrase = `DELETE ${clientIds.length} CLIENT${clientIds.length === 1 ? '' : 'S'}`; useEffect(() => { if (open) { setStage('intent'); setCode(''); setTypedPhrase(''); setMaskedEmail(null); } }, [open]); const requestCode = useMutation({ mutationFn: () => apiFetch<{ data: { count: number; sentToMaskedEmail: string } }>( '/api/v1/clients/bulk-hard-delete-request', { method: 'POST', body: { ids: clientIds } }, ), onSuccess: (res) => { setMaskedEmail(res.data.sentToMaskedEmail); setStage('confirm'); toast.success(`Code sent to ${res.data.sentToMaskedEmail}`); }, onError: (err: unknown) => { toast.error(err instanceof Error ? err.message : 'Failed to send code'); }, }); const bulkDelete = useMutation({ mutationFn: () => apiFetch<{ data: { deletedCount: number } }>('/api/v1/clients/bulk-hard-delete', { method: 'POST', body: { ids: clientIds, code, typedPhrase }, }), onSuccess: (res) => { const n = res.data.deletedCount; const failed = clientIds.length - n; if (failed === 0) { toast.success(`${n} client${n === 1 ? '' : 's'} permanently deleted.`); } else { toast.warning(`${n} of ${clientIds.length} deleted. ${failed} failed (see audit log).`); } qc.invalidateQueries({ queryKey: ['clients'] }); onOpenChange(false); onDeleted?.(n); }, onError: (err: unknown) => { toast.error(err instanceof Error ? err.message : 'Bulk delete failed'); }, }); const phraseMatches = typedPhrase.trim().toUpperCase() === expectedPhrase; const codeValid = /^\d{4}$/.test(code.trim()); return ( Permanently delete {clientIds.length} client{clientIds.length === 1 ? '' : 's'} All selected clients must already be archived. This cannot be undone. {stage === 'intent' ? (

We’ll email a 4-digit confirmation code to your account address. The code is tied to this exact set of clients and expires in 10 minutes.

For each client we delete: client record + addresses, contacts, notes, tags, portal user, GDPR records, all interests, all reservations. Signed documents, email threads, files and reminders are detached but kept.
) : (
Code sent to {maskedEmail}. Enter both fields below.
setCode(e.target.value.replace(/\D/g, ''))} placeholder="0000" className="font-mono tracking-[0.4em] text-center text-lg" autoComplete="off" />
setTypedPhrase(e.target.value)} placeholder={expectedPhrase} autoComplete="off" className="font-mono" />
)} {stage === 'intent' ? ( ) : ( )}
); }