fix(auth): improve OTP autofill DOM binding, event listeners, and WebOTP compatibility
All checks were successful
Deploy Canina / deploy (push) Successful in 1m31s
All checks were successful
Deploy Canina / deploy (push) Successful in 1m31s
This commit is contained in:
parent
8d1b786a24
commit
2e217ebe3d
@ -53,6 +53,11 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showNewPassword, setShowNewPassword] = useState(false);
|
||||
const otpInputRef = useRef<HTMLInputElement>(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) {
|
||||
<div className="p-6 pb-0 flex justify-between items-center">
|
||||
<div className="flex items-center gap-2 text-canina-blue font-black text-sm">
|
||||
<ShieldCheck className="w-5 h-5" />
|
||||
<span>ورود امن به کانینو</span>
|
||||
<span>ورود امن به کنینا</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@ -734,13 +775,15 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
<Key className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-medical-gray-400 pointer-events-none" />
|
||||
<input
|
||||
ref={otpInputRef}
|
||||
id="otp-code-input"
|
||||
name="one-time-code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
maxLength={5}
|
||||
placeholder="کد ۵ رقمی"
|
||||
value={otpCode}
|
||||
onChange={(e) => handleOtpChange(e.target.value)}
|
||||
onInput={(e) => handleOtpChange((e.target as HTMLInputElement).value)}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-4 pr-12 pl-4 focus:ring-2 focus:ring-canina-blue/20 outline-none font-bold text-center text-2xl font-mono tracking-[0.5em]"
|
||||
dir="ltr"
|
||||
@ -849,11 +892,10 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
maxLength={5}
|
||||
required
|
||||
placeholder="کد ۵ رقمی"
|
||||
value={otpCode}
|
||||
onChange={(e) => setOtpCode(toEnglishDigits(e.target.value).replace(/[^0-9]/g, "").slice(0, 5))}
|
||||
onInput={(e) => setOtpCode(toEnglishDigits((e.target as HTMLInputElement).value).replace(/[^0-9]/g, "").slice(0, 5))}
|
||||
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl py-3 pr-11 pl-4 focus:ring-2 focus:ring-canina-blue/20 outline-none text-center font-mono font-bold text-xl tracking-[0.4em]"
|
||||
dir="ltr"
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user