Files
HealthLog/src/components/admin/danger-zone-section.tsx
T
Marc-André Bombeck 40f7196c20 feat(medications,coach,admin,perf): era corrections, a truthful weekly status and one card language everywhere
The dashboard snapshot fires the moment login succeeds instead of
waiting for the page chunk (start ~670 to ~200 ms throttled). Guided
follow-ups are personalised from the user's snapshot and an answer gets
the coach's streamed reaction before the next question. Archived
schedule eras can be corrected through a superseding revision that
keeps the original as an audit trail, era writes serialise per
medication, and duplicate dose times collapse in validation. A rolling
medication whose next dose is tomorrow can no longer read as heavily
overdue — the card status derives strictly from the served next-due —
and the adherence bars keep a fixed geometry in every state. The
server-wide assistant key gains its admin card, the one duplicated
coach preference collapses onto a single owner, twenty-seven admin and
settings card headers move onto the shared pattern with sixty
containers normalised, and the remaining hydration warnings are gone.
2026-06-11 13:57:15 +02:00

128 lines
4.4 KiB
TypeScript

"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 { useTranslations } from "@/lib/i18n/context";
import { apiDelete } from "@/lib/api/api-fetch";
export function DangerZoneSection() {
const { t } = useTranslations();
const queryClient = useQueryClient();
const [wipeMsg, setWipeMsg] = useState<string | null>(null);
const wipeAllData = useMutation({
mutationFn: async () => {
return apiDelete<{
measurements: number;
intakeEvents: number;
medications: number;
apiTokens: number;
withingsConnections: number;
authChallenges: number;
notificationChannels: number;
pushSubscriptions: number;
telegramScheduledDeletions: number;
}>("/api/admin/data", { confirm: "DELETE ALL" });
},
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 (
<div className="bg-destructive/5 border-destructive/30 rounded-xl border p-4 sm:p-6">
{/* v1.4.19 A8 / F-08: page header `admin.section.danger-zone.*`
already names the section, so the card-level title was a
duplicate of the page title. The destructive icon stays so
the card still flags itself as dangerous at a glance. */}
<AlertTriangle className="text-destructive h-5 w-5" aria-hidden="true" />
<div className="mt-4">
<p className="text-sm font-medium">{t("admin.deleteAllData")}</p>
<p className="text-muted-foreground text-xs">
{t("admin.deleteAllDescription")}
</p>
<div className="mt-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
size="sm"
disabled={wipeAllData.isPending}
>
{wipeAllData.isPending ? (
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
) : (
<Trash2 className="h-3.5 w-3.5" />
)}
{t("admin.deleteButton")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("admin.deleteAllConfirm")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("admin.deleteAllConfirmDescription")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
disabled={wipeAllData.isPending}
aria-busy={wipeAllData.isPending || undefined}
onClick={() => wipeAllData.mutate()}
>
{wipeAllData.isPending && (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
)}
{t("admin.finalDelete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
{wipeMsg && (
<p
className={`mt-2 text-sm ${
wipeAllData.isError ? "text-destructive" : "text-dracula-green"
}`}
>
{wipeMsg}
</p>
)}
</div>
</div>
);
}