Files
full-stack-fastapi-template/frontend/src/components/UserSettings/ChangePassword.tsx
T
Yurii Motov a8f1ba0e70 ⬆️ Bump @hey-api/openapi-ts from 0.73.0 to 0.97.3 and migrate to new client (#2388)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 18:25:28 +02:00

147 lines
4.2 KiB
TypeScript

import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { type UpdatePassword, UsersService } from "@/client"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { LoadingButton } from "@/components/ui/loading-button"
import { PasswordInput } from "@/components/ui/password-input"
import useCustomToast from "@/hooks/useCustomToast"
import { handleError } from "@/utils"
const formSchema = z
.object({
current_password: z
.string()
.min(1, { message: "Password is required" })
.min(8, { message: "Password must be at least 8 characters" }),
new_password: z
.string()
.min(1, { message: "Password is required" })
.min(8, { message: "Password must be at least 8 characters" }),
confirm_password: z
.string()
.min(1, { message: "Password confirmation is required" }),
})
.refine((data) => data.new_password === data.confirm_password, {
message: "The passwords don't match",
path: ["confirm_password"],
})
type FormData = z.infer<typeof formSchema>
const ChangePassword = () => {
const { showSuccessToast, showErrorToast } = useCustomToast()
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
mode: "onSubmit",
criteriaMode: "all",
defaultValues: {
current_password: "",
new_password: "",
confirm_password: "",
},
})
const mutation = useMutation({
mutationFn: (data: UpdatePassword) =>
UsersService.updatePasswordMe({ body: data }),
onSuccess: () => {
showSuccessToast("Password updated successfully")
form.reset()
},
onError: handleError.bind(showErrorToast),
})
const onSubmit = async (data: FormData) => {
mutation.mutate(data)
}
return (
<div className="max-w-md">
<h3 className="text-lg font-semibold py-4">Change Password</h3>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<FormField
control={form.control}
name="current_password"
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>Current Password</FormLabel>
<FormControl>
<PasswordInput
data-testid="current-password-input"
placeholder="••••••••"
aria-invalid={fieldState.invalid}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="new_password"
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>New Password</FormLabel>
<FormControl>
<PasswordInput
data-testid="new-password-input"
placeholder="••••••••"
aria-invalid={fieldState.invalid}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirm_password"
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<PasswordInput
data-testid="confirm-password-input"
placeholder="••••••••"
aria-invalid={fieldState.invalid}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<LoadingButton
type="submit"
loading={mutation.isPending}
className="self-start"
>
Update Password
</LoadingButton>
</form>
</Form>
</div>
)
}
export default ChangePassword