From 8cb22ef9fdee27ac8f46cddb4b1be69357985c33 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 6 Jun 2026 11:19:02 +0330 Subject: [PATCH] resend --- .../auth/components/StepEnterNumber.tsx | 30 ++++++- src/features/auth/components/StepEnterOtp.tsx | 14 +++- src/features/auth/components/StepOtp.tsx | 84 ++++++++++++++----- src/locales/en/auth.json | 4 +- src/locales/fa/auth.json | 4 +- 5 files changed, 106 insertions(+), 30 deletions(-) diff --git a/src/features/auth/components/StepEnterNumber.tsx b/src/features/auth/components/StepEnterNumber.tsx index 2e8e429..a11f016 100644 --- a/src/features/auth/components/StepEnterNumber.tsx +++ b/src/features/auth/components/StepEnterNumber.tsx @@ -15,6 +15,7 @@ import { useParams } from 'next/navigation' import { toast } from '@/components/Toast' import StepOtp from './StepOtp' import { extractErrorMessage } from '@/lib/func' +import { useCountdown } from '@/hooks/useCountdown' type StepEnterNumberProps = { pending?: boolean @@ -29,6 +30,10 @@ function StepEnterNumber(_props: StepEnterNumberProps = {}) { const { name } = useParams() const [step, setStep] = useState(AUTH_STEP.ENTER_NUMBER) const { mutate: mutateOtpRequest, isPending } = useOtpRequest() + const { timerRunning, secondsLeft, restart } = useCountdown( + step === AUTH_STEP.ENTER_OTP, + 120 + ) const formik = useFormik({ @@ -56,6 +61,22 @@ function StepEnterNumber(_props: StepEnterNumberProps = {}) { } }) + const handleResendOtp = () => { + if (timerRunning || !name) return + + mutateOtpRequest( + { phone: formik.values.phone, slug: name as string }, + { + onSuccess: () => { + toast(t('otp_sent'), 'success') + restart() + }, + onError: (error) => { + toast(extractErrorMessage(error), 'error') + }, + } + ) + } return ( <> @@ -100,7 +121,14 @@ function StepEnterNumber(_props: StepEnterNumberProps = {}) { /> )} {step === AUTH_STEP.ENTER_OTP && ( - + )} { diff --git a/src/features/auth/components/StepEnterOtp.tsx b/src/features/auth/components/StepEnterOtp.tsx index 42253e7..d8ad5af 100644 --- a/src/features/auth/components/StepEnterOtp.tsx +++ b/src/features/auth/components/StepEnterOtp.tsx @@ -22,6 +22,15 @@ type Props = { pending?: boolean | undefined } & Omit, 'id'> +const formatCountdown = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + if (mins > 0) { + return `${mins}:${String(secs).padStart(2, '0')}` + } + return String(seconds) +} + function StepEnterOtp ({ onChange, onClick, @@ -87,10 +96,7 @@ function StepEnterOtp ({
{timerRunning ? (
- {t('OTP.TimerRunning').replace( - '{seconds}', - String(secondsLeft) - )} + {t('OTP.TimerRunning', { time: formatCountdown(secondsLeft) })}
) : (
diff --git a/src/features/auth/components/StepOtp.tsx b/src/features/auth/components/StepOtp.tsx index dd63863..7f5f592 100644 --- a/src/features/auth/components/StepOtp.tsx +++ b/src/features/auth/components/StepOtp.tsx @@ -12,14 +12,28 @@ import { useReceiptStore } from '@/zustand/receiptStore' import { useBulkCart } from '@/app/[name]/(Main)/cart/hooks/useCartData' import { setRefreshToken, setToken } from '@/lib/api/func' import { useSearchParams } from 'next/navigation' - +import { AUTH_PAGE_ELEMENT } from '@/enums' +import clsx from 'clsx' type Props = { - phone: string, + phone: string slug: string + timerRunning: boolean + secondsLeft: number + onResend: () => void + isResendPending?: boolean } -const StepOtp = ({ phone, slug }: Props) => { +const formatCountdown = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + if (mins > 0) { + return `${mins}:${String(secs).padStart(2, '0')}` + } + return String(seconds) +} + +const StepOtp = ({ phone, slug, timerRunning, secondsLeft, onResend, isResendPending }: Props) => { const searchParams = useSearchParams() const redirectUrl = searchParams.get('redirect') const { t } = useTranslation('auth') @@ -89,23 +103,51 @@ const StepOtp = ({ phone, slug }: Props) => { } }) return ( -
- formik.setFieldValue('otp', otp)} - renderInput={(props) => - - } - /> + <> +
+ formik.setFieldValue('otp', otp)} + renderInput={(props) => + + } + /> +
+ +
+ {timerRunning ? ( +
+ {t('OTP.TimerRunning', { time: formatCountdown(secondsLeft) })} +
+ ) : ( +
+ {t('OTP.TimerOut')} + +
+ )} +
-
+ ) } diff --git a/src/locales/en/auth.json b/src/locales/en/auth.json index 95edf97..bb4ee26 100644 --- a/src/locales/en/auth.json +++ b/src/locales/en/auth.json @@ -9,9 +9,9 @@ "Heading": "Enter Activation Code", "Description": "Please enter the 6-digit code sent to the number {{phoneNumber}}.", "Label": "", - "TimerRunning": "{seconds} seconds left to receive the code", + "TimerRunning": "Resend available in {{time}}", "TimerOut": "Didn’t receive the code?", - "TimerReset": "Try again", + "TimerReset": "Resend", "ButtonSubmit": "Next" }, "NewPassword": { diff --git a/src/locales/fa/auth.json b/src/locales/fa/auth.json index 03b5364..f3ee411 100644 --- a/src/locales/fa/auth.json +++ b/src/locales/fa/auth.json @@ -9,9 +9,9 @@ "Heading": "کد فعالسازی را وارد کنید", "Description": "کد ۵ رقمی ارسال شده به شماره {{phoneNumber}} را وارد نمایید.", "Label": "", - "TimerRunning": "{seconds} ثانیه دیگر تا دریافت کد", + "TimerRunning": "ارسال مجدد تا {{time}} دیگر", "TimerOut": "کد را دریافت نکردید؟", - "TimerReset": "دوباره امتحان کنید", + "TimerReset": "ارسال مجدد", "ButtonSubmit": "بعدی" }, "NewPassword": {