Просмотр исходного кода

RHL-043 feat(admin-users): require username confirmation for deletes

Code_Uwe 1 месяц назад
Родитель
Сommit
261f5225a4
1 измененных файлов с 41 добавлено и 4 удалено
  1. 41 4
      components/admin/users/DeleteUserDialog.jsx

+ 41 - 4
components/admin/users/DeleteUserDialog.jsx

@@ -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"
 					>