Skip to content

Commit a19af90

Browse files
committed
♻️ Refactor Login component and form handling
1 parent c42cb9f commit a19af90

1 file changed

Lines changed: 99 additions & 78 deletions

File tree

frontend/src/routes/login.tsx

Lines changed: 99 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1-
import { Container, Image, Input, Text } from "@chakra-ui/react"
1+
import { zodResolver } from "@hookform/resolvers/zod"
22
import {
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

1010
import 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"
1422
import { PasswordInput } from "@/components/ui/password-input"
1523
import 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

1935
export const Route = createFileRoute("/login")({
2036
component: Login,
@@ -28,12 +44,9 @@ export const Route = createFileRoute("/login")({
2844
})
2945

3046
function 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

Comments
 (0)