diff --git a/src/app/components/UserDeleteDialog.tsx b/src/app/components/UserDeleteDialog.tsx new file mode 100644 index 0000000..468ed0e --- /dev/null +++ b/src/app/components/UserDeleteDialog.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import type { ManagedUser } from "@/lib/userManagement"; + +interface UserDeleteDialogProps { + user: ManagedUser; + open: boolean; + onOpenChange: (open: boolean) => void; + onChanged: () => Promise; +} + +export function UserDeleteDialog({ + user, + open, + onOpenChange, + onChanged, +}: UserDeleteDialogProps) { + const t = useTranslations("userManagement"); + const [error, setError] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (!open) { + setError(null); + setSaving(false); + } + }, [open]); + + const confirm = async () => { + if (saving) return; + setSaving(true); + setError(null); + try { + const response = await fetch( + `/api/auth/users/${encodeURIComponent(user.username)}`, + { method: "DELETE", credentials: "same-origin" } + ); + const body = (await response.json().catch(() => ({}))) as { + message?: string; + error?: string; + }; + if (!response.ok) { + throw new Error(body.message || body.error || "Request failed."); + } + toast.success(t("remove.done", { username: user.username })); + onOpenChange(false); + await onChanged(); + } catch (reason) { + setError(reason instanceof Error ? reason.message : "Request failed."); + setSaving(false); + } + }; + + return ( + + + + {t("remove.title")} + + {t("remove.description", { username: user.username })} + + + {error && ( +

+ {error} +

+ )} + + + + +
+
+ ); +} diff --git a/src/app/components/UserResetPasswordDialog.tsx b/src/app/components/UserResetPasswordDialog.tsx new file mode 100644 index 0000000..f4aa30c --- /dev/null +++ b/src/app/components/UserResetPasswordDialog.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { FormEvent, useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { isValidPassword, type ManagedUser } from "@/lib/userManagement"; + +interface UserResetPasswordDialogProps { + user: ManagedUser; + open: boolean; + onOpenChange: (open: boolean) => void; + onChanged: () => Promise; +} + +export function UserResetPasswordDialog({ + user, + open, + onOpenChange, + onChanged, +}: UserResetPasswordDialogProps) { + const t = useTranslations("userManagement"); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (!open) { + setPassword(""); + setError(null); + setSaving(false); + } + }, [open]); + + const canSubmit = isValidPassword(password) && !saving; + + const submit = async (event: FormEvent) => { + event.preventDefault(); + if (!canSubmit) return; + setSaving(true); + setError(null); + try { + const response = await fetch( + `/api/auth/users/${encodeURIComponent(user.username)}`, + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + credentials: "same-origin", + body: JSON.stringify({ password }), + } + ); + const body = (await response.json().catch(() => ({}))) as { + message?: string; + error?: string; + }; + if (!response.ok) { + throw new Error(body.message || body.error || "Request failed."); + } + toast.success(t("reset.done", { username: user.username })); + onOpenChange(false); + await onChanged(); + } catch (reason) { + setError(reason instanceof Error ? reason.message : "Request failed."); + setSaving(false); + } + }; + + return ( + + + + {t("reset.title")} + + {t("reset.description", { username: user.username })} + + +
+
+ + setPassword(event.target.value)} + disabled={saving} + autoFocus + /> +

+ {t("reset.passwordHint")} +

+
+ {error && ( +

+ {error} +

+ )} + + + + +
+
+
+ ); +}