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 SignupOtpPage() { 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.signup.mobile) { return } useEffect(() => { if (!state.signup.otpExpiresAt || getOtpRemainingSeconds(state.signup.otpExpiresAt) <= 0) { return } const timer = window.setInterval(() => { setNow(Date.now()) }, 1000) return () => window.clearInterval(timer) }, [state.signup.otpExpiresAt]) const sendSignupOtp = async () => { setIsSendingOtp(true) try { const response = await sendOtp(state.signup.mobile, "register") clearCooldown("signupOtpSend") setCode("signup", "") setOtpDelivery("signup", response.expires_in_seconds) setNow(Date.now()) toast.success(t.login.toasts.verifySent) } catch (error) { clearOtpDelivery("signup") if ( !handleThrottleError({ error, cooldownKey: "signupOtpSend", setCooldown, formatTime: (seconds) => formatCooldown(seconds, isRtl), throttleCopy: t.login.throttle, }) ) { toast.error(getApiErrorMessage(error, t.login.toasts.failedOtp)) } } finally { setIsSendingOtp(false) } } useEffect(() => { if (!state.signup.pendingOtpSend || autoSendStartedRef.current) { return } autoSendStartedRef.current = true void sendSignupOtp() }, [state.signup.pendingOtpSend]) const otpRemainingSeconds = state.signup.otpExpiresAt ? Math.max(0, Math.ceil((state.signup.otpExpiresAt - now) / 1000)) : 0 const alert = useMemo(() => { if (state.cooldowns.signupOtpSend <= 0) { return null } const formatted = formatCooldown(state.cooldowns.signupOtpSend, isRtl) return { title: t.login.throttle.title, description: t.login.throttle.otpSendMessage(formatted), } }, [isRtl, state.cooldowns.signupOtpSend, t.login.throttle]) const expiryMessage = otpRemainingSeconds > 0 ? t.login.otpExpiresIn(formatCooldown(otpRemainingSeconds, isRtl)) : state.signup.otpExpiresAt ? t.login.otpExpired : null const continueToPassword = async (code: string) => { if (code.length !== 5) { toast.error(t.login.toasts.enterOtp) return } setIsContinuing(true) setCode("signup", code) navigate("/auth/signup/password") } const isBusy = isSendingOtp || isContinuing return (
{ event.preventDefault() void continueToPassword(state.signup.code) }} className="grid gap-4" > setCode("signup", value)} onComplete={(value) => void continueToPassword(value)} /> {expiryMessage ? (

{expiryMessage}

) : null}
) }