Enable Paste of OTP into OTP Input Field

This commit is contained in:
Umesh Desai 2025-06-11 18:25:06 +05:30
parent f735509e31
commit bd76668731

View File

@ -17,11 +17,8 @@ const otpSchema = z.object({
const LoginWithOtp = () => {
const navigate = useNavigate();
const [ loading, setLoading ] = useState( false );
const [ timeLeft, setTimeLeft ] = useState( 0 );
const [loading, setLoading] = useState(false);
const [timeLeft, setTimeLeft] = useState(0);
const inputRefs = useRef([]);
const {
@ -29,69 +26,68 @@ const LoginWithOtp = () => {
handleSubmit,
formState: { errors, isSubmitted },
getValues,
setValue
} = useForm({
resolver: zodResolver(otpSchema),
});
const onSubmit = async (data) => {
const finalOtp = data.otp1 + data.otp2 + data.otp3 + data.otp4;
const username = localStorage.getItem( "otpUsername" );
const username = localStorage.getItem("otpUsername");
setLoading(true);
try {
let requestedData = {
email: username,
otp:finalOtp
}
const response = await AuthRepository.verifyOTP( requestedData )
localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken);
setLoading( false );
localStorage.removeItem( "otpUsername" );
localStorage.removeItem( "otpSentTime" );
navigate( "/dashboard" );
const requestedData = {
email: username,
otp: finalOtp,
};
const response = await AuthRepository.verifyOTP(requestedData);
localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken);
localStorage.removeItem("otpUsername");
localStorage.removeItem("otpSentTime");
navigate("/dashboard");
} catch (err) {
showToast( "Invalid or expired OTP.", "error" );
setLoading(false);
showToast("Invalid or expired OTP.", "error");
} finally {
setLoading(false);
}
};
const formatTime = (seconds) => {
const min = Math.floor(seconds / 60).toString().padStart(2, "0");
const sec = (seconds % 60).toString().padStart(2, "0");
return `${min}:${sec}`;
};
useEffect(() => {
const otpSentTime = localStorage.getItem("otpSentTime");
const now = Date.now();
const formatTime = (seconds) => {
const min = Math.floor(seconds / 60).toString().padStart(2, "0");
const sec = (seconds % 60).toString().padStart(2, "0");
return `${min}:${sec}`;
};
if (otpSentTime) {
const elapsed = Math.floor((now - Number(otpSentTime)) / 1000); // in seconds
const remaining = Math.max(OTP_EXPIRY_SECONDS - elapsed, 0); // prevent negatives
setTimeLeft(remaining);
}
}, []);
useEffect(() => {
if (timeLeft <= 0) return;
useEffect(() => {
const otpSentTime = localStorage.getItem("otpSentTime");
const now = Date.now();
const timer = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
clearInterval(timer);
localStorage.removeItem( "otpSentTime" );
localStorage.removeItem("otpUsername");
return 0;
}
return prev - 1;
});
}, 1000);
if (otpSentTime) {
const elapsed = Math.floor((now - Number(otpSentTime)) / 1000);
const remaining = Math.max(OTP_EXPIRY_SECONDS - elapsed, 0);
setTimeLeft(remaining);
}
}, []);
return () => clearInterval(timer);
}, [timeLeft]);
useEffect(() => {
if (timeLeft <= 0) return;
const timer = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
clearInterval(timer);
localStorage.removeItem("otpSentTime");
localStorage.removeItem("otpUsername");
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [timeLeft]);
return (
<AuthWrapper>
@ -100,7 +96,7 @@ useEffect(() => {
<p className="mb-4">Please enter the 4-digit code sent to your email.</p>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="d-flex justify-content-center gap-6 mb-3">
<div className="d-flex justify-content-center gap-3 mb-3">
{[1, 2, 3, 4].map((num, idx) => {
const { ref, onChange, ...rest } = register(`otp${num}`);
@ -132,6 +128,24 @@ useEffect(() => {
inputRefs.current[idx - 1]?.focus();
}
}}
onPaste={
idx === 0
? (e) => {
const pasteData = e.clipboardData.getData("Text").trim();
if (/^\d{4}$/.test(pasteData)) {
e.preventDefault();
pasteData.split("").forEach((char, i) => {
const ref = inputRefs.current[i];
if (ref) {
ref.value = char;
setValue(`otp${i + 1}`, char);
}
});
inputRefs.current[3]?.focus();
}
}
: undefined
}
style={{ width: "40px", height: "40px", fontSize: "15px" }}
{...rest}
/>
@ -140,10 +154,7 @@ useEffect(() => {
</div>
{isSubmitted && Object.values(errors).some((e) => e?.message) && (
<div
className="text-danger text-center mb-3"
style={{ fontSize: "12px" }}
>
<div className="text-danger text-center mb-3" style={{ fontSize: "12px" }}>
Please fill all four digits.
</div>
)}
@ -157,23 +168,21 @@ useEffect(() => {
</button>
{timeLeft > 0 ? (
<p
className="text-center text-muted mt-2"
style={{ fontSize: "14px" }}
>
<p className="text-center text-muted mt-2" style={{ fontSize: "14px" }}>
This OTP will expire in <strong>{formatTime(timeLeft)}</strong>
</p>
) : (
<div>
<p
className="text-center text-danger mt-2 text small-text m-0"
>
OTP has expired. Please request a new one.
</p>
<a className="text-primary cursor-pointer" onClick={()=>navigate('/auth/login')}>Try Again</a>
</div>
) : (
<div>
<p className="text-center text-danger mt-2 small-text m-0">
OTP has expired. Please request a new one.
</p>
<a
className="text-primary cursor-pointer"
onClick={() => navigate("/auth/login")}
>
Try Again
</a>
</div>
)}
</form>
</div>