feat(frontend): confirmation dialogs and undo for destructive-feeling actions

Volk aufloesen, Bienenstand deaktivieren, Benutzer deaktivieren und
Webhook loeschen liefen bisher ohne jede Rueckfrage direkt auf Klick.
Neue wiederverwendbare ConfirmDialog-Komponente erzwingt jetzt eine
bewusste Bestaetigung. Zusaetzlich zeigen Volk/Bienenstand/Benutzer
nach dem Aufloesen/Deaktivieren einen Reaktivieren-Button (nutzt die
neuen Backend-Endpunkte), da diese Aktionen nie ein echtes Loeschen
sind und ein versehentlicher Klick bisher nicht rueckgaengig zu
machen war.
This commit is contained in:
Nick Szaunig committed 2026-08-23 11:50:45 +02:00
1 parent 57979f0371
commit 6ab028d1be
9 files changed
+231 -36

No files matched your search

@@ -8,6 +8,7 @@ import { deactivateUser, fetchUsers } from "@/lib/users/api";
vi.mock("@/lib/users/api", () => ({
fetchUsers: vi.fn(),
deactivateUser: vi.fn(),
activateUser: vi.fn(),
createUser: vi.fn(),
}));
@@ -1,9 +1,11 @@
"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { deactivateUser, fetchUsers } from "@/lib/users/api";
import { toast } from "sonner";
import { activateUser, deactivateUser, fetchUsers } from "@/lib/users/api";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ConfirmDialog } from "@/components/confirm-dialog";
import {
Table,
TableBody,
@@ -25,7 +27,20 @@ export default function UsersPage() {
mutationFn: deactivateUser,
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["users"] });
toast.success("Benutzer deaktiviert", {
description: "Kann jederzeit über „Aktivieren“ wiederhergestellt werden.",
});
},
onError: () => toast.error("Benutzer konnte nicht deaktiviert werden"),
});
const activateMutation = useMutation({
mutationFn: activateUser,
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["users"] });
toast.success("Benutzer aktiviert");
},
onError: () => toast.error("Benutzer konnte nicht aktiviert werden"),
});
return (
@@ -65,14 +80,27 @@ export default function UsersPage() {
</Badge>
</TableCell>
<TableCell className="text-right">
<Button
variant="outline"
size="sm"
disabled={!user.isActive || deactivateMutation.isPending}
onClick={() => deactivateMutation.mutate(user.id)}
>
Deaktivieren
</Button>
{user.isActive ? (
<ConfirmDialog
triggerLabel="Deaktivieren"
triggerSize="sm"
triggerDisabled={deactivateMutation.isPending}
title="Benutzer deaktivieren?"
description={`„${user.name}“ kann sich danach nicht mehr anmelden. Der Zugang kann jederzeit über „Aktivieren“ wiederhergestellt werden.`}
confirmLabel="Deaktivieren"
isPending={deactivateMutation.isPending}
onConfirm={() => deactivateMutation.mutate(user.id)}
/>
) : (
<Button
variant="outline"
size="sm"
disabled={activateMutation.isPending}
onClick={() => activateMutation.mutate(user.id)}
>
Aktivieren
</Button>
)}
</TableCell>
</TableRow>
))}
@@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { deleteWebhook, fetchWebhooks } from "@/lib/webhooks/api";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ConfirmDialog } from "@/components/confirm-dialog";
import {
Table,
TableBody,
@@ -28,6 +28,7 @@ export default function WebhooksPage() {
void queryClient.invalidateQueries({ queryKey: ["webhooks"] });
toast.success("Webhook gelöscht");
},
onError: () => toast.error("Webhook konnte nicht gelöscht werden"),
});
return (
@@ -83,14 +84,16 @@ export default function WebhooksPage() {
</Badge>
</TableCell>
<TableCell className="text-right">
<Button
variant="outline"
size="sm"
disabled={deleteMutation.isPending}
onClick={() => deleteMutation.mutate(webhook.id)}
>
Löschen
</Button>
<ConfirmDialog
triggerLabel="Löschen"
triggerSize="sm"
triggerDisabled={deleteMutation.isPending}
title="Webhook löschen?"
description={`Der Webhook für „${webhook.url}“ wird endgültig gelöscht. Externe Systeme erhalten danach keine Ereignisse mehr über diese Verbindung, und der Vorgang kann nicht rückgängig gemacht werden.`}
confirmLabel="Endgültig löschen"
isPending={deleteMutation.isPending}
onConfirm={() => deleteMutation.mutate(webhook.id)}
/>
</TableCell>
</TableRow>
))}