Files
chat_room/web/src/app/verify/page.tsx
T
mobiniqow cba4bc30da chore: merge web repo history into monorepo at web/
git-subtree-dir: web
git-subtree-mainline: 75a0ba593d
git-subtree-split: 8f624e7416
2026-09-27 10:31:24 +03:30

146 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}