Files
butcem/src/features/admin-users/components/user-action-dialogs.tsx
T

220 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { PasswordInput } from "@/components/password-input";
import { ResponsiveDialog } from "@/components/responsive-dialog";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Textarea } from "@/components/ui/textarea";
import { tr } from "@/i18n/tr";
import type { ActionResult } from "@/lib/action-result";
import {
banUserSchema,
setPasswordSchema,
type BanUserInput,
type SetPasswordInput,
} from "@/shared/schemas/user";
import type { AdminUser } from "../types";
const t = tr.adminUsers;
type DialogProps<T> = {
user: AdminUser | undefined;
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (user: AdminUser, values: T) => Promise<ActionResult>;
};
export function SetPasswordDialog({
user,
open,
onOpenChange,
onConfirm,
}: DialogProps<{ password: string }>) {
const form = useForm<SetPasswordInput>({
resolver: zodResolver(setPasswordSchema),
defaultValues: { password: "" },
});
useEffect(() => {
if (open) form.reset({ password: "" });
}, [open, form]);
async function onSubmit(values: SetPasswordInput) {
if (!user) return;
const result = await onConfirm(user, values);
if (result.ok) onOpenChange(false);
else form.setError("root", { message: result.error });
}
return (
<ResponsiveDialog
open={open}
onOpenChange={onOpenChange}
title={t.password.title}
description={user ? t.password.description(user.name) : undefined}
footer={
<>
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
{tr.common.cancel}
</Button>
<Button type="submit" form="password-form" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? <Loader2 className="animate-spin" /> : null}
{t.password.confirm}
</Button>
</>
}
>
<form id="password-form" onSubmit={form.handleSubmit(onSubmit)} noValidate>
<FieldGroup>
<Controller
name="password"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="new-password">{t.password.newPassword}</FieldLabel>
<PasswordInput
{...field}
id="new-password"
autoComplete="new-password"
aria-invalid={fieldState.invalid}
aria-describedby="new-password-hint"
/>
<FieldDescription id="new-password-hint">{t.form.passwordHint}</FieldDescription>
{fieldState.invalid ? <FieldError errors={[fieldState.error]} /> : null}
</Field>
)}
/>
{form.formState.errors.root ? (
<FieldError>{form.formState.errors.root.message}</FieldError>
) : null}
</FieldGroup>
</form>
</ResponsiveDialog>
);
}
export function BanUserDialog({
user,
open,
onOpenChange,
onConfirm,
}: DialogProps<{ reason?: string }>) {
const form = useForm<BanUserInput, unknown, { reason?: string }>({
resolver: zodResolver(banUserSchema),
defaultValues: { reason: "" },
});
useEffect(() => {
if (open) form.reset({ reason: "" });
}, [open, form]);
async function onSubmit(values: { reason?: string }) {
if (!user) return;
const result = await onConfirm(user, values);
if (result.ok) onOpenChange(false);
else form.setError("root", { message: result.error });
}
return (
<ResponsiveDialog
open={open}
onOpenChange={onOpenChange}
title={t.ban.title}
description={user ? t.ban.description(user.name) : undefined}
footer={
<>
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
{tr.common.cancel}
</Button>
<Button
type="submit"
form="ban-form"
variant="destructive"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting ? <Loader2 className="animate-spin" /> : null}
{t.ban.confirm}
</Button>
</>
}
>
<form id="ban-form" onSubmit={form.handleSubmit(onSubmit)} noValidate>
<FieldGroup>
<Controller
name="reason"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="ban-reason">{t.ban.reason}</FieldLabel>
<Textarea
{...field}
value={field.value ?? ""}
id="ban-reason"
rows={3}
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid ? <FieldError errors={[fieldState.error]} /> : null}
</Field>
)}
/>
{form.formState.errors.root ? (
<FieldError>{form.formState.errors.root.message}</FieldError>
) : null}
</FieldGroup>
</form>
</ResponsiveDialog>
);
}
export function DeleteUserDialog({ user, open, onOpenChange, onConfirm }: DialogProps<void>) {
const [pending, setPending] = useState(false);
const [error, setError] = useState<string | null>(null);
function handleOpenChange(next: boolean) {
if (!next) setError(null);
onOpenChange(next);
}
async function confirm() {
if (!user) return;
setPending(true);
const result = await onConfirm(user, undefined);
setPending(false);
if (result.ok) handleOpenChange(false);
else setError(result.error);
}
return (
<AlertDialog open={open} onOpenChange={handleOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t.remove.title}</AlertDialogTitle>
<AlertDialogDescription>
{user ? t.remove.description(user.name) : null}
</AlertDialogDescription>
</AlertDialogHeader>
{error ? <FieldError>{error}</FieldError> : null}
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>{tr.common.cancel}</AlertDialogCancel>
{/* AlertDialogAction otomatik kapanır; sonuç beklenmesi için normal düğme. */}
<Button variant="destructive" onClick={confirm} disabled={pending}>
{pending ? <Loader2 className="animate-spin" /> : null}
{t.remove.confirm}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}