"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { RefreshCw } from "lucide-react"; import { http, toUserError } from "@/lib/api"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export type CaptchaChallenge = { challengeId: string; imageBase64: string; expiresIn?: number; }; const FALLBACK_SECONDS = 120; export function CaptchaField({ id, value, onChange, onChallenge, onError, }: { id: string; value: string; onChange: (value: string) => void; onChallenge: (challenge: CaptchaChallenge | null) => void; onError?: (message: string | null) => void; }) { const [challenge, setChallenge] = useState(null); const [loading, setLoading] = useState(true); const [left, setLeft] = useState(FALLBACK_SECONDS); const gen = useRef(0); const rotating = useRef(false); const onChallengeRef = useRef(onChallenge); const onChangeRef = useRef(onChange); const onErrorRef = useRef(onError); onChallengeRef.current = onChallenge; onChangeRef.current = onChange; onErrorRef.current = onError; const load = useCallback(async (silent: boolean) => { if (silent && rotating.current) return; if (silent) rotating.current = true; const ticket = ++gen.current; if (!silent) setLoading(true); try { const data = await http.get("/api/auth/captcha"); if (ticket !== gen.current) return; setChallenge(data); onChallengeRef.current(data); setLeft(Math.max(15, Math.round(data.expiresIn || FALLBACK_SECONDS))); onChangeRef.current(""); onErrorRef.current?.(null); } catch (err) { if (ticket !== gen.current) return; if (!silent) { setChallenge(null); onChallengeRef.current(null); } onErrorRef.current?.(toUserError(err, "بارگذاری کد امنیتی ممکن نشد")); } finally { rotating.current = false; if (ticket === gen.current && !silent) setLoading(false); } }, []); useEffect(() => { void load(false); }, [load]); useEffect(() => { if (!challenge) return; const timer = window.setInterval(() => { setLeft((n) => { if (n <= 1) { void load(true); return n; } return n - 1; }); }, 1000); return () => window.clearInterval(timer); }, [challenge, load]); const minutes = String(Math.floor(left / 60)).padStart(1, "0"); const seconds = String(left % 60).padStart(2, "0"); return (
{challenge?.imageBase64 ? ( // eslint-disable-next-line @next/next/no-img-element کد امنیتی ) : ( {loading ? "…" : "—"} )} {challenge && ( )}
onChange(e.target.value.toUpperCase())} required dir="ltr" className="h-14 text-center font-mono tracking-widest uppercase placeholder:text-center" placeholder="کد تصویر" maxLength={6} />
); }