feat: add AdminCreateUserForm component for user creation with validation and reset functionality

This commit is contained in:
ngc2207 2024-12-03 16:11:44 +08:00
parent b075614a42
commit 3d4b6c93ca
2 changed files with 249 additions and 242 deletions

View File

@ -0,0 +1,246 @@
"use client";
import { z } from "zod";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import logger from "@/lib/logger";
import { useEffect } from "react";
import { Loader2 } from "lucide-react";
import { useForm } from "react-hook-form";
import { useToast } from "@/hooks/use-toast";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { ToastAction } from "@/components/ui/toast";
import { zodResolver } from "@hookform/resolvers/zod";
import adminCreateUserStore from "@/app/actions/(gitea)/admin/users/store";
const formSchema = z.object({
source_id: z.coerce.number().int().nonnegative().default(0),
visibility: z.enum(["public", "limited", "private"]).default("public"),
username: z
.string()
.min(1, "Username cannot be empty")
.max(40, "Username is too long (max 40 characters)"),
email: z
.string()
.email("Invalid email address")
.max(254, "Email is too long (max 254 characters)"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.max(255, "Password must be at most 255 characters"),
must_change_password: z.coerce.boolean().default(true),
});
const defaultValues: z.infer<typeof formSchema> = {
source_id: 0,
visibility: "public",
username: "",
email: "",
password: "",
must_change_password: true,
};
export default function AdminCreateUserForm() {
const { toast } = useToast();
const { user, error, loading, adminCreateUser } = adminCreateUserStore();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues,
});
function onSubmit(values: z.infer<typeof formSchema>) {
logger.info({ values }, "submitting form");
adminCreateUser(values).then(() => {
form.reset(defaultValues);
});
}
function onReset() {
form.reset(defaultValues);
}
useEffect(() => {
form.reset(defaultValues);
}, [form]);
useEffect(() => {
if (user) {
toast({
variant: "default",
title: "User created",
description: `User ${user.login} created successfully`,
action: (
<ToastAction altText="Create another">Create another</ToastAction>
),
});
} else if (error) {
toast({
variant: "destructive",
title: "Failed to create user",
description: `${error.message}`,
action: <ToastAction altText="Try again">Try again</ToastAction>,
});
}
}, [user, error, toast]);
return (
<Card className="mx-auto min-w-80">
<CardHeader>
<CardTitle className="text-2xl">Gitea</CardTitle>
<CardDescription>Admin Create User Card</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="source_id"
render={({ field }) => (
<FormItem>
<FormLabel>Authentication Source</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent position="popper">
<SelectItem value="0">Local</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="visibility"
render={({ field }) => (
<FormItem>
<FormLabel>User visibility</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent position="popper">
<SelectItem value="public">Public</SelectItem>
<SelectItem value="limited">Limited</SelectItem>
<SelectItem value="private">Private</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username of the user" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input placeholder="Email of the user" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Password of the user"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="must_change_password"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
className="mt-0.5"
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Require user to change password</FormLabel>
<FormDescription>(recommended)</FormDescription>
<FormMessage />
</div>
</FormItem>
)}
/>
<div className="flex items-center justify-between">
<Button variant="outline" onClick={onReset}>
Reset
</Button>
<Button type="submit" disabled={loading}>
{loading ? (
<>
<Loader2 className="animate-spin" /> Submitting...
</>
) : (
"Submit"
)}
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
}

View File

@ -1,248 +1,9 @@
"use client";
import { z } from "zod";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import logger from "@/lib/logger";
import { useEffect } from "react";
import { Loader2 } from "lucide-react";
import { useForm } from "react-hook-form";
import { useToast } from "@/hooks/use-toast";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { ToastAction } from "@/components/ui/toast";
import { zodResolver } from "@hookform/resolvers/zod";
import adminCreateUserStore from "@/app/actions/(gitea)/admin/users/store";
const formSchema = z.object({
source_id: z.coerce.number().int().nonnegative().default(0),
visibility: z.enum(["public", "limited", "private"]).default("public"),
username: z
.string()
.min(1, "Username cannot be empty")
.max(40, "Username is too long (max 40 characters)"),
email: z
.string()
.email("Invalid email address")
.max(254, "Email is too long (max 254 characters)"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.max(255, "Password must be at most 255 characters"),
must_change_password: z.coerce.boolean().default(true),
});
const defaultValues: z.infer<typeof formSchema> = {
source_id: 0,
visibility: "public",
username: "",
email: "",
password: "",
must_change_password: true,
};
export default function AdminCreateUserForm() {
const { toast } = useToast();
const { user, error, loading, adminCreateUser } = adminCreateUserStore();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues,
});
function onSubmit(values: z.infer<typeof formSchema>) {
logger.info({ values }, "submitting form");
adminCreateUser(values).then(() => {
form.reset(defaultValues);
});
}
function onReset() {
form.reset(defaultValues);
}
useEffect(() => {
form.reset(defaultValues);
}, [form]);
useEffect(() => {
if (user) {
toast({
variant: "default",
title: "User created",
description: `User ${user.login} created successfully`,
action: (
<ToastAction altText="Create another">Create another</ToastAction>
),
});
} else if (error) {
toast({
variant: "destructive",
title: "Failed to create user",
description: `${error.message}`,
action: <ToastAction altText="Try again">Try again</ToastAction>,
});
}
}, [user, error, toast]);
import AdminCreateUserForm from "./admin-create-user-form";
export default function AdminCreateUserPage() {
return (
<div className="h-full w-full flex items-center justify-center px-4">
<Card className="mx-auto min-w-80">
<CardHeader>
<CardTitle className="text-2xl">Gitea</CardTitle>
<CardDescription>Admin Create User Card</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="source_id"
render={({ field }) => (
<FormItem>
<FormLabel>Authentication Source</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent position="popper">
<SelectItem value="0">Local</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="visibility"
render={({ field }) => (
<FormItem>
<FormLabel>User visibility</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent position="popper">
<SelectItem value="public">Public</SelectItem>
<SelectItem value="limited">Limited</SelectItem>
<SelectItem value="private">Private</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username of the user" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input placeholder="Email of the user" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Password of the user"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="must_change_password"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
className="mt-0.5"
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Require user to change password</FormLabel>
<FormDescription>(recommended)</FormDescription>
<FormMessage />
</div>
</FormItem>
)}
/>
<div className="flex items-center justify-between">
<Button variant="outline" onClick={onReset}>
Reset
</Button>
<Button type="submit" disabled={loading}>
{loading ? (
<>
<Loader2 className="animate-spin" /> Submitting...
</>
) : (
"Submit"
)}
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
<AdminCreateUserForm />
</div>
);
}