import { Loader2 } from "lucide-react" import { useEffect, useMemo, useRef, useState } from "react" import { Navigate, useNavigate } from "react-router-dom" import { toast } from "sonner" import { sendOtp } from "../../api/users" import { Button } from "../../components/ui/button" import { useAuthFlow } from "../../context/AuthFlowContext" import { useTranslation } from "../../hooks/useTranslation" import { AuthOtpInput } from "./AuthOtpInput" import { AuthPanel } from "./AuthPanel" import { formatCooldown, getApiErrorMessage, getOtpRemainingSeconds, handleThrottleError } from "./utils" export function ForgotPasswordOtpPage() { const navigate = useNavigate() const { t, lang } = useTranslation() const { state, setCode, setCooldown, clearCooldown, setOtpDelivery, clearOtpDelivery, } = useAuthFlow() const isRtl = lang === "fa" const autoSendStartedRef = useRef(false) const [isSendingOtp, setIsSendingOtp] = useState(false) const [isContinuing, setIsContinuing] = useState(false) const [now, setNow] = useState(Date.now()) if (!state.forgotPassword.mobile) { return } useEffect(() => { if (!state.forgotPassword.otpExpiresAt || getOtpRemainingSeconds(state.forgotPassword.otpExpiresAt) <= 0) { return } const timer = window.setInterval(() => { setNow(Date.now()) }, 1000) return () => window.clearInterval(timer) }, [state.forgotPassword.otpExpiresAt]) const sendForgotPasswordOtp = async () => { setIsSendingOtp(true) try { const response = await sendOtp(state.forgotPassword.mobile, "forget_password") clearCooldown("forgotPasswordOtpSend") setCode("forgotPassword", "") setOtpDelivery("forgotPassword", response.expires_in_seconds) setNow(Date.now()) toast.success(t.login.toasts.verifySent) } catch (error) { clearOtpDelivery("forgotPassword") if ( !handleThrottleError({ error, cooldownKey: "forgotPasswordOtpSend", setCooldown, formatTime: (seconds) => formatCooldown(seconds, isRtl), throttleCopy: t.login.throttle, }) ) { toast.error(getApiErrorMessage(error, t.login.toasts.failedOtp)) } } finally { setIsSendingOtp(false) } } useEffect(() => { if (!state.forgotPassword.pendingOtpSend || autoSendStartedRef.current) { return } autoSendStartedRef.current = true void sendForgotPasswordOtp() }, [state.forgotPassword.pendingOtpSend]) const otpRemainingSeconds = state.forgotPassword.otpExpiresAt ? Math.max(0, Math.ceil((state.forgotPassword.otpExpiresAt - now) / 1000)) : 0 const alert = useMemo(() => { if (state.cooldowns.forgotPasswordOtpSend <= 0) { return null } const formatted = formatCooldown(state.cooldowns.forgotPasswordOtpSend, isRtl) return { title: t.login.throttle.title, description: t.login.throttle.otpSendMessage(formatted), } }, [isRtl, state.cooldowns.forgotPasswordOtpSend, t.login.throttle]) const expiryMessage = otpRemainingSeconds > 0 ? t.login.otpExpiresIn(formatCooldown(otpRemainingSeconds, isRtl)) : state.forgotPassword.otpExpiresAt ? t.login.otpExpired : null const continueToReset = async (code: string) => { if (code.length !== 5) { toast.error(t.login.toasts.enterOtp) return } setIsContinuing(true) setCode("forgotPassword", code) navigate("/auth/forgot-password/password") } const isBusy = isSendingOtp || isContinuing return (
{ event.preventDefault() void continueToReset(state.forgotPassword.code) }} className="grid gap-4" > setCode("forgotPassword", value)} onComplete={(value) => void continueToReset(value)} /> {expiryMessage ? (

{expiryMessage}

) : null}
) }