From 3f7242742e625f71bcf03f60ea3459dccb61352a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D9=87=D8=B1=D8=B2=D8=A7=D8=AF=20=D9=82=D8=AF=D8=B1?= =?UTF-8?q?=D8=AA=DB=8C?= Date: Mon, 28 Jul 2025 13:02:24 +0330 Subject: [PATCH] feat: otp resend timer and logic added --- public/locales/en/authentication.json | 5 +- public/locales/fa/authentication.json | 5 +- .../components/AuthenticationCard.tsx | 18 +++ .../components/CompleteSignUp.tsx | 7 +- .../components/LoginRegiserForm.tsx | 14 +- .../components/OtpVerifyForm.tsx | 142 +++++++++++++----- .../authentication/components/SmsOtpForm.tsx | 44 ------ .../routes/AuthenticationPage.tsx | 12 +- 8 files changed, 143 insertions(+), 104 deletions(-) create mode 100644 src/features/authentication/components/AuthenticationCard.tsx delete mode 100644 src/features/authentication/components/SmsOtpForm.tsx diff --git a/public/locales/en/authentication.json b/public/locales/en/authentication.json index ef0caca..7b70125 100644 --- a/public/locales/en/authentication.json +++ b/public/locales/en/authentication.json @@ -16,7 +16,10 @@ "thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "There is no account with this email address. A 4-digit verification code has been sent to this email address to create a new account.", "theVerificationCodeIsIncorrect": "The verification code is incorrect.", "youHaveSuccessfullyLoggedIn": "You have successfully logged in", - "youHaveSuccessfullySignedIn": "You have successfully signed in" + "youHaveSuccessfullySignedIn": "You have successfully signed in", + "resendCodeIn": "Resend code in", + "moreMinute": "minute", + "resendCode": "Resend code" }, "completeSignUp": { "completeSignUp": "Complete Sign Up", diff --git a/public/locales/fa/authentication.json b/public/locales/fa/authentication.json index ad86fc7..224d34d 100644 --- a/public/locales/fa/authentication.json +++ b/public/locales/fa/authentication.json @@ -19,7 +19,10 @@ "thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "حساب کاربری با این ایمیل وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این ایمیل ارسال گردید.", "theVerificationCodeIsIncorrect": "کد تایید اشتباه می باشد", "youHaveSuccessfullyLoggedIn": "با موفقیت وارد شدید", - "youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد" + "youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد", + "resendCodeIn": "ارسال مجدد کد تا", + "moreMinute": "دقیقه دیگر", + "resendCode": "ارسال مجدد" }, "completeSignUp": { "completeSignUp": "تکمیل ثبت نام", diff --git a/src/features/authentication/components/AuthenticationCard.tsx b/src/features/authentication/components/AuthenticationCard.tsx new file mode 100644 index 0000000..9e82632 --- /dev/null +++ b/src/features/authentication/components/AuthenticationCard.tsx @@ -0,0 +1,18 @@ +import { Paper } from '@mui/material'; +import React, { type PropsWithChildren } from 'react'; + +// Beacuse in the otp verify there is a element outside of the authentication card +export const AuthenticationCard = ({ children }: PropsWithChildren) => { + return ( + + {children} + + ); +}; diff --git a/src/features/authentication/components/CompleteSignUp.tsx b/src/features/authentication/components/CompleteSignUp.tsx index ccf8eb6..098396b 100644 --- a/src/features/authentication/components/CompleteSignUp.tsx +++ b/src/features/authentication/components/CompleteSignUp.tsx @@ -1,8 +1,9 @@ -import { Box, Button, TextField, Typography } from '@mui/material'; +import { Box, Button, Paper, TextField, Typography } from '@mui/material'; import parsePhoneNumberFromString from 'libphonenumber-js'; import React, { useRef, useState, type Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; import { CountryCodeSelector } from './CountryCodeSelector'; +import { AuthenticationCard } from './AuthenticationCard'; export interface CompleteSignUpProps { email: string; @@ -59,7 +60,7 @@ export const CompleteSignUp = ({ }; return ( - + {t('completeSignUp.completeSignUp')} @@ -101,6 +102,6 @@ export const CompleteSignUp = ({ - + ); }; diff --git a/src/features/authentication/components/LoginRegiserForm.tsx b/src/features/authentication/components/LoginRegiserForm.tsx index edf9296..a851d14 100644 --- a/src/features/authentication/components/LoginRegiserForm.tsx +++ b/src/features/authentication/components/LoginRegiserForm.tsx @@ -1,4 +1,11 @@ -import { Box, Button, Stack, TextField, Typography } from '@mui/material'; +import { + Box, + Button, + Paper, + Stack, + TextField, + Typography, +} from '@mui/material'; import { useRef, useState, type Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; import { CountryCodeSelector } from './CountryCodeSelector'; @@ -7,6 +14,7 @@ import { isNumeric } from '@/utils/regexes/isNumeric'; import type { AuthMode, AuthType } from '../types/auth-types'; import { isEmail } from '@/utils/regexes/isEmail'; import parsePhoneNumberFromString from 'libphonenumber-js'; +import { AuthenticationCard } from './AuthenticationCard'; export interface LoginRegisterFormProps { loginRegisterValue: string; @@ -95,7 +103,7 @@ export function LoginRegisterForm({ const showAdornment = authType === 'phone' && loginRegisterValue.length > 0; return ( - + {t('loginForm.title')} @@ -136,6 +144,6 @@ export function LoginRegisterForm({ {t('loginForm.loginWithGoogle')} - + ); } diff --git a/src/features/authentication/components/OtpVerifyForm.tsx b/src/features/authentication/components/OtpVerifyForm.tsx index 0748f42..f958bfc 100644 --- a/src/features/authentication/components/OtpVerifyForm.tsx +++ b/src/features/authentication/components/OtpVerifyForm.tsx @@ -1,10 +1,11 @@ import { useTranslation } from 'react-i18next'; -import { Alert, Box, Button, Snackbar, Typography } from '@mui/material'; +import { Alert, Box, Button, Snackbar, Stack, Typography } from '@mui/material'; import { Edit2 } from 'iconsax-reactjs'; import DigitInput from '@/components/components/DigitsInput'; import type { AuthMode, AuthType } from '../types/auth-types'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Toast } from '@/components/Toast'; +import { AuthenticationCard } from './AuthenticationCard'; interface OtpVerifyFormProps { value: string; @@ -28,6 +29,42 @@ export function OtpVerifyForm({ useState(false); const [verifyAlertOpen, setVerifyAlertOpen] = useState(false); const { t } = useTranslation('authentication'); + const [resendTimer, setResendTimer] = useState(120); + const [canResend, setCanResend] = useState(false); + const [resendLoading, setResendLoading] = useState(false); + + useEffect(() => { + let interval: NodeJS.Timeout; + if (resendTimer > 0) { + interval = setInterval(() => { + setResendTimer((prev) => prev - 1); + }, 1000); + } else { + setCanResend(true); + } + + return () => clearInterval(interval); + }, [resendTimer]); + + const handleResendOTPCode = () => { + setResendLoading(true); + + // TODO: Call API here instead of settimeout + + setTimeout(() => { + console.log('resended'); + + setResendTimer(120); + setCanResend(false); + setResendLoading(false); + }, 1000); + }; + + const formatTime = (seconds: number) => { + const min = Math.floor(seconds / 60); + const sec = seconds % 60; + return `${min}:${sec.toString().padStart(2, '0')}`; + }; const handleDigitInputChange = (value: string[]) => { const formattedValue = value.filter((char) => char !== '').join(''); @@ -87,53 +124,76 @@ export function OtpVerifyForm({ }; return ( - - setVerifyAlertOpen(false)} - color={verifyStatus === 'failed' ? 'error' : 'success'} - > - {verifyAlertMessage()} - + + + setVerifyAlertOpen(false)} + color={verifyStatus === 'failed' ? 'error' : 'success'} + > + {verifyAlertMessage()} + - + {t('verify.verify')} + + + + + + {otpMessage()} + + + handleDigitInputChange(value as string[])} + /> + + + + + - {t('verify.verify')} + {t('verify.resendCodeIn')} - - - - {otpMessage()} - - - handleDigitInputChange(value as string[])} - /> - - - + + ); } diff --git a/src/features/authentication/components/SmsOtpForm.tsx b/src/features/authentication/components/SmsOtpForm.tsx deleted file mode 100644 index ce03b0a..0000000 --- a/src/features/authentication/components/SmsOtpForm.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { Box, Button, Typography } from '@mui/material'; -import { Edit2 } from 'iconsax-reactjs'; -import DigitInput from '@/components/components/DigitsInput'; - -interface SmsOtpProps { - value: string; - type: 'phone' | 'email'; -} - -export function SmsOtpForm({ value, type }: SmsOtpProps) { - const { t } = useTranslation('authentication'); - - return ( - - - اعتبارسنجی - - - - - کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید. - - console.log(value)} /> - - - ); -} diff --git a/src/features/authentication/routes/AuthenticationPage.tsx b/src/features/authentication/routes/AuthenticationPage.tsx index 158d0a8..b23f6c5 100644 --- a/src/features/authentication/routes/AuthenticationPage.tsx +++ b/src/features/authentication/routes/AuthenticationPage.tsx @@ -1,7 +1,6 @@ import { FlexBox } from '@/components/components/common/FlexBox'; import Logo from '@/components/Logo'; import { Paper } from '@mui/material'; -import { SmsOtpForm } from '../components/SmsOtpForm'; import { useState } from 'react'; import { AuthenticationContainer } from '../components/AuthenticationContainer'; @@ -17,16 +16,7 @@ export function AuthenticationPage() { }} > - - - + ); }