Skip to content

Commit e93ef6e

Browse files
committed
♻️ Refactor SignUp component and form handling
1 parent a19af90 commit e93ef6e

1 file changed

Lines changed: 125 additions & 90 deletions

File tree

frontend/src/routes/signup.tsx

Lines changed: 125 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
1-
import { Container, Flex, Image, Input, Text } from "@chakra-ui/react"
1+
import { zodResolver } from "@hookform/resolvers/zod"
22
import {
33
createFileRoute,
44
Link as RouterLink,
55
redirect,
66
} from "@tanstack/react-router"
7-
import { type SubmitHandler, useForm } from "react-hook-form"
8-
import { FiLock, FiUser } from "react-icons/fi"
7+
import { useForm } from "react-hook-form"
8+
import { z } from "zod"
99

10-
import type { UserRegister } from "@/client"
11-
import { Button } from "@/components/ui/button"
12-
import { Field } from "@/components/ui/field"
13-
import { InputGroup } from "@/components/ui/input-group"
14-
import { PasswordInput } from "@/components/ui/password-input"
1510
import useAuth, { isLoggedIn } from "@/hooks/useAuth"
16-
import { confirmPasswordRules, emailPattern, passwordRules } from "@/utils"
17-
import Logo from "/assets/images/fastapi-logo.svg"
11+
import { AuthLayout } from "@/components/Common/AuthLayout"
12+
import { LoadingButton } from "@/components/ui/loading-button"
13+
import { Input } from "@/components/ui/input"
14+
import { PasswordInput } from "@/components/ui/password-input"
15+
import {
16+
Form,
17+
FormControl,
18+
FormField,
19+
FormItem,
20+
FormLabel,
21+
FormMessage,
22+
} from "@/components/ui/form"
23+
24+
const formSchema = z
25+
.object({
26+
email: z.email(),
27+
full_name: z.string().min(1, { message: "Full Name is required" }),
28+
password: z
29+
.string()
30+
.min(1, { message: "Password is required" })
31+
.min(8, { message: "Password must be at least 8 characters" }),
32+
confirm_password: z.string().min(1, { message: "Password confirmation is required" }),
33+
})
34+
.refine((data) => data.password === data.confirm_password, {
35+
message: "The passwords don't match",
36+
path: ["confirm_password"],
37+
})
38+
39+
type FormData = z.infer<typeof formSchema>
1840

1941
export const Route = createFileRoute("/signup")({
2042
component: SignUp,
@@ -27,18 +49,10 @@ export const Route = createFileRoute("/signup")({
2749
},
2850
})
2951

30-
interface UserRegisterForm extends UserRegister {
31-
confirm_password: string
32-
}
33-
3452
function SignUp() {
3553
const { signUpMutation } = useAuth()
36-
const {
37-
register,
38-
handleSubmit,
39-
getValues,
40-
formState: { errors, isSubmitting },
41-
} = useForm<UserRegisterForm>({
54+
const form = useForm<FormData>({
55+
resolver: zodResolver(formSchema),
4256
mode: "onBlur",
4357
criteriaMode: "all",
4458
defaultValues: {
@@ -49,83 +63,104 @@ function SignUp() {
4963
},
5064
})
5165

52-
const onSubmit: SubmitHandler<UserRegisterForm> = (data) => {
53-
signUpMutation.mutate(data)
66+
const onSubmit = (data: FormData) => {
67+
if (signUpMutation.isPending) return
68+
69+
// exclude confirm_password from submission data
70+
const { confirm_password, ...submitData } = data
71+
signUpMutation.mutate(submitData)
5472
}
5573

5674
return (
57-
<Flex flexDir={{ base: "column", md: "row" }} justify="center" h="100vh">
58-
<Container
59-
as="form"
60-
onSubmit={handleSubmit(onSubmit)}
61-
h="100vh"
62-
maxW="sm"
63-
alignItems="stretch"
64-
justifyContent="center"
65-
gap={4}
66-
centerContent
67-
>
68-
<Image
69-
src={Logo}
70-
alt="FastAPI logo"
71-
height="auto"
72-
maxW="2xs"
73-
alignSelf="center"
74-
mb={4}
75-
/>
76-
<Field
77-
invalid={!!errors.full_name}
78-
errorText={errors.full_name?.message}
75+
<AuthLayout>
76+
<Form {...form}>
77+
<form
78+
onSubmit={form.handleSubmit(onSubmit)}
79+
className="flex flex-col gap-6"
7980
>
80-
<InputGroup w="100%" startElement={<FiUser />}>
81-
<Input
82-
minLength={3}
83-
{...register("full_name", {
84-
required: "Full Name is required",
85-
})}
86-
placeholder="Full Name"
87-
type="text"
81+
<div className="flex flex-col items-center gap-2 text-center">
82+
<h1 className="text-2xl font-bold">Create an account</h1>
83+
</div>
84+
85+
<div className="grid gap-4">
86+
<FormField
87+
control={form.control}
88+
name="full_name"
89+
render={({ field }) => (
90+
<FormItem>
91+
<FormLabel>Full Name</FormLabel>
92+
<FormControl>
93+
<Input placeholder="User" type="text" {...field} />
94+
</FormControl>
95+
<FormMessage />
96+
</FormItem>
97+
)}
98+
/>
99+
100+
<FormField
101+
control={form.control}
102+
name="email"
103+
render={({ field }) => (
104+
<FormItem>
105+
<FormLabel>Email</FormLabel>
106+
<FormControl>
107+
<Input
108+
placeholder="user@example.com"
109+
type="email"
110+
{...field}
111+
/>
112+
</FormControl>
113+
<FormMessage />
114+
</FormItem>
115+
)}
88116
/>
89-
</InputGroup>
90-
</Field>
91117

92-
<Field invalid={!!errors.email} errorText={errors.email?.message}>
93-
<InputGroup w="100%" startElement={<FiUser />}>
94-
<Input
95-
{...register("email", {
96-
required: "Email is required",
97-
pattern: emailPattern,
98-
})}
99-
placeholder="Email"
100-
type="email"
118+
<FormField
119+
control={form.control}
120+
name="password"
121+
render={({ field }) => (
122+
<FormItem>
123+
<FormLabel>Password</FormLabel>
124+
<FormControl>
125+
<PasswordInput placeholder="Password" {...field} />
126+
</FormControl>
127+
<FormMessage />
128+
</FormItem>
129+
)}
101130
/>
102-
</InputGroup>
103-
</Field>
104-
<PasswordInput
105-
type="password"
106-
startElement={<FiLock />}
107-
{...register("password", passwordRules())}
108-
placeholder="Password"
109-
errors={errors}
110-
/>
111-
<PasswordInput
112-
type="confirm_password"
113-
startElement={<FiLock />}
114-
{...register("confirm_password", confirmPasswordRules(getValues))}
115-
placeholder="Confirm Password"
116-
errors={errors}
117-
/>
118-
<Button variant="solid" type="submit" loading={isSubmitting}>
119-
Sign Up
120-
</Button>
121-
<Text>
122-
Already have an account?{" "}
123-
<RouterLink to="/login" className="main-link">
124-
Log In
125-
</RouterLink>
126-
</Text>
127-
</Container>
128-
</Flex>
131+
132+
<FormField
133+
control={form.control}
134+
name="confirm_password"
135+
render={({ field }) => (
136+
<FormItem>
137+
<FormLabel>Confirm Password</FormLabel>
138+
<FormControl>
139+
<PasswordInput placeholder="Confirm Password" {...field} />
140+
</FormControl>
141+
<FormMessage />
142+
</FormItem>
143+
)}
144+
/>
145+
146+
<LoadingButton
147+
type="submit"
148+
className="w-full"
149+
loading={signUpMutation.isPending}
150+
>
151+
Sign Up
152+
</LoadingButton>
153+
</div>
154+
155+
<div className="text-center text-sm">
156+
Already have an account?{" "}
157+
<RouterLink to="/login" className="underline underline-offset-4">
158+
Log in
159+
</RouterLink>
160+
</div>
161+
</form>
162+
</Form>
163+
</AuthLayout>
129164
)
130165
}
131166

0 commit comments

Comments
 (0)