142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useForm } from "react-hook-form";
|
|
import { updateUser } from "@/actions/users";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { useServerAction } from "@/hooks/use-server-action";
|
|
import { type UpdateUserInput, updateUserSchema } from "@/lib/validators/user";
|
|
|
|
interface UserEditFormProps {
|
|
user: {
|
|
id: number;
|
|
username: string;
|
|
mail: string;
|
|
rank: number;
|
|
motto: string;
|
|
credits: number;
|
|
pixels: number;
|
|
diamonds: number;
|
|
duckets: number;
|
|
};
|
|
/** Ranks loaded from the `permissions` table (id + human-readable name). */
|
|
ranks?: Array<{ id: number; name: string }>;
|
|
}
|
|
|
|
// Fallback used if the page does not supply the DB-loaded rank list.
|
|
const FALLBACK_RANKS: Array<{ id: number; name: string }> = [
|
|
{ id: 1, name: "User" },
|
|
{ id: 2, name: "Bronze" },
|
|
{ id: 3, name: "Silver" },
|
|
{ id: 4, name: "Gold" },
|
|
{ id: 5, name: "Moderator" },
|
|
{ id: 6, name: "Admin" },
|
|
{ id: 7, name: "Super Admin" },
|
|
];
|
|
|
|
export function UserEditForm({ user, ranks }: UserEditFormProps) {
|
|
const rankOptions = ranks && ranks.length > 0 ? ranks : FALLBACK_RANKS;
|
|
const { isPending, run } = useServerAction();
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
setValue,
|
|
formState: { errors },
|
|
} = useForm<UpdateUserInput>({
|
|
resolver: zodResolver(updateUserSchema),
|
|
defaultValues: {
|
|
username: user.username,
|
|
mail: user.mail,
|
|
rank: user.rank,
|
|
motto: user.motto,
|
|
credits: user.credits,
|
|
pixels: user.pixels,
|
|
diamonds: user.diamonds,
|
|
duckets: user.duckets,
|
|
},
|
|
});
|
|
|
|
function onSubmit(data: UpdateUserInput) {
|
|
run(() => updateUser({ id: user.id, ...data }), {
|
|
successMessage: "User updated successfully.",
|
|
});
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Edit User</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="username">Username</Label>
|
|
<Input id="username" {...register("username")} />
|
|
{errors.username && <p className="text-sm text-destructive">{errors.username.message}</p>}
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="mail">Email</Label>
|
|
<Input id="mail" type="email" {...register("mail")} />
|
|
{errors.mail && <p className="text-sm text-destructive">{errors.mail.message}</p>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="rank">Rank</Label>
|
|
<Select
|
|
defaultValue={String(user.rank)}
|
|
onValueChange={(v) => setValue("rank", parseInt(v, 10))}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select rank" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{rankOptions.map((r) => (
|
|
<SelectItem key={r.id} value={String(r.id)}>
|
|
{r.id} — {r.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="motto">Motto</Label>
|
|
<Input id="motto" {...register("motto")} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="credits">Credits</Label>
|
|
<Input id="credits" type="number" {...register("credits", { valueAsNumber: true })} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="pixels">Pixels</Label>
|
|
<Input id="pixels" type="number" {...register("pixels", { valueAsNumber: true })} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="diamonds">Diamonds</Label>
|
|
<Input id="diamonds" type="number" {...register("diamonds", { valueAsNumber: true })} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="duckets">Duckets</Label>
|
|
<Input id="duckets" type="number" {...register("duckets", { valueAsNumber: true })} />
|
|
</div>
|
|
</div>
|
|
|
|
<Button type="submit" disabled={isPending}>
|
|
{isPending ? "Saving..." : "Save Changes"}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|