sign in
This commit is contained in:
@@ -54,9 +54,6 @@ const App: FC = () => {
|
||||
|
||||
}, [])
|
||||
|
||||
console.log('us', isLogin);
|
||||
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react'
|
||||
import ReactLoading from 'react-loading'
|
||||
import MoonLoader from "react-spinners/MoonLoader"
|
||||
import { XOR } from '../helpers/types'
|
||||
import { clx } from '../helpers/utils'
|
||||
|
||||
@@ -21,7 +21,7 @@ const Button: FC<Props> = memo((props: Props) => {
|
||||
<button {...props} className={`${buttonClass} ${props.className}`} >
|
||||
{
|
||||
props.isLoading ?
|
||||
<ReactLoading type='spin' color={'white'} width={20} height={20} />
|
||||
<MoonLoader color="white" size={20} />
|
||||
:
|
||||
props.label || props.children
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import axios from "axios";
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_BASE_URL,
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response.status === 401) {
|
||||
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME);
|
||||
window.location.href = "/auth";
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axiosInstance.interceptors.request.use(async function (config) {
|
||||
const token = localStorage.getItem(import.meta.env.VITE_TOKEN_NAME)
|
||||
? localStorage.getItem(import.meta.env.VITE_TOKEN_NAME)
|
||||
: "";
|
||||
|
||||
config.headers.Authorization = "Bearer " + token;
|
||||
return config;
|
||||
});
|
||||
|
||||
export default axiosInstance;
|
||||
@@ -0,0 +1,4 @@
|
||||
export function isEmail(input: string): boolean {
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(input);
|
||||
}
|
||||
+13
-3
@@ -5,6 +5,16 @@ export type XOR<T, U> = T | U extends object
|
||||
: T | U;
|
||||
|
||||
export type ItemsSelectType = {
|
||||
value: any,
|
||||
label: string,
|
||||
}
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type ErrorType = Error & {
|
||||
response?: {
|
||||
data?: {
|
||||
error: {
|
||||
message: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -126,3 +126,8 @@ tbody tr {
|
||||
.overflowX::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
direction: rtl;
|
||||
font-family: "irancell" !important;
|
||||
}
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@
|
||||
"enter_password_step3": "رمز عبور را وارد کنید",
|
||||
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
||||
"forgot_password": "فراموشی رمز عبور",
|
||||
"login_with_otp": "ورود با رمز عبور یکبار مصرف"
|
||||
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
|
||||
"otp_sent": "کد تایید ارسال شد"
|
||||
},
|
||||
"errors": {
|
||||
"required": "این فیلد اجباری می باشد"
|
||||
|
||||
@@ -8,7 +8,9 @@ import LoginStep3 from './components/LoginStep3'
|
||||
|
||||
const Login: FC = () => {
|
||||
|
||||
const { stepLogin } = useAuthStore()
|
||||
const { stepLogin, phone, email } = useAuthStore()
|
||||
|
||||
console.log(phone, email, stepLogin);
|
||||
|
||||
|
||||
return (
|
||||
@@ -23,11 +25,13 @@ const Login: FC = () => {
|
||||
stepLogin === 1 ?
|
||||
<LoginStep1 />
|
||||
:
|
||||
stepLogin === 2 ?
|
||||
stepLogin === 2 && !!phone ?
|
||||
<LoginStep2 />
|
||||
: stepLogin === 3 ?
|
||||
: stepLogin === 2 && !!email ?
|
||||
<LoginStep3 />
|
||||
: null
|
||||
: stepLogin === 3 ?
|
||||
<LoginStep3 />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import { isEmail } from '../../../config/func'
|
||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
const LoginStep1: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setPhone, phone, setStepLogin } = useAuthStore()
|
||||
const { setPhone, phone, setStepLogin, setEmail } = useAuthStore()
|
||||
const loginWithOtp = useLoginWithOtp()
|
||||
const checkHasAccount = useCheckHasAccount()
|
||||
|
||||
const formik = useFormik<LoginType>({
|
||||
initialValues: {
|
||||
@@ -24,8 +30,28 @@ const LoginStep1: FC = () => {
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit(values) {
|
||||
setPhone(values.phone_email)
|
||||
setStepLogin(2)
|
||||
if (isEmail(values.phone_email)) {
|
||||
setEmail(values.phone_email)
|
||||
checkHasAccount.mutate({ email: values.phone_email }, {
|
||||
onSuccess() {
|
||||
setStepLogin(2)
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
},
|
||||
})
|
||||
} else {
|
||||
setPhone(values.phone_email)
|
||||
loginWithOtp.mutate({ phone: values.phone_email }, {
|
||||
onSuccess() {
|
||||
setStepLogin(2)
|
||||
toast.success(t('auth.otp_sent'))
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -65,6 +91,7 @@ const LoginStep1: FC = () => {
|
||||
label={t('auth.next')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
/>
|
||||
|
||||
<div className='mt-6 flex justify-center gap-2 items-center text-sm'>
|
||||
|
||||
@@ -1,34 +1,49 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { LoginType } from '../types/AuthTypes'
|
||||
import { 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 ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import { useOtpVerify } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
const LoginStep2: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setPhone, phone, setStepLogin } = useAuthStore()
|
||||
const [otpValue, setOtpValue] = useState<string>()
|
||||
const { phone, setStepLogin } = useAuthStore()
|
||||
const { value } = useCountDown(2, true)
|
||||
const otpVerify = useOtpVerify()
|
||||
|
||||
const formik = useFormik<LoginType>({
|
||||
const formik = useFormik<OtpVerifyType>({
|
||||
initialValues: {
|
||||
phone_email: phone,
|
||||
phone: phone,
|
||||
code: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone_email: Yup.string()
|
||||
code: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit(values) {
|
||||
setPhone(values.phone_email)
|
||||
localStorage.setItem(import.meta.env.VITE_TOKEN_NAME, 'token')
|
||||
window.location.href = Pages.dashboard
|
||||
const params: OtpVerifyType = {
|
||||
phone: phone,
|
||||
code: values.code
|
||||
}
|
||||
otpVerify.mutate(params, {
|
||||
onSuccess(data) {
|
||||
localStorage.setItem(import.meta.env.VITE_TOKEN_NAME, data?.data?.accessToken?.token)
|
||||
localStorage.setItem(import.meta.env.VITE_REFRESH_TOKEN_NAME, data?.data?.refreshToken?.token)
|
||||
window.location.href = Pages.dashboard
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -59,14 +74,19 @@ const LoginStep2: FC = () => {
|
||||
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||
<OTPInput
|
||||
shouldAutoFocus
|
||||
value={otpValue}
|
||||
value={formik.values.code}
|
||||
numInputs={5}
|
||||
inputType='tel'
|
||||
onChange={(otp: string) => setOtpValue(otp)}
|
||||
onChange={(otp: string) => formik.setFieldValue('code', otp)}
|
||||
renderInput={(props) => <input {...props} className='w-full h-[50px] flex-1 mx-2 bg-white border rounded-2.5' />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
formik.touched.code && formik.errors.code &&
|
||||
<div className='text-xs mt-2 text-red-500'>{formik.errors.code}</div>
|
||||
}
|
||||
|
||||
<div className='flex mt-4 justify-end'>
|
||||
<div className='flex gap-1 text-description text-xs'>
|
||||
<div>{value}</div>
|
||||
@@ -81,14 +101,15 @@ const LoginStep2: FC = () => {
|
||||
label={t('auth.login')}
|
||||
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'>
|
||||
{/* <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> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { LoginPasswordType } from '../types/AuthTypes'
|
||||
import { LoginWithPasswordType } from '../types/AuthTypes'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useAuthStore } from '../store/AuthStore'
|
||||
@@ -10,24 +10,36 @@ import Button from '../../../components/Button'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||
import { useLoginWithPassword } from '../hooks/useAuthData'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
const LoginStep3: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setStepLogin } = useAuthStore()
|
||||
const { setStepLogin, email } = useAuthStore()
|
||||
const loginWithPassword = useLoginWithPassword()
|
||||
|
||||
const formik = useFormik<LoginPasswordType>({
|
||||
const formik = useFormik<LoginWithPasswordType>({
|
||||
initialValues: {
|
||||
password: '',
|
||||
email: email
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone_email: Yup.string()
|
||||
password: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit(values) {
|
||||
console.log(values)
|
||||
localStorage.setItem(import.meta.env.VITE_TOKEN_NAME, 'token')
|
||||
window.location.href = Pages.dashboard
|
||||
loginWithPassword.mutate(values, {
|
||||
onSuccess(data) {
|
||||
localStorage.setItem(import.meta.env.VITE_TOKEN_NAME, data?.data?.accessToken?.token)
|
||||
localStorage.setItem(import.meta.env.VITE_REFRESH_TOKEN_NAME, data?.data?.refreshToken?.token)
|
||||
window.location.href = Pages.dashboard
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
},
|
||||
})
|
||||
|
||||
},
|
||||
})
|
||||
@@ -51,6 +63,7 @@ const LoginStep3: FC = () => {
|
||||
name='password'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.password}
|
||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||
/>
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import * as api from '../service/AuthService'
|
||||
import { CheckHasAccountType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType } 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),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import axios from "../../../config/axios";
|
||||
import {
|
||||
CheckHasAccountType,
|
||||
LoginWithOtpType,
|
||||
LoginWithPasswordType,
|
||||
OtpVerifyType,
|
||||
} 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(`/auth/otp/send`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const otpVerify = async (params: OtpVerifyType) => {
|
||||
const { data } = await axios.post(`/auth/otp/verify`, params);
|
||||
return data;
|
||||
};
|
||||
export const checkHasAccount = async (params: CheckHasAccountType) => {
|
||||
const { data } = await axios.post(`/auth/check`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -6,6 +6,10 @@ export const useAuthStore = create<AuthStoreType>((set) => ({
|
||||
setPhone(value) {
|
||||
set({ phone: value });
|
||||
},
|
||||
email: "",
|
||||
setEmail(value) {
|
||||
set({ email: value });
|
||||
},
|
||||
stepLogin: 1,
|
||||
setStepLogin(value) {
|
||||
set({ stepLogin: value });
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { XOR } from "../../../helpers/types";
|
||||
|
||||
export type LoginType = {
|
||||
phone_email: string;
|
||||
};
|
||||
@@ -8,6 +10,28 @@ export type LoginPasswordType = {
|
||||
export type AuthStoreType = {
|
||||
phone: string;
|
||||
setPhone: (value: string) => void;
|
||||
email: string;
|
||||
setEmail: (value: string) => void;
|
||||
stepLogin: number;
|
||||
setStepLogin: (value: number) => void;
|
||||
};
|
||||
|
||||
export type LoginWithPasswordType = XOR<
|
||||
{ email: string },
|
||||
{ phone: number }
|
||||
> & {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type LoginWithOtpType = {
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type OtpVerifyType = {
|
||||
phone: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type CheckHasAccountType = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user