login
This commit is contained in:
Generated
+11
@@ -23,6 +23,7 @@
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^12.0.2",
|
||||
@@ -4574,6 +4575,16 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-otp-input": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-otp-input/-/react-otp-input-3.1.1.tgz",
|
||||
"integrity": "sha512-bjPavgJ0/Zmf/AYi4onj8FbH93IjeD+e8pWwxIJreDEWsU1ILR5fs8jEJmMGWSBe/yyvPP6X/W6Mk9UkOCkTPw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": ">=16.8.6 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^12.0.2",
|
||||
|
||||
+5
-3
@@ -9,20 +9,22 @@ import MainRouter from './router/MainRouter'
|
||||
// import AuthRouter from './router/AuthRouter'
|
||||
import ToastContainer from './components/Toast'
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { getToken } from './config/func';
|
||||
import AuthRouter from './router/AuthRouter';
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
const App: FC = () => {
|
||||
|
||||
// const isLoggedIn = getToken()
|
||||
const isLoggedIn = getToken()
|
||||
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
{/* {isLoggedIn ? <MainRouter /> : <AuthRouter />} */}
|
||||
<HelmetProvider>
|
||||
{isLoggedIn ? <MainRouter /> : <AuthRouter />}
|
||||
|
||||
<MainRouter />
|
||||
</HelmetProvider>
|
||||
<ToastContainer />
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="36" height="65" viewBox="0 0 36 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.7925 64.8275L20.2339 60.6914L17.7098 64.7757L15.1835 60.6957L12.6292 64.8275H22.7925Z" fill="#F6AF31"/>
|
||||
<path d="M17.6947 0L0 28.6356L2.32581 32.4009C2.74183 31.7263 3.14707 31.0862 3.51351 30.4784L2.37539 28.6356L17.6947 3.84294L33.014 28.6356L31.891 30.4547C32.2682 31.1228 32.7015 31.7091 33.0787 32.3772L35.3894 28.6356L17.6947 0Z" fill="#F6AF31"/>
|
||||
<path d="M17.7098 7.50488L0.0150757 36.1405L17.7098 64.7761L35.4045 36.1405L17.7098 7.50488ZM18.4319 29.3124V12.516L23.7797 21.1718L18.4319 29.3124ZM24.6592 22.5943L28.6491 29.0516L18.4319 43.5289V32.0734L24.6592 22.5943ZM16.9166 31.86V43.4362L6.94509 28.7693L11.1333 21.9908L16.9166 31.86ZM12.0364 20.5316L16.9187 12.6324V28.8641L12.0364 20.5316ZM18.4319 46.1563L29.5156 30.4526L33.0291 36.1405L18.4319 59.765V46.1563ZM6.07426 30.181L16.9187 46.1304V59.6507L2.39262 36.1405L6.07426 30.181Z" fill="#4F5260"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 978 B |
@@ -1,5 +1,6 @@
|
||||
export const Paths = {
|
||||
home: '/home',
|
||||
profile: '/profile',
|
||||
myOrders: '/my-orders',
|
||||
proformaInvoice: '/proforma-invoice',
|
||||
newOrder: '/new-order',
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ const instance = axios.create({
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIwMUtGWjREWDhTNVJNOFMzMDc1NVZTWUNQUSIsImlhdCI6MTc2OTQ5NzYyMSwiZXhwIjoxNzY5NDk4ODIxfQ.YK6hQh3y-msWITy2sdLMsmwBvwXC5iHa3OxtWphyWQI" ||
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIwMUtGWjREWDhTNVJNOFMzMDc1NVZTWUNQUSIsImlhdCI6MTc2OTUwMTE0OCwiZXhwIjoxNzY5NTAyMzQ4fQ.IOYPKCBPCUS9BtWdIDJWG1uG3nnVmLoInDirEZfsl6k" ||
|
||||
getToken();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
@@ -45,3 +45,23 @@ export const NumberFormat = (num?: number): string => {
|
||||
if (!num) return "0";
|
||||
return num.toLocaleString("fa-IR");
|
||||
};
|
||||
|
||||
export interface IGeneralError {
|
||||
status: number;
|
||||
success: boolean;
|
||||
error: {
|
||||
message: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export const extractErrorMessage = (
|
||||
error: Error | unknown,
|
||||
fallbackMessage: string = "خطایی رخ داده است",
|
||||
): string => {
|
||||
try {
|
||||
const axiosError = error as { response?: { data: IGeneralError } };
|
||||
return axiosError?.response?.data?.error?.message?.[0] || fallbackMessage;
|
||||
} catch {
|
||||
return fallbackMessage;
|
||||
}
|
||||
};
|
||||
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export interface ICountDown {
|
||||
m: number;
|
||||
s: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param minutes how many minutes.
|
||||
* @param autoplay by default true. if you want play coundDown after an action, you can set it to false and manually play it.
|
||||
* @param onEnd you can pass a callback function that will trigger at the END.
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export function useCountDown(
|
||||
minutes: number,
|
||||
autoplay = true,
|
||||
onEnd = () => {},
|
||||
) {
|
||||
const [countDown, setCountDown] = useState<ICountDown>({ m: minutes, s: 0 });
|
||||
const [stop, setStop] = useState<boolean>(!autoplay);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
if (countDown.m !== 0 || countDown.s !== 0) {
|
||||
setCountDown(calculateCountDown(countDown));
|
||||
} else {
|
||||
onEnd();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [countDown]);
|
||||
|
||||
const reset = () => {
|
||||
setCountDown({ m: minutes, s: 0 });
|
||||
};
|
||||
const pause = () => {
|
||||
setStop(true);
|
||||
};
|
||||
const play = () => {
|
||||
setStop(false);
|
||||
setCountDown({ ...countDown });
|
||||
};
|
||||
|
||||
const calculateCountDown = (st: ICountDown): ICountDown => {
|
||||
if (stop) {
|
||||
return countDown;
|
||||
}
|
||||
const timeLeft = { m: 0, s: 0 };
|
||||
const remain = st.m * 60 + st.s - 1;
|
||||
if (remain > 0) {
|
||||
timeLeft.m = Math.floor(remain / 60);
|
||||
timeLeft.s = remain - timeLeft.m * 60;
|
||||
return timeLeft;
|
||||
} else {
|
||||
if (remain === 0 && countDown.s === 1) {
|
||||
return timeLeft;
|
||||
} else {
|
||||
return countDown;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
value: `${countDown.m > 9 ? countDown.m : "0" + countDown.m}:${
|
||||
countDown.s > 9 ? countDown.s : "0" + countDown.s
|
||||
}`,
|
||||
reset,
|
||||
pause,
|
||||
play,
|
||||
};
|
||||
}
|
||||
+27
-1
@@ -61,7 +61,9 @@ tbody tr:nth-child(odd) {
|
||||
width: 100% !important;
|
||||
margin: 0px;
|
||||
padding-right: 16px !important;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
}
|
||||
.readOny .rmdp-input {
|
||||
background-color: #f5f5f5 !important;
|
||||
@@ -75,3 +77,27 @@ tbody tr:nth-child(odd) {
|
||||
.rowTwoInput {
|
||||
@apply flex flex-col items-start sm:flex-row sm:gap-5 gap-5;
|
||||
}
|
||||
|
||||
.rmdp-input {
|
||||
min-height: 40px;
|
||||
border-radius: 12px !important;
|
||||
border: 1px solid #d0d0d0 !important;
|
||||
font-size: 12px !important;
|
||||
width: 100% !important;
|
||||
margin: 0px;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
.readOny .rmdp-input {
|
||||
background-color: #f5f5f5 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.rmdp-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dltr {
|
||||
direction: ltr;
|
||||
}
|
||||
.drtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { type FC } from 'react'
|
||||
import LogoImage from '../../assets/images/logo.svg'
|
||||
import LogoSmallImage from '../../assets/images/logo-small.svg'
|
||||
import { useAuthStore } from './store/AuthStore'
|
||||
import LoginStep1 from './components/LoginStep1'
|
||||
import LoginStep2 from './components/LoginStep2'
|
||||
|
||||
const Login: FC = () => {
|
||||
|
||||
const { stepLogin, phone } = useAuthStore()
|
||||
|
||||
|
||||
return (
|
||||
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
|
||||
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
|
||||
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||
<div className='flex-1 h-full flex flex-col'>
|
||||
|
||||
<div className='flex relative flex-1 flex-col h-full justify-center'>
|
||||
<img src={LogoSmallImage} className='w-8 hidden xl:block' />
|
||||
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden' />
|
||||
{
|
||||
stepLogin === 1 ?
|
||||
<LoginStep1 />
|
||||
:
|
||||
stepLogin === 2 && !!phone ?
|
||||
<LoginStep2 />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 min-w-[50%] lg:flex hidden justify-center items-center'>
|
||||
<img src={LogoImage} className='h-20' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
@@ -0,0 +1,83 @@
|
||||
import { type FC } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { type LoginType } from '../types/AuthTypes'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useAuthStore } from '../store/AuthStore'
|
||||
import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { toast } from '@/shared/toast'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
|
||||
const LoginStep1: FC = () => {
|
||||
|
||||
const { phone, setPhone, setStepLogin } = useAuthStore()
|
||||
const loginWithOtp = useLoginWithOtp()
|
||||
const checkHasAccount = useCheckHasAccount()
|
||||
|
||||
const formik = useFormik<LoginType>({
|
||||
initialValues: {
|
||||
phone_email: phone,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone_email: Yup.string()
|
||||
.required('این فیلد اجباری است.')
|
||||
}),
|
||||
onSubmit(values) {
|
||||
setPhone(values.phone_email)
|
||||
loginWithOtp.mutate({ phone: values.phone_email }, {
|
||||
onSuccess() {
|
||||
setStepLogin(2)
|
||||
toast('کد تایید ارسال شد', 'success')
|
||||
},
|
||||
onError(error) {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
خوش آمدید
|
||||
</h2>
|
||||
<p className='text-description text-sm mt-2'>
|
||||
لطفا اطلاعات خود را وارد کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mt-16'>
|
||||
<Input
|
||||
label={'شماره موبایل'}
|
||||
placeholder={'شماره موبایل خود را وارد کنید'}
|
||||
type='tel'
|
||||
className='text-right'
|
||||
name='phone_email'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.phone_email}
|
||||
/>
|
||||
|
||||
{
|
||||
formik.touched.phone_email && formik.errors.phone_email &&
|
||||
<Error
|
||||
errorText={formik.errors.phone_email}
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<Button
|
||||
label={'ادامه'}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginStep1
|
||||
@@ -0,0 +1,180 @@
|
||||
import { type FC, useEffect } 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 } = useAuthStore()
|
||||
const { value, reset } = useCountDown(2, true)
|
||||
const otpVerify = useOtpVerify()
|
||||
const loginWithOtp = useLoginWithOtp()
|
||||
|
||||
const formik = useFormik<OtpVerifyType>({
|
||||
initialValues: {
|
||||
phone: phone,
|
||||
otp: ''
|
||||
},
|
||||
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.profile
|
||||
},
|
||||
onError(error) {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
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: () => {
|
||||
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 (
|
||||
<div>
|
||||
<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) =>
|
||||
<input
|
||||
{...props}
|
||||
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'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
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> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginStep2
|
||||
@@ -0,0 +1,77 @@
|
||||
import { FC } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CheckHasAccountPhoneType } from '../types/AuthTypes'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useAuthStore } from '../store/AuthStore'
|
||||
import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { useCheckHasAccountRegister, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
const RegisterStep1: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setPhone, setStepLogin } = useAuthStore()
|
||||
const loginWithOtp = useLoginWithOtp()
|
||||
const checkHasAccount = useCheckHasAccountRegister()
|
||||
|
||||
const formik = useFormik<CheckHasAccountPhoneType>({
|
||||
initialValues: {
|
||||
phone: '',
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit(values) {
|
||||
checkHasAccount.mutate(values, {
|
||||
onSuccess() {
|
||||
setPhone(values.phone)
|
||||
setStepLogin(2)
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast(error?.response?.data?.error?.message[0], 'error')
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='flex-1'>
|
||||
|
||||
|
||||
<div className='mt-16'>
|
||||
<Input
|
||||
label={t('auth.mobile_number')}
|
||||
placeholder={t('auth.enter_mobile_number')}
|
||||
type='tel'
|
||||
className='text-right'
|
||||
name='phone'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.phone}
|
||||
/>
|
||||
|
||||
{
|
||||
formik.touched.phone && formik.errors.phone &&
|
||||
<Error
|
||||
errorText={formik.errors.phone}
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<Button
|
||||
label={t('auth.next')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterStep1
|
||||
@@ -0,0 +1,137 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import { RegisterType } from '../types/AuthTypes'
|
||||
import * as Yup from 'yup'
|
||||
import { useAuthStore } from '../store/AuthStore'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../../components/Input'
|
||||
import Button from '../../../components/Button'
|
||||
import { useRegister } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import { setToken } from '../../../config/func'
|
||||
import { setRefreshToken } from '../../../config/func'
|
||||
import { getRedirectUrl } from '../../../config/func'
|
||||
import { toast } from '../../../components/Toast'
|
||||
const RegisterStep2: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { phone } = useAuthStore()
|
||||
const register = useRegister()
|
||||
|
||||
const formik = useFormik<RegisterType>({
|
||||
initialValues: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
code: 0,
|
||||
password: '',
|
||||
phone: phone,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
firstName: Yup.string()
|
||||
.required(t('errors.required')),
|
||||
lastName: Yup.string()
|
||||
.required(t('errors.required')),
|
||||
code: Yup.number()
|
||||
.required(t('errors.required')),
|
||||
password: Yup.string()
|
||||
.required(t('errors.required')),
|
||||
phone: Yup.string()
|
||||
.required(t('errors.required')),
|
||||
}),
|
||||
onSubmit(values) {
|
||||
register.mutate(values, {
|
||||
onSuccess(data) {
|
||||
setToken(data?.data?.accessToken?.token)
|
||||
setRefreshToken(data?.data?.refreshToken?.token)
|
||||
const redirectUrl = getRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl;
|
||||
return; // Prevent the default navigation
|
||||
}
|
||||
window.location.href = Pages.dashboard
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast(error?.response?.data?.error?.message[0], 'error')
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='mt-5 rowTwoInput'>
|
||||
<Input
|
||||
label={t('auth.name')}
|
||||
placeholder={t('auth.enter_name')}
|
||||
name='firstName'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('auth.family')}
|
||||
placeholder={t('auth.enter_family')}
|
||||
name='lastName'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : ''}
|
||||
/>
|
||||
|
||||
</div>
|
||||
{/* <div className='mt-4 min-w-full rowTwoInput'>
|
||||
<DatePickerComponent
|
||||
label={t('auth.dateofbirth')}
|
||||
onChange={(date: string) => formik.setFieldValue('birthDate', date)}
|
||||
placeholder={t('auth.select_date')}
|
||||
error_text={formik.touched.birthDate && formik.errors.birthDate ? formik.errors.birthDate : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('auth.national_code')}
|
||||
placeholder={t('auth.national_code_enter')}
|
||||
name='nationalCode'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.nationalCode && formik.errors.nationalCode ? formik.errors.nationalCode : ''}
|
||||
/>
|
||||
</div> */}
|
||||
|
||||
<div className='mt-4 rowTwoInput'>
|
||||
<Input
|
||||
label={t('auth.password')}
|
||||
placeholder={t('auth.enter_password')}
|
||||
type='password'
|
||||
name='password'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||
/>
|
||||
<Input
|
||||
label={t('auth.phonen_number')}
|
||||
placeholder={t('auth.enter_phone_number')}
|
||||
readOnly
|
||||
value={phone}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-4 rowTwoInput'>
|
||||
<Input
|
||||
label={t('auth.verify_code')}
|
||||
placeholder={t('auth.enter_verify_code')}
|
||||
type='tel'
|
||||
name='code'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.code && formik.errors.code ? formik.errors.code : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
label={t('auth.next')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={register.isPending}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterStep2
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import * as api from '../service/AuthService'
|
||||
import type { CheckHasAccountPhoneType, CheckHasAccountType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType, RegisterType } from '../types/AuthTypes';
|
||||
|
||||
export const useLoginWithPassword = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: LoginWithPasswordType) => api.loginWithPassword(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useLoginWithOtp = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: LoginWithOtpType) => api.loginWithOtp(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useOtpVerify = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: OtpVerifyType) => api.otpVerify(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCheckHasAccount = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: CheckHasAccountType) => api.checkHasAccount(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCheckHasAccountRegister = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: CheckHasAccountPhoneType) => api.checkHasAccountRegister(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useRegister = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: RegisterType) => api.register(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useLogout = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.logout,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type {
|
||||
CheckHasAccountPhoneType,
|
||||
CheckHasAccountType,
|
||||
LoginWithOtpType,
|
||||
LoginWithPasswordType,
|
||||
OtpVerifyType,
|
||||
RefreshTokenType,
|
||||
RegisterType,
|
||||
} from "../types/AuthTypes";
|
||||
|
||||
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
||||
const { data } = await axios.post(`/auth/login/password`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const loginWithOtp = async (params: LoginWithOtpType) => {
|
||||
const { data } = await axios.post(`/public/auth/otp/request`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const otpVerify = async (params: OtpVerifyType) => {
|
||||
const { data } = await axios.post(`/public/auth/otp/verify`, params);
|
||||
return data;
|
||||
};
|
||||
export const checkHasAccount = async (params: CheckHasAccountType) => {
|
||||
const { data } = await axios.post(`/auth/check`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const checkHasAccountRegister = async (
|
||||
params: CheckHasAccountPhoneType,
|
||||
) => {
|
||||
const { data } = await axios.post(`/auth/register/initiate`, params);
|
||||
return data;
|
||||
};
|
||||
export const register = async (params: RegisterType) => {
|
||||
const { data } = await axios.post(`/auth/register/complete`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const refreshToken = async (params: RefreshTokenType) => {
|
||||
const { data } = await axios.post(`/auth/refresh`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const logout = async () => {
|
||||
const { data } = await axios.post(`/auth/logout`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { create } from "zustand";
|
||||
import { type AuthStoreType } from "../../auth/types/AuthTypes";
|
||||
|
||||
export const useAuthStore = create<AuthStoreType>((set) => ({
|
||||
phone: "",
|
||||
setPhone(value) {
|
||||
set({ phone: value });
|
||||
},
|
||||
email: "",
|
||||
setEmail(value) {
|
||||
set({ email: value });
|
||||
},
|
||||
stepLogin: 1,
|
||||
setStepLogin(value) {
|
||||
set({ stepLogin: value });
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,50 @@
|
||||
export type LoginType = {
|
||||
phone_email: string;
|
||||
};
|
||||
export type LoginPasswordType = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type AuthStoreType = {
|
||||
phone: string;
|
||||
setPhone: (value: string) => void;
|
||||
email: string;
|
||||
setEmail: (value: string) => void;
|
||||
stepLogin: number;
|
||||
setStepLogin: (value: number) => void;
|
||||
};
|
||||
|
||||
export type LoginWithPasswordType = {
|
||||
email?: string;
|
||||
password: string;
|
||||
phone?: string;
|
||||
};
|
||||
|
||||
export type LoginWithOtpType = {
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type OtpVerifyType = {
|
||||
phone: string;
|
||||
otp: string;
|
||||
};
|
||||
|
||||
export type CheckHasAccountType = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type CheckHasAccountPhoneType = {
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type RegisterType = {
|
||||
phone: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
code: number;
|
||||
};
|
||||
|
||||
export type RefreshTokenType = {
|
||||
refreshToken: string;
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
import Login from '@/pages/auth/Login'
|
||||
import { type FC } from 'react'
|
||||
import { Routes } from 'react-router-dom'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
const AuthRouter: FC = () => {
|
||||
return (
|
||||
<Routes>
|
||||
{/* <Route path="/login" element={<Login />} /> */}
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user