|
|
@@ -14,9 +14,12 @@ import {
|
|
|
} 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,
|
|
|
@@ -39,8 +42,13 @@ export default function DeleteUserDialog({
|
|
|
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 =
|
|
|
@@ -55,12 +63,15 @@ export default function DeleteUserDialog({
|
|
|
|
|
|
const handleOpenChange = React.useCallback((nextOpen) => {
|
|
|
setOpen(nextOpen);
|
|
|
- if (!nextOpen) setError(null);
|
|
|
+ if (!nextOpen) {
|
|
|
+ setError(null);
|
|
|
+ setTypedUsername("");
|
|
|
+ }
|
|
|
}, []);
|
|
|
|
|
|
const handleDelete = React.useCallback(async () => {
|
|
|
if (!user?.id) return;
|
|
|
- if (effectiveDisabled) return;
|
|
|
+ if (effectiveDisabled || !canConfirmDelete) return;
|
|
|
|
|
|
setError(null);
|
|
|
setIsSubmitting(true);
|
|
|
@@ -132,7 +143,7 @@ export default function DeleteUserDialog({
|
|
|
} finally {
|
|
|
setIsSubmitting(false);
|
|
|
}
|
|
|
- }, [user, effectiveDisabled, onDeleted, redirectToLoginExpired]);
|
|
|
+ }, [user, effectiveDisabled, canConfirmDelete, onDeleted, redirectToLoginExpired]);
|
|
|
|
|
|
if (!user || typeof user.id !== "string" || !user.id) return null;
|
|
|
|
|
|
@@ -177,6 +188,32 @@ export default function DeleteUserDialog({
|
|
|
</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">
|
|
|
@@ -204,7 +241,7 @@ export default function DeleteUserDialog({
|
|
|
<Button
|
|
|
type="button"
|
|
|
variant="destructive"
|
|
|
- disabled={effectiveDisabled}
|
|
|
+ disabled={effectiveDisabled || !canConfirmDelete}
|
|
|
onClick={handleDelete}
|
|
|
title="Endgültig löschen"
|
|
|
>
|