This commit is contained in:
hamid zarghami
2026-06-06 11:19:02 +03:30
parent 24ff9a4100
commit 8cb22ef9fd
5 changed files with 106 additions and 30 deletions
@@ -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<LoginOTPRequestType>({
@@ -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 && (
<StepOtp phone={formik.values.phone} slug={formik.values.slug} />
<StepOtp
phone={formik.values.phone}
slug={(name as string) || formik.values.slug}
timerRunning={timerRunning}
secondsLeft={secondsLeft}
onResend={handleResendOtp}
isResendPending={isPending}
/>
)}
</div>
{
+10 -4
View File
@@ -22,6 +22,15 @@ type Props = {
pending?: boolean | undefined
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, '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 ({
<div>
{timerRunning ? (
<div>
{t('OTP.TimerRunning').replace(
'{seconds}',
String(secondsLeft)
)}
{t('OTP.TimerRunning', { time: formatCountdown(secondsLeft) })}
</div>
) : (
<div>
+63 -21
View File
@@ -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 (
<div className='mt-6 sm:mt-8 dltr otp'>
<OTPInput
shouldAutoFocus
value={formik.values.otp}
numInputs={5}
inputType='tel'
containerStyle={{ direction: 'ltr' }}
onChange={(otp: string) => formik.setFieldValue('otp', otp)}
renderInput={(props) =>
<input
{...props}
type='tel'
autoComplete="one-time-code"
inputMode="numeric"
className='w-full h-[45px] sm:h-[50px] flex-1 mx-1 sm:mx-2 bg-container border border-border text-foreground outline-0 rounded-[10px] text-center text-sm' />
}
/>
<>
<div className='mt-6 sm:mt-8 dltr otp'>
<OTPInput
shouldAutoFocus
value={formik.values.otp}
numInputs={5}
inputType='tel'
containerStyle={{ direction: 'ltr' }}
onChange={(otp: string) => formik.setFieldValue('otp', otp)}
renderInput={(props) =>
<input
{...props}
type='tel'
autoComplete="one-time-code"
inputMode="numeric"
className='w-full h-[45px] sm:h-[50px] flex-1 mx-1 sm:mx-2 bg-container border border-border text-foreground outline-0 rounded-[10px] text-center text-sm' />
}
/>
</div>
<div className='text-center w-full pt-6 text-xs'>
{timerRunning ? (
<div>
{t('OTP.TimerRunning', { time: formatCountdown(secondsLeft) })}
</div>
) : (
<div>
{t('OTP.TimerOut')}
<button
type='button'
disabled={isResendPending}
id={AUTH_PAGE_ELEMENT.RESEND_OTP}
className={clsx(
'px-1 transition-colors duration-200',
!isResendPending
? 'text-primary cursor-pointer'
: 'text-neutral-200 cursor-auto'
)}
onClick={onResend}
>
{t('OTP.TimerReset')}
</button>
</div>
)}
</div>
<Button
className='mt-10 dark:bg-white dark:text-black hover:dark:bg-white'
@@ -116,7 +158,7 @@ const StepOtp = ({ phone, slug }: Props) => {
>
{t('OTP.ButtonSubmit')}
</Button>
</div>
</>
)
}
+2 -2
View File
@@ -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": "Didnt receive the code?",
"TimerReset": "Try again",
"TimerReset": "Resend",
"ButtonSubmit": "Next"
},
"NewPassword": {
+2 -2
View File
@@ -9,9 +9,9 @@
"Heading": "کد فعالسازی را وارد کنید",
"Description": "کد ۵ رقمی ارسال شده به شماره {{phoneNumber}} را وارد نمایید.",
"Label": "",
"TimerRunning": "{seconds} ثانیه دیگر تا دریافت کد",
"TimerRunning": "ارسال مجدد تا {{time}} دیگر",
"TimerOut": "کد را دریافت نکردید؟",
"TimerReset": "دوباره امتحان کنید",
"TimerReset": "ارسال مجدد",
"ButtonSubmit": "بعدی"
},
"NewPassword": {