From aa18e3fbf8020c956a532f80fcdb960b6945a6ca Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Tue, 13 Oct 2026 22:20:05 -0700 Subject: [PATCH 1/2] fix: allow iOS one-time-code AutoFill in OTP input Mark the digit fields with autocomplete="one-time-code" so iOS offers the code from Mail, and accept a full code inserted into a single field instead of rejecting it via maxLength=1. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/components/auth/OtpInput.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/auth/OtpInput.tsx b/src/components/auth/OtpInput.tsx index 756b4a6..1bd6ef3 100644 --- a/src/components/auth/OtpInput.tsx +++ b/src/components/auth/OtpInput.tsx @@ -72,20 +72,25 @@ export function OtpInput({ onComplete, isLoading = false, error, onResend }: Otp }, [digits]); const handleChange = (index: number, e: ChangeEvent) => { - const value = e.target.value; + const value = e.target.value.replace(/\D/g, ""); - // Only allow single digit - if (value.length > 1) return; + // iOS/Android one-time-code AutoFill inserts the whole code into one field + if (value.length >= OTP_LENGTH) { + setDigits(value.slice(-OTP_LENGTH).split("")); + inputRefs.current[OTP_LENGTH - 1]?.focus(); + return; + } - // Only allow numbers - if (value && !/^\d$/.test(value)) return; + // Typing into a filled field: keep the newest digit + if (e.target.value && !value) return; + const digit = value.slice(-1); const newDigits = [...digits]; - newDigits[index] = value; + newDigits[index] = digit; setDigits(newDigits); // Auto-advance to next input - if (value && index < OTP_LENGTH - 1) { + if (digit && index < OTP_LENGTH - 1) { inputRefs.current[index + 1]?.focus(); } }; @@ -151,7 +156,7 @@ export function OtpInput({ onComplete, isLoading = false, error, onResend }: Otp }} type="text" inputMode="numeric" - maxLength={1} + autoComplete="one-time-code" value={digit} onChange={(e) => handleChange(index, e)} onKeyDown={(e) => handleKeyDown(index, e)} From e04aa8ca67eff307fc576854a80bab19640b798e Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Tue, 13 Oct 2026 22:30:59 -0700 Subject: [PATCH 2/2] fix: select OTP digit on focus so input replaces it Co-Authored-By: Claude Opus 5.5 (1M context) --- src/components/auth/OtpInput.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/auth/OtpInput.tsx b/src/components/auth/OtpInput.tsx index 1bd6ef3..3399343 100644 --- a/src/components/auth/OtpInput.tsx +++ b/src/components/auth/OtpInput.tsx @@ -160,6 +160,8 @@ export function OtpInput({ onComplete, isLoading = false, error, onResend }: Otp value={digit} onChange={(e) => handleChange(index, e)} onKeyDown={(e) => handleKeyDown(index, e)} + // Select existing digit so typing or AutoFill replaces it regardless of caret position + onFocus={(e) => e.target.select()} onPaste={handlePaste} disabled={isLoading} className="w-12 h-14 text-center text-2xl font-semibold text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-amber-500 disabled:opacity-50 disabled:cursor-not-allowed"