'use client'; import { useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { useTranslations } from 'next-intl'; import { Link, useRouter } from '@/i18n/navigation'; import { Phone, ShieldCheck } from 'lucide-react'; import { authApi } from '@/lib/api/auth'; import { useAuth } from '@/lib/hooks/useAuth'; import { Button } from '@/components/ui/shared/Button'; import { Input } from '@/components/ui/shared/Input'; import { TopBarControls } from '@/components/ui/shared/TopBarControls'; type ForgotPasswordForm = { mobile: string; code: string; }; function normalizeIranMobile(input: string): string { let digits = input.replace(/\D/g, ''); if (digits.startsWith('98') && digits.length === 12) digits = digits.slice(2); if (digits.startsWith('0') && digits.length === 11) digits = digits.slice(1); return digits; } export default function ForgotPasswordPage() { const t = useTranslations('auth'); const tCommon = useTranslations('common'); const tValidation = useTranslations('validation'); const router = useRouter(); const { refreshSession } = useAuth(); const [step, setStep] = useState<'mobile' | 'code'>('mobile'); const [error, setError] = useState(null); const [isSending, setIsSending] = useState(false); const [isVerifying, setIsVerifying] = useState(false); const [sentMobile, setSentMobile] = useState(''); const schema = useMemo( () => z.object({ mobile: z .string() .min(1, tValidation('mobileRequired')) .refine((value) => /^9\d{9}$/.test(normalizeIranMobile(value)), { message: tValidation('mobileInvalid'), }), code: z.string(), }), [tValidation], ); const { register, handleSubmit, getValues, formState: { errors }, } = useForm({ resolver: zodResolver(schema), defaultValues: { mobile: '', code: '' }, }); const onSendCode = async () => { const mobile = getValues('mobile'); const parsed = schema.safeParse({ mobile, code: '' }); if (!parsed.success) { setError(parsed.error.issues[0]?.message ?? tValidation('mobileInvalid')); return; } try { setError(null); setIsSending(true); await authApi.sendForgotPasswordCode(mobile); setSentMobile(mobile); setStep('code'); } catch (err: unknown) { const message = err instanceof Error ? err.message : t('codeSendFailed'); setError(message || t('codeSendFailed')); } finally { setIsSending(false); } }; const onVerify = async (data: ForgotPasswordForm) => { if (!data.code.trim()) { setError(tValidation('codeRequired')); return; } try { setError(null); setIsVerifying(true); const response = await authApi.verifyForgotPasswordCode( sentMobile || data.mobile, data.code.trim(), ); const orgs = response.data.organizations; if (orgs.length === 1) { await authApi.selectOrganization(orgs[0].id); localStorage.setItem('currentOrganizationId', orgs[0].id); await refreshSession(); router.push('/settings/account?reset=1'); return; } if (orgs.length > 1) { sessionStorage.setItem('authRedirect', '/settings/account?reset=1'); await refreshSession(); router.push('/select-organization'); return; } await refreshSession(); router.push('/settings/account?reset=1'); } catch (err: unknown) { const message = err instanceof Error ? err.message : t('verifyFailed'); setError(message || t('verifyFailed')); } finally { setIsVerifying(false); } }; return (
{tCommon('appName')}

{t('forgotPasswordTitle')}

{step === 'mobile' ? t('forgotPasswordSubtitle') : t('codeSentHint')}

undefined)} > {step === 'mobile' ? ( } /> ) : ( } /> )} {error && (

{error}

)} {step === 'mobile' ? ( ) : ( )}

{t('backToSignIn')}

); }