import HCaptcha from '@hcaptcha/react-hcaptcha' import { zodResolver } from '@hookform/resolvers/zod' import { useRouter } from 'next/router' import { useRef, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Form, FormControl, FormField, Input } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { useResetPasswordMutation } from '@/data/misc/reset-password-mutation' import { BASE_PATH } from '@/lib/constants' import { auth } from '@/lib/gotrue' const forgotPasswordSchema = z.object({ email: z.string().min(1, 'Please provide an email address').email('Must be a valid email'), }) const codeSchema = z.object({ code: z.string().regex(/^\d{6}$/, 'Code must be 6 digits'), }) type ForgotPasswordFormData = z.infer type CodeFormData = z.infer export const ForgotPasswordWizard = () => { const [email, setEmail] = useState('') if (email) { return } return setEmail(email)} /> } const ConfirmResetCodeForm = ({ email }: { email: string }) => { const router = useRouter() const [isLoading, setIsLoading] = useState(false) const codeForm = useForm({ resolver: zodResolver(codeSchema as any), defaultValues: { code: '' }, }) const onCodeEntered: SubmitHandler = async (data) => { setIsLoading(true) const { data: { user }, error, } = await auth.verifyOtp({ email, token: data.code, type: 'recovery' }) // This fixes a race condition where the user is redirected to the reset password page without the session being set // which causes the user to be redirected to /sign-in page even though he's signed in await new Promise((resolve) => setTimeout(resolve, 1000)) if (error) { setIsLoading(false) toast.error(`Failed to verify code: ${error.message}`) } else { if (user?.factors?.length) { await router.push({ pathname: '/forgot-password-mfa', query: router.query, }) } else { await router.push({ pathname: '/reset-password', query: router.query, }) } } } return (
( )} />
) } const ForgotPasswordForm = ({ onSuccess }: { onSuccess: (email: string) => void }) => { const captchaRef = useRef(null) const [captchaToken, setCaptchaToken] = useState(null) const forgotPasswordForm = useForm({ resolver: zodResolver(forgotPasswordSchema as any), defaultValues: { email: '' }, }) const { mutate: resetPassword, isPending } = useResetPasswordMutation({ onSuccess: () => { onSuccess(forgotPasswordForm.getValues('email')) }, onError: (error) => { setCaptchaToken(null) captchaRef.current?.resetCaptcha() toast.error(`Failed to send reset email: ${error.message}`) }, }) const onForgotPassword: SubmitHandler = async (data) => { let token = captchaToken if (!token) { const captchaResponse = await captchaRef.current?.execute({ async: true }) token = captchaResponse?.response ?? null } resetPassword({ email: data.email, hcaptchaToken: token, redirectTo: `${ process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' ? location.origin : process.env.NEXT_PUBLIC_SITE_URL }${BASE_PATH}/reset-password`, }) } return (
( )} />
{ setCaptchaToken(token) }} onExpire={() => { setCaptchaToken(null) }} />
) }