"use client"; import { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, Loader2, Trash2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { SettingsCard } from "@/components/settings/settings-card"; import { SettingsCardHeader } from "@/components/settings/_card-header"; import { useTranslations } from "@/lib/i18n/context"; import { apiDelete } from "@/lib/api/api-fetch"; /** * The literal an admin must type to arm the wipe. Mirrors the Backups * `RESTORE` gate — and matches the server's typed-token defence at * `src/app/api/admin/data/route.ts` (which 422s any other `confirm`). */ const WIPE_CONFIRM_TOKEN = "DELETE ALL"; export function DangerZoneSection() { const { t } = useTranslations(); const queryClient = useQueryClient(); const [wipeMsg, setWipeMsg] = useState(null); // Typed-confirmation gate. The destructive button stays disabled until // the admin types `DELETE ALL` verbatim, matching the Backups Restore // dialog (which forces typing `RESTORE`) so the FAR more destructive // global wipe is not the weaker gate. Reset whenever the dialog closes. const [open, setOpen] = useState(false); const [typed, setTyped] = useState(""); const matched = typed.trim() === WIPE_CONFIRM_TOKEN; const wipeAllData = useMutation({ mutationFn: async () => { // Idempotency-Key prevents a double-submit from re-running the // destructive transaction, matching the Backups restore path. const idempotencyKey = `admin-data-wipe-${crypto.randomUUID()}`; return apiDelete<{ measurements: number; intakeEvents: number; medications: number; apiTokens: number; withingsConnections: number; authChallenges: number; notificationChannels: number; pushSubscriptions: number; telegramScheduledDeletions: number; }>( "/api/admin/data", { confirm: WIPE_CONFIRM_TOKEN }, { headers: { "Idempotency-Key": idempotencyKey } }, ); }, onSuccess: (data) => { queryClient.invalidateQueries(); setWipeMsg( t("admin.deletedResult", { measurements: data.measurements, medications: data.medications, intakeEvents: data.intakeEvents, apiTokens: data.apiTokens, withingsConnections: data.withingsConnections, authChallenges: data.authChallenges, notificationChannels: data.notificationChannels, pushSubscriptions: data.pushSubscriptions, telegramScheduledDeletions: data.telegramScheduledDeletions, }), ); }, onError: (err: Error) => { setWipeMsg(err.message); }, }); return ( // v1.18.10 (W7) — the card SURFACE is neutral like every other admin / // settings section (`SettingsCard`: bg-card + border-border): a red card // edge reads as alarming, not premium. The danger signal lives ONLY in // the destructive delete button + its typed-confirmation dialog, which // are alarm enough. The header keeps the `AlertTriangle` icon so the // section still flags its intent. { setOpen(next); if (!next) setTyped(""); }} > {t("admin.deleteAllConfirm")} {t("admin.deleteAllConfirmDescription")}
setTyped(e.target.value)} autoComplete="off" spellCheck={false} placeholder={WIPE_CONFIRM_TOKEN} />
{t("common.cancel")} { if (!matched) { e.preventDefault(); return; } setOpen(false); setTyped(""); wipeAllData.mutate(); }} > {wipeAllData.isPending && ( )} {t("admin.finalDelete")}
} /> {wipeMsg && (

{wipeMsg}

)}
); }