1- import { Container , Image , Input , Text } from "@chakra-ui/react "
1+ import { zodResolver } from "@hookform/resolvers/zod "
22import {
33 createFileRoute ,
4- Link as RouterLink ,
54 redirect ,
5+ Link as RouterLink ,
66} from "@tanstack/react-router"
7- import { type SubmitHandler , useForm } from "react-hook-form"
8- import { FiLock , FiMail } from "react-icons/fi "
7+ import { useForm } from "react-hook-form"
8+ import { z } from "zod "
99
1010import type { Body_login_login_access_token as AccessToken } from "@/client"
11- import { Button } from "@/components/ui/button"
12- import { Field } from "@/components/ui/field"
13- import { InputGroup } from "@/components/ui/input-group"
11+ import { AuthLayout } from "@/components/Common/AuthLayout"
12+ import {
13+ Form ,
14+ FormControl ,
15+ FormField ,
16+ FormItem ,
17+ FormLabel ,
18+ FormMessage ,
19+ } from "@/components/ui/form"
20+ import { Input } from "@/components/ui/input"
21+ import { LoadingButton } from "@/components/ui/loading-button"
1422import { PasswordInput } from "@/components/ui/password-input"
1523import useAuth , { isLoggedIn } from "@/hooks/useAuth"
16- import Logo from "/assets/images/fastapi-logo.svg"
17- import { emailPattern , passwordRules } from "../utils"
24+
25+ const formSchema = z . object ( {
26+ username : z . email ( ) ,
27+ password : z
28+ . string ( )
29+ . min ( 1 , { message : "Password is required" } )
30+ . min ( 8 , { message : "Password must be at least 8 characters" } ) ,
31+ } ) satisfies z . ZodType < AccessToken >
32+
33+ type FormData = z . infer < typeof formSchema >
1834
1935export const Route = createFileRoute ( "/login" ) ( {
2036 component : Login ,
@@ -28,12 +44,9 @@ export const Route = createFileRoute("/login")({
2844} )
2945
3046function Login ( ) {
31- const { loginMutation, error, resetError } = useAuth ( )
32- const {
33- register,
34- handleSubmit,
35- formState : { errors, isSubmitting } ,
36- } = useForm < AccessToken > ( {
47+ const { loginMutation } = useAuth ( )
48+ const form = useForm < FormData > ( {
49+ resolver : zodResolver ( formSchema ) ,
3750 mode : "onBlur" ,
3851 criteriaMode : "all" ,
3952 defaultValues : {
@@ -42,71 +55,79 @@ function Login() {
4255 } ,
4356 } )
4457
45- const onSubmit : SubmitHandler < AccessToken > = async ( data ) => {
46- if ( isSubmitting ) return
47-
48- resetError ( )
49-
50- try {
51- await loginMutation . mutateAsync ( data )
52- } catch {
53- // error is handled by useAuth hook
54- }
58+ const onSubmit = async ( data : FormData ) => {
59+ if ( loginMutation . isPending ) return
60+ await loginMutation . mutateAsync ( data )
5561 }
5662
5763 return (
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 . username }
78- errorText = { errors . username ?. message || ! ! error }
79- >
80- < InputGroup w = "100%" startElement = { < FiMail /> } >
81- < Input
82- { ...register ( "username" , {
83- required : "Username is required" ,
84- pattern : emailPattern ,
85- } ) }
86- placeholder = "Email"
87- type = "email"
88- />
89- </ InputGroup >
90- </ Field >
91- < PasswordInput
92- type = "password"
93- startElement = { < FiLock /> }
94- { ...register ( "password" , passwordRules ( ) ) }
95- placeholder = "Password"
96- errors = { errors }
97- />
98- < RouterLink to = "/recover-password" className = "main-link" >
99- Forgot Password?
100- </ RouterLink >
101- < Button variant = "solid" type = "submit" loading = { isSubmitting } size = "md" >
102- Log In
103- </ Button >
104- < Text >
105- Don't have an account?{ " " }
106- < RouterLink to = "/signup" className = "main-link" >
107- Sign Up
108- </ RouterLink >
109- </ Text >
110- </ Container >
64+ < AuthLayout >
65+ < Form { ...form } >
66+ < form
67+ onSubmit = { form . handleSubmit ( onSubmit ) }
68+ className = "flex flex-col gap-6"
69+ >
70+ < div className = "flex flex-col items-center gap-2 text-center" >
71+ < h1 className = "text-2xl font-bold" > Login to your account</ h1 >
72+ </ div >
73+
74+ < div className = "grid gap-4" >
75+ < FormField
76+ control = { form . control }
77+ name = "username"
78+ render = { ( { field } ) => (
79+ < FormItem >
80+ < FormLabel > Email</ FormLabel >
81+ < FormControl >
82+ < Input
83+ placeholder = "user@example.com"
84+ type = "email"
85+ { ...field }
86+ />
87+ </ FormControl >
88+ < FormMessage className = "text-xs" />
89+ </ FormItem >
90+ ) }
91+ />
92+
93+ < FormField
94+ control = { form . control }
95+ name = "password"
96+ render = { ( { field } ) => (
97+ < FormItem >
98+ < div className = "flex items-center" >
99+ < FormLabel > Password</ FormLabel >
100+ < RouterLink
101+ to = "/recover-password"
102+ className = "ml-auto text-sm underline-offset-4 hover:underline"
103+ >
104+ Forgot your password?
105+ </ RouterLink >
106+ </ div >
107+ < FormControl >
108+ < PasswordInput placeholder = "Password" { ...field } />
109+ </ FormControl >
110+ < FormMessage className = "text-xs" />
111+ </ FormItem >
112+ ) }
113+ />
114+
115+ < LoadingButton
116+ type = "submit"
117+ loading = { loginMutation . isPending }
118+ >
119+ Log In
120+ </ LoadingButton >
121+ </ div >
122+
123+ < div className = "text-center text-sm" >
124+ Don't have an account yet?{ " " }
125+ < RouterLink to = "/signup" className = "underline underline-offset-4" >
126+ Sign up
127+ </ RouterLink >
128+ </ div >
129+ </ form >
130+ </ Form >
131+ </ AuthLayout >
111132 )
112133}
0 commit comments