diff --git a/frontend/application/components/AuthModal.tsx b/frontend/application/components/AuthModal.tsx index 1b23f61..884e61f 100644 --- a/frontend/application/components/AuthModal.tsx +++ b/frontend/application/components/AuthModal.tsx @@ -53,6 +53,11 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { const [showPassword, setShowPassword] = useState(false); const [showNewPassword, setShowNewPassword] = useState(false); const otpInputRef = useRef(null); + const phoneNumberRef = useRef(phoneNumber); + + useEffect(() => { + phoneNumberRef.current = phoneNumber; + }, [phoneNumber]); useEffect(() => { if (view === "otp") { @@ -93,8 +98,8 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { const triggerVerifyOtp = useCallback( async (codeToVerify: string) => { - const cleanCode = toEnglishDigits(codeToVerify).trim(); - const cleanPhone = toEnglishDigits(phoneNumber).trim(); + const cleanCode = toEnglishDigits(codeToVerify).replace(/[^0-9]/g, "").slice(0, 5); + const cleanPhone = toEnglishDigits(phoneNumberRef.current).replace(/[^0-9]/g, ""); if (cleanCode.length !== 5 || !cleanPhone) return; setIsLoading(true); @@ -112,38 +117,74 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) { setIsLoading(false); } }, - [phoneNumber, fetchProfile, onClose], + [fetchProfile, onClose], ); // WebOTP API SMS Auto-read useEffect(() => { - if (view === "otp" && typeof window !== "undefined" && "OTPCredential" in window) { - const ac = new AbortController(); + if (view !== "otp") return; + + let isMounted = true; + const ac = new AbortController(); + + if (typeof window !== "undefined" && "OTPCredential" in window) { (navigator as unknown as { credentials: { get: (opts: unknown) => Promise<{ code?: string }> } }).credentials ?.get({ otp: { transport: ["sms"] }, signal: ac.signal, }) .then((otp) => { - if (otp?.code) { + if (!isMounted) return; + if (otp && typeof otp.code === "string") { const clean = toEnglishDigits(otp.code).replace(/[^0-9]/g, "").slice(0, 5); - setOtpCode(clean); - if (clean.length === 5) { - triggerVerifyOtp(clean); + if (clean) { + setOtpCode(clean); + if (otpInputRef.current) { + otpInputRef.current.value = clean; + } + if (clean.length === 5) { + triggerVerifyOtp(clean); + } } } }) .catch(() => {}); - - return () => { - ac.abort(); - }; } + + // Direct DOM input / change listener for mobile keyboard autofill compatibility + const inputEl = otpInputRef.current; + const handleDomAutofill = (e: Event) => { + const target = e.target as HTMLInputElement; + if (target && target.value) { + const clean = toEnglishDigits(target.value).replace(/[^0-9]/g, "").slice(0, 5); + setOtpCode(clean); + if (clean.length === 5) { + triggerVerifyOtp(clean); + } + } + }; + + if (inputEl) { + inputEl.addEventListener("input", handleDomAutofill); + inputEl.addEventListener("change", handleDomAutofill); + } + + return () => { + isMounted = false; + ac.abort(); + if (inputEl) { + inputEl.removeEventListener("input", handleDomAutofill); + inputEl.removeEventListener("change", handleDomAutofill); + } + }; }, [view, triggerVerifyOtp]); const handleOtpChange = (val: string) => { const clean = toEnglishDigits(val).replace(/[^0-9]/g, "").slice(0, 5); setOtpCode(clean); + if (otpInputRef.current) { + otpInputRef.current.value = clean; + } if (clean.length === 5) { triggerVerifyOtp(clean); } @@ -332,7 +373,7 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
- ورود امن به کانینو + ورود امن به کنینا