207 lines
7.5 KiB
TypeScript
207 lines
7.5 KiB
TypeScript
import { type FC, useEffect, useRef } from 'react'
|
||
import { type OtpVerifyType } from '../types/AuthTypes'
|
||
import { useFormik } from 'formik'
|
||
import * as Yup from 'yup'
|
||
import { useAuthStore } from '../store/AuthStore'
|
||
import Button from '../../../components/Button'
|
||
import OTPInput from 'react-otp-input'
|
||
import { useCountDown } from '../../../hooks/useCountDown'
|
||
// import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||
import { useLoginWithOtp, useOtpVerify } from '../hooks/useAuthData'
|
||
import { extractErrorMessage, setRefreshToken } from '../../../config/func'
|
||
import { setToken } from '../../../config/func'
|
||
import { toast } from '@/shared/toast'
|
||
import { Paths } from '@/config/Paths'
|
||
|
||
const LoginStep2: FC = () => {
|
||
|
||
const { phone, setStepLogin, devOtpCode, setDevOtpCode } = useAuthStore()
|
||
const { value, reset } = useCountDown(2, true)
|
||
const otpVerify = useOtpVerify()
|
||
const loginWithOtp = useLoginWithOtp()
|
||
const firstOtpInputRef = useRef<HTMLInputElement | null>(null)
|
||
|
||
const formik = useFormik<OtpVerifyType>({
|
||
initialValues: {
|
||
phone: phone,
|
||
// TODO: remove before production — dev-only OTP autofill
|
||
otp: devOtpCode,
|
||
},
|
||
validationSchema: Yup.object({
|
||
otp: Yup.string()
|
||
.required('این فیلد اجباری است.')
|
||
}),
|
||
onSubmit(values) {
|
||
const params: OtpVerifyType = {
|
||
phone: phone,
|
||
otp: values.otp
|
||
}
|
||
otpVerify.mutate(params, {
|
||
onSuccess(data) {
|
||
setToken(data?.data?.tokens?.accessToken?.token)
|
||
setRefreshToken(data?.data?.tokens?.refreshToken?.token)
|
||
// Check if there's a redirect parameter in the URL
|
||
window.location.href = Paths.home
|
||
},
|
||
onError(error) {
|
||
toast(extractErrorMessage(error), 'error')
|
||
},
|
||
})
|
||
},
|
||
})
|
||
|
||
useEffect(() => {
|
||
const frameId = requestAnimationFrame(() => {
|
||
firstOtpInputRef.current?.focus()
|
||
})
|
||
|
||
return () => cancelAnimationFrame(frameId)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
// بررسی پشتیبانی از Web OTP API
|
||
if (!("OTPCredential" in window)) return;
|
||
|
||
const ac = new AbortController();
|
||
|
||
navigator.credentials
|
||
.get({
|
||
otp: { transport: ["sms"] },
|
||
signal: ac.signal,
|
||
} as CredentialRequestOptions) // اطمینان از تطابق تایپها
|
||
.then((otpCredential) => {
|
||
if (otpCredential && "otp" in otpCredential) {
|
||
formik.setFieldValue("otp", (otpCredential).otp);
|
||
}
|
||
})
|
||
.catch((err) => console.error("SMS AutoFill Error:", err));
|
||
|
||
return () => ac.abort();
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, []);
|
||
|
||
const reSend = () => {
|
||
loginWithOtp.mutate({ phone: phone }, {
|
||
onSuccess: (data) => {
|
||
// TODO: remove before production — dev-only OTP autofill
|
||
const code = data?.data?.code
|
||
if (code) {
|
||
const otp = String(code)
|
||
setDevOtpCode(otp)
|
||
formik.setFieldValue('otp', otp)
|
||
}
|
||
reset()
|
||
toast('کد مجدد ارسال شد', 'success')
|
||
},
|
||
onError: (error) => {
|
||
toast(extractErrorMessage(error), 'error')
|
||
}
|
||
})
|
||
}
|
||
|
||
useEffect(() => {
|
||
|
||
if (formik.values.otp.length === 5) {
|
||
formik.handleSubmit()
|
||
}
|
||
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [formik.values.otp])
|
||
|
||
|
||
return (
|
||
<form onSubmit={formik.handleSubmit}>
|
||
<div className='mt-5'>
|
||
<h2 className='lg:text-2xl font-bold'>
|
||
کد تایید را وارد کنید
|
||
</h2>
|
||
<p className='text-description flex lg:flex-row flex-col lg:gap-1 gap-2 text-sm lg:mt-2 mt-3'>
|
||
<div className='flex gap-1'>
|
||
<div>کد تایید برای</div>
|
||
<div>{phone}</div>
|
||
<div>
|
||
ارسال شد.
|
||
</div>
|
||
</div>
|
||
<div onClick={() => setStepLogin(1)} className='text-black cursor-pointer'>
|
||
تغییر شماره
|
||
</div>
|
||
</p>
|
||
</div>
|
||
|
||
<div className='mt-16'>
|
||
<div className='text-sm'>
|
||
کد تایید
|
||
</div>
|
||
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||
<OTPInput
|
||
shouldAutoFocus
|
||
value={formik.values.otp}
|
||
numInputs={5}
|
||
inputType='tel'
|
||
onChange={(otp: string) => formik.setFieldValue('otp', otp)}
|
||
renderInput={(props, index) => {
|
||
const { ref, ...inputProps } = props
|
||
|
||
return (
|
||
<input
|
||
{...inputProps}
|
||
ref={(element) => {
|
||
ref(element)
|
||
if (index === 0) {
|
||
firstOtpInputRef.current = element
|
||
}
|
||
}}
|
||
type='tel'
|
||
autoComplete="one-time-otp"
|
||
inputMode="numeric"
|
||
className='w-full h-[50px] min-w-[50px] flex-1 mx-2 bg-white border border-border rounded-[10px]' />
|
||
)
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
{
|
||
formik.touched.otp && formik.errors.otp &&
|
||
<div className='text-xs mt-2 text-red-500'>{formik.errors.otp}</div>
|
||
}
|
||
|
||
<div className='flex mt-4 justify-end'>
|
||
{
|
||
value !== '00:00' ?
|
||
<div className='flex gap-1 text-description text-xs'>
|
||
<div>{value}</div>
|
||
<div>مانده تا دریافت مجدد کد</div>
|
||
</div>
|
||
:
|
||
<div onClick={reSend} className='flex cursor-pointer gap-1 pl-2 text-black text-sm'>
|
||
<div className=''>
|
||
ارسال مجدد
|
||
</div>
|
||
</div>
|
||
|
||
}
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<Button
|
||
label={'ورود'}
|
||
className='mt-8'
|
||
type='submit'
|
||
isLoading={otpVerify.isPending}
|
||
/>
|
||
|
||
{/* <div onClick={() => setStepLogin(3)} className='mt-6 cursor-pointer flex gap-2 items-center text-sm'>
|
||
<p className='text-description'>
|
||
{t('auth.login_with_password')}
|
||
</p>
|
||
<img src={ArrowLeftIcon} className='w-5' />
|
||
</div> */}
|
||
</form>
|
||
)
|
||
}
|
||
|
||
export default LoginStep2 |