category service
This commit is contained in:
@@ -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
@@ -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'>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
@@ -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),
|
||||
});
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,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;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Select from '../../components/Select'
|
||||
import Input from '../../components/Input'
|
||||
import Td from '../../components/Td'
|
||||
import SwitchComponent from '../../components/Switch'
|
||||
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import CreateCategory from './components/CreateCategory'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { ServiceCategoryType } from './types/ServiceTypes'
|
||||
import { useGetAllCategory } from './hooks/useServiceData'
|
||||
import StatusCategory from './components/StatusCategory'
|
||||
import Pagination from '../../components/Pagination'
|
||||
|
||||
|
||||
const Category: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const [page, setPage] = useState<number>(1)
|
||||
const [isActive, setIsActive] = useState<'active' | 'deactive' | ''>('')
|
||||
const getCategory = useGetAllCategory(page, search, isActive)
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
@@ -24,11 +31,12 @@ const Category: FC = () => {
|
||||
<div className='w-32'>
|
||||
<Select
|
||||
items={[
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' },
|
||||
{ label: t('all'), value: '' },
|
||||
{ label: t('service.active'), value: 'active' },
|
||||
{ label: t('service.deactive'), value: 'deactive' },
|
||||
]}
|
||||
placeholder={t('service.category')}
|
||||
placeholder={t('status')}
|
||||
onChange={(e) => setIsActive(e.target.value as 'active' | 'deactive' | '')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -37,98 +45,70 @@ const Category: FC = () => {
|
||||
variant='search'
|
||||
placeholder={t('search')}
|
||||
className='bg-white'
|
||||
onChangeSearchFinal={(value) => setSearch(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 mt-8 bg-white py-2 xl:px-10 px-5 rounded-3xl'>
|
||||
<div className='relative overflow-x-auto rounded-3xl w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text='' />
|
||||
<Td text={t('service.title')} />
|
||||
<Td text={t('service.parent_category')} />
|
||||
<Td text={t('service.count_sub_category')} />
|
||||
<Td text={t('service.count_service')} />
|
||||
<Td text={t('ticket.status')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className='tr'>
|
||||
<Td text=''>
|
||||
<div className='size-10 rounded-lg bg-red-100'></div>
|
||||
</Td>
|
||||
<Td text={'کسب و کار'} />
|
||||
<Td text={'-'} />
|
||||
<Td text={'2'} />
|
||||
<Td text={'14'} />
|
||||
<Td text={''}>
|
||||
<SwitchComponent
|
||||
active
|
||||
onChange={() => null}
|
||||
/>
|
||||
</Td>
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{
|
||||
getCategory.isPending ?
|
||||
<div className='mt-4'>
|
||||
<PageLoading />
|
||||
</div>
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text='' />
|
||||
<Td text={t('service.title')} />
|
||||
<Td text={t('service.parent_category')} />
|
||||
<Td text={t('service.count_sub_category')} />
|
||||
<Td text={t('service.count_service')} />
|
||||
<Td text={t('ticket.status')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
getCategory.data?.data?.categories?.map((item: ServiceCategoryType) => {
|
||||
return (
|
||||
<tr key={item.id} className='tr'>
|
||||
<Td text=''>
|
||||
<img src={item.icon} className='size-10 rounded-lg' />
|
||||
</Td>
|
||||
<Td text={item.title} />
|
||||
<Td text={item.parent ? item.parent.title : '-'} />
|
||||
<Td text={item.childrenCount + ''} />
|
||||
<Td text={item.servicesCount + ''} />
|
||||
<Td text={''}>
|
||||
<StatusCategory
|
||||
defaultActive={item.isActive}
|
||||
id={item.id}
|
||||
/>
|
||||
</Td>
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className='flex justify-end pb-3'>
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={getCategory.data?.data?.pager?.totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-white p-8 w-sidebar hidden xl:block rounded-3xl overflow-hidden relative'>
|
||||
<div>
|
||||
{t('service.add_category')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex justify-between'>
|
||||
<div className='text-sm'>
|
||||
{t('service.status_category')}
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<SwitchComponent active onChange={() => { }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('service.title_category')}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<Select
|
||||
label={t('service.category_parent_2')}
|
||||
items={[
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' },
|
||||
]}
|
||||
placeholder={t('select')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<UploadBoxDraggble
|
||||
label={t('service.icon_serice_category')}
|
||||
onChange={() => null}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 justify-end items-end mt-8'>
|
||||
<Button
|
||||
className='w-fit px-5'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<TickCircle
|
||||
className='size-5'
|
||||
color='white'
|
||||
/>
|
||||
<div>{t('save')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<CreateCategory />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import Input from '../../../components/Input'
|
||||
import Select from '../../../components/Select'
|
||||
import UploadBoxDraggble from '../../../components/UploadBoxDraggble'
|
||||
import Button from '../../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { useCreateServiceCategory, useGetCategoryParents } from '../hooks/useServiceData'
|
||||
import { CreateServiceCategoryType, ServiceCategoryType } from '../types/ServiceTypes'
|
||||
import * as Yup from 'yup'
|
||||
import { useFormik } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
const CreateCategory: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const getCategory = useGetCategoryParents()
|
||||
const createCategory = useCreateServiceCategory()
|
||||
|
||||
const formik = useFormik<CreateServiceCategoryType>({
|
||||
initialValues: {
|
||||
title: '',
|
||||
isActive: true,
|
||||
parentId: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
title: Yup.string().required(t('errors.required')),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
const params = {
|
||||
...values,
|
||||
icon: 'https://picsum.photos/200/300',
|
||||
parentId: values.parentId ? values.parentId : undefined
|
||||
}
|
||||
createCategory.mutate(params, {
|
||||
onSuccess: () => {
|
||||
formik.resetForm()
|
||||
getCategory.refetch()
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='bg-white p-8 w-sidebar hidden xl:block rounded-3xl overflow-hidden relative'>
|
||||
<div>
|
||||
{t('service.add_category')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex justify-between'>
|
||||
<div className='text-sm'>
|
||||
{t('service.status_category')}
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<SwitchComponent
|
||||
active={formik.values.isActive}
|
||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('service.title_category')}
|
||||
value={formik.values.title}
|
||||
name='title'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
getCategory.data &&
|
||||
<div className='mt-6'>
|
||||
<Select
|
||||
label={t('service.category_parent_2')}
|
||||
items={getCategory.data?.data?.categories?.map((item: ServiceCategoryType) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.id
|
||||
}
|
||||
})}
|
||||
placeholder={t('select')}
|
||||
name='parentId'
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.parentId && formik.errors.parentId ? formik.errors.parentId : ''}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div className='mt-6'>
|
||||
<UploadBoxDraggble
|
||||
label={t('service.icon_serice_category')}
|
||||
onChange={() => null}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 justify-end items-end mt-8'>
|
||||
<Button
|
||||
className='w-fit px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createCategory.isPending}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<TickCircle
|
||||
className='size-5'
|
||||
color='white'
|
||||
/>
|
||||
<div>{t('save')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCategory
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FC, useState } from 'react'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import { useChangeStatusCategory } from '../hooks/useServiceData'
|
||||
|
||||
type Props = {
|
||||
defaultActive: boolean,
|
||||
id: string
|
||||
}
|
||||
|
||||
const StatusCategory: FC<Props> = (props: Props) => {
|
||||
|
||||
const [isActive, setIsActive] = useState<boolean>(props.defaultActive)
|
||||
const toggleStatus = useChangeStatusCategory()
|
||||
|
||||
const handleChange = (value: boolean) => {
|
||||
setIsActive(value)
|
||||
toggleStatus.mutate({ id: props.id })
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchComponent
|
||||
active={isActive}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusCategory
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/ServiceService";
|
||||
import {
|
||||
CreateServiceCategoryType,
|
||||
ToggleStatusCategoryType,
|
||||
} from "../types/ServiceTypes";
|
||||
|
||||
export const useGetCategoryParents = () => {
|
||||
return useQuery({
|
||||
queryKey: ["service-category"],
|
||||
queryFn: api.getCategoryParent,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAllCategory = (
|
||||
page: number,
|
||||
search: string,
|
||||
isActive: "active" | "deactive" | ""
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ["service-category-all", page, search, isActive],
|
||||
queryFn: () => api.getAllCategory(page, search, isActive),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateServiceCategory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (variables: CreateServiceCategoryType) =>
|
||||
api.createServiceCategory(variables),
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries({
|
||||
queryKey: ["service-category-all"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useChangeStatusCategory = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: ToggleStatusCategoryType) =>
|
||||
api.changeStatusCategory(variables),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import axios from "../../../config/axios";
|
||||
import {
|
||||
CreateServiceCategoryType,
|
||||
ToggleStatusCategoryType,
|
||||
} from "../types/ServiceTypes";
|
||||
|
||||
export const getCategoryParent = async () => {
|
||||
const { data } = await axios.get(`/services/category`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getAllCategory = async (
|
||||
page: number,
|
||||
search: string,
|
||||
isActive: "active" | "deactive" | ""
|
||||
) => {
|
||||
let query = `?page=${page}&q=${search}`;
|
||||
if (isActive) {
|
||||
query += `&isActive=${isActive === "active" ? "1" : "0"}`;
|
||||
}
|
||||
const { data } = await axios.get(`/services/category-list${query}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createServiceCategory = async (
|
||||
params: CreateServiceCategoryType
|
||||
) => {
|
||||
const { data } = await axios.post(`/services/category`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const changeStatusCategory = async (
|
||||
params: ToggleStatusCategoryType
|
||||
) => {
|
||||
const { data } = await axios.post(
|
||||
`/services/category/toggle-status/${params.id}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
export type ServiceCategoryType = {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
icon: string;
|
||||
|
||||
isActive: boolean;
|
||||
|
||||
parent: ServiceCategoryType;
|
||||
|
||||
children: ServiceCategoryType[];
|
||||
|
||||
parentId: string;
|
||||
childrenCount: number;
|
||||
servicesCount: number;
|
||||
};
|
||||
|
||||
export type CreateServiceCategoryType = {
|
||||
title: string;
|
||||
icon?: string;
|
||||
isActive: boolean;
|
||||
parentId?: string;
|
||||
};
|
||||
|
||||
export type ToggleStatusCategoryType = {
|
||||
id: string;
|
||||
};
|
||||
Reference in New Issue
Block a user