From def6524566106ac0bd3bee30435d63694cb6ff34 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sat, 7 Jun 2025 17:40:03 +0530 Subject: [PATCH] created new component for verify OTP --- src/pages/authentication/LoginWithOtp.jsx | 185 ++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/pages/authentication/LoginWithOtp.jsx diff --git a/src/pages/authentication/LoginWithOtp.jsx b/src/pages/authentication/LoginWithOtp.jsx new file mode 100644 index 00000000..b04cc7f8 --- /dev/null +++ b/src/pages/authentication/LoginWithOtp.jsx @@ -0,0 +1,185 @@ +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState, useRef, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import showToast from "../../services/toastService"; +import { AuthWrapper } from "./AuthWrapper"; +import { OTP_EXPIRY_SECONDS } from "../../utils/constants"; +import AuthRepository from "../../repositories/AuthRepository"; + +const otpSchema = z.object({ + otp1: z.string().min(1, "Required"), + otp2: z.string().min(1, "Required"), + otp3: z.string().min(1, "Required"), + otp4: z.string().min(1, "Required"), +}); + +const LoginWithOtp = () => { + const navigate = useNavigate(); + + const [ loading, setLoading ] = useState( false ); + const [ timeLeft, setTimeLeft ] = useState( 0 ); + + + const inputRefs = useRef([]); + + const { + register, + handleSubmit, + formState: { errors, isSubmitted }, + getValues, + } = useForm({ + resolver: zodResolver(otpSchema), + }); + + const onSubmit = async (data) => { + const finalOtp = data.otp1 + data.otp2 + data.otp3 + data.otp4; + const username = localStorage.getItem( "otpUsername" ); + console.log(username) + 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" ); + + } catch (err) { + 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}`; +}; + +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; + + 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 ( + +
+

Verify Your OTP

+

Please enter the 4-digit code sent to your email.

+ +
+
+ {[1, 2, 3, 4].map((num, idx) => { + const { ref, onChange, ...rest } = register(`otp${num}`); + + return ( + { + inputRefs.current[idx] = el; + ref(el); + }} + onChange={(e) => { + const val = e.target.value; + onChange(e); + if (/^\d$/.test(val) && idx < 3) { + inputRefs.current[idx + 1]?.focus(); + } + }} + onKeyDown={(e) => { + if ( + e.key === "Backspace" && + !getValues()[`otp${num}`] && + idx > 0 + ) { + inputRefs.current[idx - 1]?.focus(); + } + }} + style={{ width: "40px", height: "40px", fontSize: "15px" }} + {...rest} + /> + ); + })} +
+ + {isSubmitted && Object.values(errors).some((e) => e?.message) && ( +
+ Please fill all four digits. +
+ )} + + + + {timeLeft > 0 ? ( +

+ This OTP will expire in {formatTime(timeLeft)} +

+ ) : ( +
+

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

+ navigate('/auth/login')}>Try Again +
+ + )} +
+
+
+ ); +}; + +export default LoginWithOtp;