"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"; import { AxiosResponse } from "axios"; import { RequestPasswordResetBody, RequestPasswordResetResponse, ResetPasswordBody, ResetPasswordResponse } from "@server/routers/auth"; import { Loader2 } from "lucide-react"; import { Alert, AlertDescription } from "@app/components/ui/alert"; import { toast } from "@app/hooks/useToast"; import { useRouter } from "next/navigation"; import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; import { passwordSchema } from "@server/auth/passwordSchema"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { useTranslations } from "next-intl"; const requestSchema = z.object({ email: z.string().email() }); export type ResetPasswordFormProps = { emailParam?: string; tokenParam?: string; redirect?: string; quickstart?: boolean; }; export default function ResetPasswordForm({ emailParam, tokenParam, redirect, quickstart }: ResetPasswordFormProps) { const router = useRouter(); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const t = useTranslations(); function getState() { if (emailParam && !tokenParam) { return "request"; } if (emailParam && tokenParam) { return "reset"; } return "request"; } const [state, setState] = useState<"request" | "reset" | "mfa">(getState()); const api = createApiClient(useEnvContext()); const formSchema = z .object({ email: z.string().email({ message: t('emailInvalid') }), token: z.string().min(8, { message: t('tokenInvalid') }), password: passwordSchema, confirmPassword: passwordSchema }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: t('passwordNotMatch') }); const mfaSchema = z.object({ code: z.string().length(6, { message: t('pincodeInvalid') }) }); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: emailParam || "", token: tokenParam || "", password: "", confirmPassword: "" } }); const mfaForm = useForm>({ resolver: zodResolver(mfaSchema), defaultValues: { code: "" } }); const requestForm = useForm>({ resolver: zodResolver(requestSchema), defaultValues: { email: emailParam || "" } }); async function onRequest(data: z.infer) { const { email } = data; setIsSubmitting(true); const res = await api .post>( "/auth/reset-password/request", { email } as RequestPasswordResetBody ) .catch((e) => { setError(formatAxiosError(e, t('errorOccurred'))); console.error(t('passwordErrorRequestReset'), e); setIsSubmitting(false); }); if (res && res.data?.data) { setError(null); setState("reset"); setIsSubmitting(false); form.setValue("email", email); } } async function onReset(data: any) { setIsSubmitting(true); const { password, email, token } = form.getValues(); const { code } = mfaForm.getValues(); const res = await api .post>( "/auth/reset-password", { email, token, newPassword: password, code } as ResetPasswordBody ) .catch((e) => { setError(formatAxiosError(e, t('errorOccurred'))); console.error(t('passwordErrorReset'), e); setIsSubmitting(false); }); console.log(res); if (res) { setError(null); if (res.data.data?.codeRequested) { setState("mfa"); setIsSubmitting(false); mfaForm.reset(); return; } setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess')); // Auto-login after successful password reset try { const loginRes = await api.post("/auth/login", { email: form.getValues("email"), password: form.getValues("password") }); if (loginRes.data.data?.codeRequested) { if (redirect) { router.push(`/auth/login?redirect=${redirect}`); } else { router.push("/auth/login"); } return; } if (loginRes.data.data?.emailVerificationRequired) { try { await api.post("/auth/verify-email/request"); } catch (verificationError) { console.error("Failed to send verification code:", verificationError); } if (redirect) { router.push(`/auth/verify-email?redirect=${redirect}`); } else { router.push("/auth/verify-email"); } return; } // Login successful, redirect setTimeout(() => { if (redirect) { const safe = cleanRedirect(redirect); router.push(safe); } else { router.push("/"); } setIsSubmitting(false); }, 1500); } catch (loginError) { // Auto-login failed, but password reset was successful console.error("Auto-login failed:", loginError); setTimeout(() => { if (redirect) { const safe = cleanRedirect(redirect); router.push(safe); } else { router.push("/login"); } setIsSubmitting(false); }, 1500); } } } return (
{quickstart ? t('completeAccountSetup') : t('passwordReset')} {quickstart ? t('completeAccountSetupDescription') : t('passwordResetDescription') }
{state === "request" && (
( {t('email')} {quickstart ? t('accountSetupSent') : t('passwordResetSent') } )} /> )} {state === "reset" && (
( {t('email')} )} /> {!tokenParam && ( ( {quickstart ? t('accountSetupCode') : t('passwordResetCode') } {quickstart ? t('accountSetupCodeDescription') : t('passwordResetCodeDescription') } )} /> )} ( {quickstart ? t('passwordCreate') : t('passwordNew') } )} /> ( {quickstart ? t('passwordCreateConfirm') : t('passwordNewConfirm') } )} /> )} {state === "mfa" && (
( {t('pincodeAuth')}
)} /> )} {error && ( {error} )} {successMessage && ( {successMessage} )}
{(state === "reset" || state === "mfa") && ( )} {state === "request" && ( )} {state === "mfa" && ( )} {(state === "mfa" || state === "reset") && ( )}
); }