cba4bc30da
git-subtree-dir: web git-subtree-mainline:75a0ba593dgit-subtree-split:8f624e7416
146 lines
4.6 KiB
TypeScript
146 lines
4.6 KiB
TypeScript
"use client";
|
||
|
||
import { Suspense, useEffect, useState } from "react";
|
||
import { useRouter, useSearchParams } from "next/navigation";
|
||
import { ChevronRight } from "lucide-react";
|
||
import { Button } from "@/components/ui/Button";
|
||
import { OtpInput } from "@/components/ui/OtpInput";
|
||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||
import { requestOtp } from "@/lib/api";
|
||
import { formatApiError } from "@/lib/hooks";
|
||
import { useAuth } from "@/lib/auth-context";
|
||
import { stepByOrder } from "@/lib/onboarding-steps";
|
||
|
||
const RESEND_SECONDS = 60;
|
||
|
||
function formatCountdown(seconds: number) {
|
||
const m = Math.floor(seconds / 60);
|
||
const s = seconds % 60;
|
||
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
||
}
|
||
|
||
function VerifyForm() {
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
const phone = searchParams.get("phone") ?? "";
|
||
const { login } = useAuth();
|
||
|
||
const [otp, setOtp] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [countdown, setCountdown] = useState(RESEND_SECONDS);
|
||
const [resending, setResending] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (countdown <= 0) return;
|
||
const t = setTimeout(() => setCountdown((c) => c - 1), 1000);
|
||
return () => clearTimeout(t);
|
||
}, [countdown]);
|
||
|
||
useEffect(() => {
|
||
if (!phone) router.replace("/login");
|
||
}, [phone, router]);
|
||
|
||
async function handleVerify(code: string) {
|
||
if (code.length !== 4) return;
|
||
setLoading(true);
|
||
setError(null);
|
||
try {
|
||
const user = await login(phone, code);
|
||
if (user.is_onboarded) {
|
||
router.push("/home");
|
||
} else {
|
||
const stepSlug = stepByOrder(user.current_step)?.slug ?? "basic-info";
|
||
router.push(`/onboarding/${stepSlug}`);
|
||
}
|
||
} catch (err) {
|
||
setError(formatApiError(err, "کد وارد شده اشتباهه"));
|
||
setOtp("");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
async function handleResend() {
|
||
setResending(true);
|
||
setError(null);
|
||
try {
|
||
await requestOtp(phone);
|
||
setCountdown(RESEND_SECONDS);
|
||
} catch (err) {
|
||
setError(formatApiError(err));
|
||
} finally {
|
||
setResending(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="flex-1 flex flex-col min-h-dvh px-6 pt-[calc(env(safe-area-inset-top)+16px)] pb-[calc(env(safe-area-inset-bottom)+32px)]">
|
||
<button
|
||
onClick={() => router.push("/login")}
|
||
className="w-9 h-9 rounded-full flex items-center justify-center hover:bg-(--color-bg-secondary) self-start"
|
||
aria-label="بازگشت"
|
||
>
|
||
<ChevronRight size={20} className="text-(--color-content-secondary)" />
|
||
</button>
|
||
|
||
<div className="flex flex-col items-center text-center gap-2 pt-6">
|
||
<span
|
||
dir="ltr"
|
||
className={`font-[var(--font-display)] font-extrabold text-[44px] tabular-nums tracking-wide ${
|
||
countdown > 0 ? "text-(--color-brand-500)" : "text-(--color-content-disabled)"
|
||
}`}
|
||
>
|
||
{formatCountdown(countdown)}
|
||
</span>
|
||
<h1 className="font-[var(--font-display)] font-extrabold text-[20px] pt-2">
|
||
کد تایید رو وارد کن
|
||
</h1>
|
||
<p className="text-[14px] text-(--color-content-secondary)">
|
||
کد ۴ رقمی ارسالشده به{" "}
|
||
<span dir="ltr" className="font-bold text-(--color-content-primary)">
|
||
{phone}
|
||
</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-6 pt-10">
|
||
<OtpInput value={otp} onChange={setOtp} onComplete={handleVerify} disabled={loading} />
|
||
<ErrorBanner message={error} />
|
||
<Button
|
||
size="lg"
|
||
isBlock
|
||
isLoading={loading}
|
||
disabled={otp.length !== 4}
|
||
onClick={() => handleVerify(otp)}
|
||
>
|
||
تایید کد
|
||
</Button>
|
||
|
||
<div className="flex items-center justify-center gap-1.5 text-[13px]">
|
||
<span className="text-(--color-content-tertiary)">کد نیومد؟</span>
|
||
{countdown > 0 ? (
|
||
<span className="font-bold text-(--color-content-secondary)">صبر کن...</span>
|
||
) : (
|
||
<button
|
||
onClick={handleResend}
|
||
disabled={resending}
|
||
className="font-bold text-(--color-brand-600) hover:text-(--color-brand-700) disabled:opacity-50"
|
||
>
|
||
دوباره بفرست
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function VerifyPage() {
|
||
return (
|
||
<Suspense>
|
||
<VerifyForm />
|
||
</Suspense>
|
||
);
|
||
}
|