category service

This commit is contained in:
hamid zarghami
2025-01-26 15:45:46 +03:30
parent bfb9f3cbcb
commit 22482aa20a
34 changed files with 1146 additions and 204 deletions
+8 -4
View File
@@ -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>
+13 -53
View File
@@ -2,20 +2,23 @@ import { FC } from 'react'
import LogoImage from '../../assets/images/logo.svg'
import LogoSmallImage from '../../assets/images/logo-small.svg'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import DatePickerComponent from '../../components/DatePicker'
import Button from '../../components/Button'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import RegisterStep1 from './components/RegisterStep1'
import { useAuthStore } from './store/AuthStore'
import RegisterStep2 from './components/RegisterStep2'
const Register: FC = () => {
const { stepLogin } = useAuthStore()
const { t } = useTranslation('global')
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 flex flex-col justify-center min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
<div className=''>
<img src={LogoSmallImage} className='w-8' />
<div className='mt-5'>
@@ -27,55 +30,12 @@ const Register: FC = () => {
</p>
</div>
{/* <div className='mt-5 text-sm'>
{t('auth.user_info')}
</div> */}
<div className='mt-5 rowTwoInput'>
<Input
label={t('auth.name')}
placeholder={t('auth.enter_name')}
/>
<Input
label={t('auth.family')}
placeholder={t('auth.enter_family')}
/>
</div>
<div className='mt-4 min-w-full rowTwoInput'>
<DatePickerComponent label={t('auth.dateofbirth')} onChange={(date: string) => console.log(date)} placeholder={t('auth.select_date')} />
<Input
label={t('auth.national_code')}
placeholder={t('auth.national_code_enter')}
/>
</div>
<div className='mt-4 rowTwoInput'>
<Input
label={t('auth.email')}
placeholder={t('auth.enter_email')}
/>
<Input
label={t('auth.phonen_number')}
placeholder={t('auth.enter_phone_number')}
/>
</div>
<div className='mt-4 rowTwoInput'>
<Input
label={t('auth.password')}
placeholder={t('auth.enter_password')}
type='password'
/>
</div>
<Button
label={t('auth.next')}
className='mt-8'
/>
{
stepLogin === 1 ?
<RegisterStep1 />
:
<RegisterStep2 />
}
<div className='mt-6 flex justify-center gap-2 items-center text-sm'>
<p className='text-description'>
+30 -13
View File
@@ -7,13 +7,17 @@ import * as Yup from 'yup'
import { useAuthStore } from '../store/AuthStore'
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 +28,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,16 +89,9 @@ 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'>
<p className='text-description'>
{t('auth.have_account')}
</p>
<Link to={Pages.auth.register}>
<div>{t('auth.register')}</div>
</Link>
</div>
</div>
)
}
+36 -15
View File
@@ -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>
)
}
+20 -7
View File
@@ -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,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 'react-toastify'
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(error?.response?.data?.error?.message[0])
},
})
},
})
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
+139
View File
@@ -0,0 +1,139 @@
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 DatePickerComponent from '../../../components/DatePicker'
import Button from '../../../components/Button'
import { useRegister } from '../hooks/useAuthData'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { Pages } from '../../../config/Pages'
const RegisterStep2: FC = () => {
const { t } = useTranslation('global')
const { phone } = useAuthStore()
const register = useRegister()
const formik = useFormik<RegisterType>({
initialValues: {
firstName: '',
lastName: '',
birthDate: '',
code: 0,
nationalCode: '',
password: '',
phone: phone,
},
validationSchema: Yup.object({
firstName: Yup.string()
.required(t('errors.required')),
lastName: Yup.string()
.required(t('errors.required')),
birthDate: Yup.string()
.required(t('errors.required')),
code: Yup.number()
.required(t('errors.required')),
nationalCode: Yup.string()
.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) {
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])
},
})
},
})
console.log(formik.errors);
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
+39
View File
@@ -0,0 +1,39 @@
import { useMutation } from '@tanstack/react-query';
import * as api from '../service/AuthService'
import { 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),
});
};
+39
View File
@@ -0,0 +1,39 @@
import axios from "../../../config/axios";
import {
CheckHasAccountPhoneType,
CheckHasAccountType,
LoginWithOtpType,
LoginWithPasswordType,
OtpVerifyType,
RegisterType,
} from "../types/AuthTypes";
export const loginWithPassword = async (params: LoginWithPasswordType) => {
const { data } = await axios.post(`/auth/login/password/admin`, 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/admin`, 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;
};
+4
View File
@@ -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 });
+38
View File
@@ -1,3 +1,5 @@
import { XOR } from "../../../helpers/types";
export type LoginType = {
phone_email: string;
};
@@ -8,6 +10,42 @@ 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;
};
export type CheckHasAccountPhoneType = {
phone: string;
};
export type RegisterType = {
phone: string;
firstName: string;
lastName: string;
birthDate: string;
nationalCode: string;
password: string;
code: number;
};