Enable Paste of OTP into OTP Input Field #204

Merged
vikas.nale merged 1 commits from Kartik_Bug#489 into Issue_Jun_2W 2025-06-12 05:31:10 +00:00
Showing only changes of commit bd76668731 - Show all commits

View File

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