1- import { Container , Flex , Image , Input , Text } from "@chakra-ui/react "
1+ import { zodResolver } from "@hookform/resolvers/zod "
22import {
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"
1510import 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
1941export 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-
3452function 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