import { useRef, useState } from 'react'; import { requestVerificationCode } from '@/lib/api/auth'; import { ImApiError } from '@/lib/api/client'; const PHONE_RE = /^\d{11}$/; interface VerificationCodeButtonProps { phone: string; scene: 'register' | 'reset_password'; onChallenge: (challengeId: string) => void; onError: (message: string) => void; } /** "获取验证码" button with resend countdown. Ported from the old app's VerificationButton. */ export function VerificationCodeButton({ phone, scene, onChallenge, onError }: VerificationCodeButtonProps) { const [seconds, setSeconds] = useState(0); const [busy, setBusy] = useState(false); const timerRef = useRef(undefined); const tick = (initial: number) => { window.clearInterval(timerRef.current); setSeconds(initial); timerRef.current = window.setInterval(() => { setSeconds((s) => { if (s <= 1) { window.clearInterval(timerRef.current); return 0; } return s - 1; }); }, 1000); }; const click = async () => { if (busy || seconds > 0 || !PHONE_RE.test(phone)) return; setBusy(true); try { const data = await requestVerificationCode(scene, phone); onChallenge(data.challenge_id); tick(data.resend_after_seconds || 60); } catch (err) { onError(err instanceof ImApiError ? err.message : '获取验证码失败'); // Rate-limited but a still-valid challenge already exists for this phone/scene -- reuse it. const details = err instanceof ImApiError ? (err.details as Record | undefined) : undefined; if (typeof details?.retry_after_seconds === 'number') tick(details.retry_after_seconds); if (typeof details?.challenge_id === 'string') onChallenge(details.challenge_id); } finally { setBusy(false); } }; return ( ); }