| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- "use client";
- import React from "react";
- import { Trash2 } from "lucide-react";
- import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
- } from "@/components/ui/dialog";
- import { Button } from "@/components/ui/button";
- import { Badge } from "@/components/ui/badge";
- import { adminDeleteUser, ApiClientError } from "@/lib/frontend/apiClient";
- import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
- import {
- notifySuccess,
- notifyError,
- notifyApiError,
- } from "@/lib/frontend/ui/toast";
- import { ROLE_LABELS_DE } from "@/components/admin/users/usersUi";
- function formatUserLabel(user) {
- const username = typeof user?.username === "string" ? user.username : "—";
- const email = typeof user?.email === "string" ? user.email : "—";
- return `${username} (${email})`;
- }
- export default function DeleteUserDialog({
- user,
- disabled = false,
- onDeleted,
- }) {
- const [open, setOpen] = React.useState(false);
- const [isSubmitting, setIsSubmitting] = React.useState(false);
- const [error, setError] = React.useState(null);
- const effectiveDisabled = Boolean(disabled || isSubmitting);
- const redirectToLoginExpired = React.useCallback(() => {
- const next =
- typeof window !== "undefined"
- ? `${window.location.pathname}${window.location.search}`
- : "/admin/users";
- window.location.replace(
- buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }),
- );
- }, []);
- const handleOpenChange = React.useCallback((nextOpen) => {
- setOpen(nextOpen);
- if (!nextOpen) setError(null);
- }, []);
- const handleDelete = React.useCallback(async () => {
- if (!user?.id) return;
- if (effectiveDisabled) return;
- setError(null);
- setIsSubmitting(true);
- try {
- await adminDeleteUser(String(user.id));
- notifySuccess({
- title: "Benutzer gelöscht",
- description: `"${formatUserLabel(user)}" wurde entfernt.`,
- });
- setOpen(false);
- if (typeof onDeleted === "function") onDeleted();
- } catch (err) {
- if (err instanceof ApiClientError) {
- if (err.code === "AUTH_UNAUTHENTICATED") {
- notifyApiError(err);
- redirectToLoginExpired();
- return;
- }
- if (err.code === "USER_NOT_FOUND") {
- const mapped = {
- title: "Benutzer nicht gefunden.",
- description:
- "Der Benutzer existiert nicht (mehr). Bitte aktualisieren Sie die Liste.",
- };
- setError(mapped);
- notifyError(mapped);
- return;
- }
- if (
- err.code === "VALIDATION_INVALID_FIELD" &&
- err.details?.reason === "SELF_DELETE_FORBIDDEN"
- ) {
- const mapped = {
- title: "Nicht möglich",
- description: "Sie können Ihr eigenes Konto nicht löschen.",
- };
- setError(mapped);
- notifyError(mapped);
- return;
- }
- setError({
- title: "Benutzer konnte nicht gelöscht werden.",
- description: "Bitte versuchen Sie es erneut.",
- });
- notifyApiError(err, {
- fallbackTitle: "Benutzer konnte nicht gelöscht werden.",
- fallbackDescription: "Bitte versuchen Sie es erneut.",
- });
- return;
- }
- setError({
- title: "Benutzer konnte nicht gelöscht werden.",
- description: "Bitte versuchen Sie es erneut.",
- });
- notifyError({
- title: "Benutzer konnte nicht gelöscht werden.",
- description: "Bitte versuchen Sie es erneut.",
- });
- } finally {
- setIsSubmitting(false);
- }
- }, [user, effectiveDisabled, onDeleted, redirectToLoginExpired]);
- if (!user || typeof user.id !== "string" || !user.id) return null;
- const roleLabel = ROLE_LABELS_DE[user.role] || String(user.role || "—");
- return (
- <Dialog open={open} onOpenChange={handleOpenChange}>
- <DialogTrigger asChild>
- <Button
- type="button"
- variant="destructive"
- size="icon-sm"
- disabled={disabled}
- title="Benutzer löschen"
- aria-label="Benutzer löschen"
- >
- <Trash2 className="h-4 w-4" />
- </Button>
- </DialogTrigger>
- <DialogContent className="sm:max-w-lg">
- <DialogHeader>
- <DialogTitle>Benutzer löschen</DialogTitle>
- <DialogDescription>
- Diese Aktion kann nicht rückgängig gemacht werden.
- </DialogDescription>
- </DialogHeader>
- <div className="space-y-3">
- <div className="rounded-lg border p-3">
- <div className="text-sm font-medium">{formatUserLabel(user)}</div>
- <div className="mt-2 flex flex-wrap gap-2">
- <Badge variant="secondary">{roleLabel}</Badge>
- {user.branchId ? (
- <Badge variant="outline">{user.branchId}</Badge>
- ) : null}
- {user.mustChangePassword ? (
- <Badge variant="destructive">Passwortwechsel</Badge>
- ) : (
- <Badge variant="secondary">Kein Passwortwechsel</Badge>
- )}
- </div>
- </div>
- {error ? (
- <div className="rounded-lg border border-destructive/40 bg-card p-3">
- <p className="text-sm font-medium text-destructive">
- {error.title}
- </p>
- {error.description ? (
- <p className="mt-1 text-sm text-muted-foreground">
- {error.description}
- </p>
- ) : null}
- </div>
- ) : null}
- </div>
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- disabled={effectiveDisabled}
- onClick={() => setOpen(false)}
- >
- Abbrechen
- </Button>
- <Button
- type="button"
- variant="destructive"
- disabled={effectiveDisabled}
- onClick={handleDelete}
- title="Endgültig löschen"
- >
- {isSubmitting ? "Löscht…" : "Löschen"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
- }
|