| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- "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 { Input } from "@/components/ui/input";
- import { Label } from "@/components/ui/label";
- import { adminDeleteUser, ApiClientError } from "@/lib/frontend/apiClient";
- import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
- import { isUsernameConfirmationMatch } from "@/lib/frontend/admin/users/userManagementUx";
- 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 [typedUsername, setTypedUsername] = React.useState("");
- const effectiveDisabled = Boolean(disabled || isSubmitting);
- const canConfirmDelete = isUsernameConfirmationMatch({
- expectedUsername: user?.username,
- typedUsername,
- });
- 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);
- setTypedUsername("");
- }
- }, []);
- const handleDelete = React.useCallback(async () => {
- if (!user?.id) return;
- if (effectiveDisabled || !canConfirmDelete) 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, canConfirmDelete, 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>
- <div className="space-y-2 rounded-lg border border-destructive/50 bg-destructive/5 p-3">
- <p className="text-sm font-medium text-destructive">
- Achtung: Dieser Benutzer wird dauerhaft gelöscht.
- </p>
- <p className="text-sm text-muted-foreground">
- Bitte geben Sie den Benutzernamen{" "}
- <strong>{user.username}</strong> ein, um das Löschen zu
- bestätigen.
- </p>
- <div className="grid gap-2">
- <Label htmlFor="du-confirm-username">
- Bitte Benutzernamen eingeben, um zu bestätigen
- </Label>
- <Input
- id="du-confirm-username"
- value={typedUsername}
- onChange={(e) => setTypedUsername(e.target.value)}
- disabled={effectiveDisabled}
- autoCapitalize="none"
- autoCorrect="off"
- spellCheck={false}
- placeholder={String(user.username || "")}
- />
- </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 || !canConfirmDelete}
- onClick={handleDelete}
- title="Endgültig löschen"
- >
- {isSubmitting ? "Löscht…" : "Löschen"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
- }
|