Skip to content

Commit 65f79ec

Browse files
committed
♻️ Refactor ChangePassword component
1 parent 77781a3 commit 65f79ec

1 file changed

Lines changed: 116 additions & 53 deletions

File tree

Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,143 @@
1-
import { Box, Button, Container, Heading, VStack } from "@chakra-ui/react"
1+
import { zodResolver } from "@hookform/resolvers/zod"
22
import { useMutation } from "@tanstack/react-query"
3-
import { type SubmitHandler, useForm } from "react-hook-form"
4-
import { FiLock } from "react-icons/fi"
3+
import { useForm } from "react-hook-form"
4+
import { z } from "zod"
55

6-
import { type ApiError, type UpdatePassword, UsersService } from "@/client"
6+
import { type UpdatePassword, UsersService } from "@/client"
7+
import {
8+
Form,
9+
FormControl,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
} from "@/components/ui/form"
15+
import { LoadingButton } from "@/components/ui/loading-button"
16+
import { PasswordInput } from "@/components/ui/password-input"
717
import useCustomToast from "@/hooks/useCustomToast"
8-
import { confirmPasswordRules, handleError, passwordRules } from "@/utils"
9-
import { PasswordInput } from "../ui/password-input"
18+
import { handleError } from "@/utils"
1019

11-
interface UpdatePasswordForm extends UpdatePassword {
12-
confirm_password: string
13-
}
20+
const formSchema = z
21+
.object({
22+
current_password: z
23+
.string()
24+
.min(1, { message: "Password is required" })
25+
.min(8, { message: "Password must be at least 8 characters" }),
26+
new_password: z
27+
.string()
28+
.min(1, { message: "Password is required" })
29+
.min(8, { message: "Password must be at least 8 characters" }),
30+
confirm_password: z
31+
.string()
32+
.min(1, { message: "Password confirmation is required" }),
33+
})
34+
.refine((data) => data.new_password === data.confirm_password, {
35+
message: "The passwords don't match",
36+
path: ["confirm_password"],
37+
})
38+
39+
type FormData = z.infer<typeof formSchema>
1440

1541
const ChangePassword = () => {
16-
const { showSuccessToast } = useCustomToast()
17-
const {
18-
register,
19-
handleSubmit,
20-
reset,
21-
getValues,
22-
formState: { errors, isSubmitting },
23-
} = useForm<UpdatePasswordForm>({
24-
mode: "onBlur",
42+
const { showSuccessToast, showErrorToast } = useCustomToast()
43+
const form = useForm<FormData>({
44+
resolver: zodResolver(formSchema),
45+
mode: "onSubmit",
2546
criteriaMode: "all",
47+
defaultValues: {
48+
current_password: "",
49+
new_password: "",
50+
confirm_password: "",
51+
},
2652
})
2753

2854
const mutation = useMutation({
2955
mutationFn: (data: UpdatePassword) =>
3056
UsersService.updatePasswordMe({ requestBody: data }),
3157
onSuccess: () => {
3258
showSuccessToast("Password updated successfully.")
33-
reset()
34-
},
35-
onError: (err: ApiError) => {
36-
handleError(err)
59+
form.reset()
3760
},
61+
onError: handleError.bind(showErrorToast),
3862
})
3963

40-
const onSubmit: SubmitHandler<UpdatePasswordForm> = async (data) => {
64+
const onSubmit = async (data: FormData) => {
4165
mutation.mutate(data)
4266
}
4367

4468
return (
45-
<Container maxW="full">
46-
<Heading size="sm" py={4}>
47-
Change Password
48-
</Heading>
49-
<Box as="form" onSubmit={handleSubmit(onSubmit)}>
50-
<VStack gap={4} w={{ base: "100%", md: "sm" }}>
51-
<PasswordInput
52-
type="current_password"
53-
startElement={<FiLock />}
54-
{...register("current_password", passwordRules())}
55-
placeholder="Current Password"
56-
errors={errors}
69+
<div className="max-w-md">
70+
<h3 className="text-lg font-semibold py-4">Change Password</h3>
71+
<Form {...form}>
72+
<form
73+
onSubmit={form.handleSubmit(onSubmit)}
74+
className="flex flex-col gap-4"
75+
>
76+
<FormField
77+
control={form.control}
78+
name="current_password"
79+
render={({ field, fieldState }) => (
80+
<FormItem>
81+
<FormLabel>Current Password</FormLabel>
82+
<FormControl>
83+
<PasswordInput
84+
placeholder="••••••••"
85+
aria-invalid={fieldState.invalid}
86+
{...field}
87+
/>
88+
</FormControl>
89+
<FormMessage />
90+
</FormItem>
91+
)}
5792
/>
58-
<PasswordInput
59-
type="new_password"
60-
startElement={<FiLock />}
61-
{...register("new_password", passwordRules())}
62-
placeholder="New Password"
63-
errors={errors}
93+
94+
<FormField
95+
control={form.control}
96+
name="new_password"
97+
render={({ field, fieldState }) => (
98+
<FormItem>
99+
<FormLabel>New Password</FormLabel>
100+
<FormControl>
101+
<PasswordInput
102+
placeholder="••••••••"
103+
aria-invalid={fieldState.invalid}
104+
{...field}
105+
/>
106+
</FormControl>
107+
<FormMessage />
108+
</FormItem>
109+
)}
64110
/>
65-
<PasswordInput
66-
type="confirm_password"
67-
startElement={<FiLock />}
68-
{...register("confirm_password", confirmPasswordRules(getValues))}
69-
placeholder="Confirm Password"
70-
errors={errors}
111+
112+
<FormField
113+
control={form.control}
114+
name="confirm_password"
115+
render={({ field, fieldState }) => (
116+
<FormItem>
117+
<FormLabel>Confirm Password</FormLabel>
118+
<FormControl>
119+
<PasswordInput
120+
placeholder="••••••••"
121+
aria-invalid={fieldState.invalid}
122+
{...field}
123+
/>
124+
</FormControl>
125+
<FormMessage />
126+
</FormItem>
127+
)}
71128
/>
72-
</VStack>
73-
<Button variant="solid" mt={4} type="submit" loading={isSubmitting}>
74-
Save
75-
</Button>
76-
</Box>
77-
</Container>
129+
130+
<LoadingButton
131+
type="submit"
132+
loading={mutation.isPending}
133+
className="self-start"
134+
>
135+
Update Password
136+
</LoadingButton>
137+
</form>
138+
</Form>
139+
</div>
78140
)
79141
}
142+
80143
export default ChangePassword

0 commit comments

Comments
 (0)