From 3b92349cce76b5411d6dbb77fcc78d90e56a1f3f Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Fri, 11 Jul 2025 11:24:29 +0530 Subject: [PATCH] Implementing a paste functionality in OTP login where the user can directly paste the OTP. --- src/pages/authentication/LoginWithOtp.jsx | 157 +++++++++++++--------- 1 file changed, 95 insertions(+), 62 deletions(-) diff --git a/src/pages/authentication/LoginWithOtp.jsx b/src/pages/authentication/LoginWithOtp.jsx index e0328b1b..2157b4e1 100644 --- a/src/pages/authentication/LoginWithOtp.jsx +++ b/src/pages/authentication/LoginWithOtp.jsx @@ -18,9 +18,9 @@ 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([]); @@ -29,68 +29,97 @@ const LoginWithOtp = () => { handleSubmit, formState: { errors, isSubmitted }, getValues, + setValue, + trigger, } = 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" ); - + 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"); + } catch (err) { - showToast( "Invalid or expired OTP.", "error" ); - - setLoading(false); + showToast("Invalid or expired OTP.", "error"); + + 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}`; -}; + 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(); + // Time Logic for OTP expiry + useEffect(() => { + const otpSentTime = localStorage.getItem("otpSentTime"); + const now = Date.now(); - 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; + 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); + } + }, []); - const timer = setInterval(() => { - setTimeLeft((prev) => { - if (prev <= 1) { - clearInterval(timer); - localStorage.removeItem( "otpSentTime" ); - localStorage.removeItem("otpUsername"); - return 0; + + 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]); + + // Handle Paste Event + const handlePaste = (e) => { + e.preventDefault(); + + const pastedData = e.clipboardData.getData("text/plain").trim(); + if (pastedData.match(/^\d{4}$/)) { + for (let i = 0; i < pastedData.length; i++) { + setValue(`otp${i + 1}`, pastedData[i], { shouldValidate: true }); + + if (inputRefs.current[i + 1]) { + inputRefs.current[i + 1].focus(); + } } - return prev - 1; - }); - }, 1000); + trigger(["otp1", "otp2", "otp3", "otp4"]); + } else { + showToast("Invalid OTP format pasted. Please enter 4 digits") - return () => clearInterval(timer); -}, [timeLeft]); + for (let i = 0; i < 4; i++) { + setValue(`otp${i + 1}`, "") + + } + } + } return ( @@ -109,9 +138,8 @@ useEffect(() => { key={num} type="text" maxLength={1} - className={`form-control text-center ${ - errors[`otp${num}`] ? "is-invalid" : "" - }`} + className={`form-control text-center ${errors[`otp${num}`] ? "is-invalid" : "" + }`} ref={(el) => { inputRefs.current[idx] = el; ref(el); @@ -121,6 +149,9 @@ useEffect(() => { onChange(e); if (/^\d$/.test(val) && idx < 3) { inputRefs.current[idx + 1]?.focus(); + + } else if (val === "" && idx > 0) { + inputRefs.current[idx - 1]?.focus(); } }} onKeyDown={(e) => { @@ -132,6 +163,8 @@ useEffect(() => { inputRefs.current[idx - 1]?.focus(); } }} + + onPaste={idx === 0 ? handlePaste : undefined} style={{ width: "40px", height: "40px", fontSize: "15px" }} {...rest} /> @@ -163,17 +196,17 @@ useEffect(() => { > This OTP will expire in {formatTime(timeLeft)}

- ) : ( -
-

- OTP has expired. Please request a new one. -

- navigate('/auth/login')}>Try Again -
- + ) : ( +
+

+ OTP has expired. Please request a new one. +

+ navigate('/auth/login')}>Try Again +
+ )}