This commit is contained in:
@@ -35,6 +35,14 @@
|
|||||||
"enter_password_step3": "رمز عبور را وارد کنید",
|
"enter_password_step3": "رمز عبور را وارد کنید",
|
||||||
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
||||||
"forgot_password": "فراموشی رمز عبور",
|
"forgot_password": "فراموشی رمز عبور",
|
||||||
|
"forgot_password_description": "ایمیل خود را وارد کنید تا کد بازیابی برای شما ارسال شود.",
|
||||||
|
"reset_password": "بازیابی رمز عبور",
|
||||||
|
"new_password": "رمز عبور جدید",
|
||||||
|
"confirm_password": "تکرار رمز عبور",
|
||||||
|
"password_mismatch": "رمز عبور و تکرار آن یکسان نیستند",
|
||||||
|
"change_email": "تغییر ایمیل",
|
||||||
|
"resend_code": "ارسال مجدد کد",
|
||||||
|
"back_to_login": "بازگشت به صفحه ورود",
|
||||||
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
|
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
|
||||||
"otp_sent": "رمز یک بار مصرف برای شما ارسال شد"
|
"otp_sent": "رمز یک بار مصرف برای شما ارسال شد"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { FC, useState } from 'react'
|
||||||
|
import LogoImage from '../../assets/images/logo.svg'
|
||||||
|
import LogoSmallImage from '../../assets/images/logo-small.svg'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ForgotPasswordStep1 from './components/ForgotPasswordStep1'
|
||||||
|
import ForgotPasswordStep2 from './components/ForgotPasswordStep2'
|
||||||
|
|
||||||
|
const ForgotPassword: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [step, setStep] = useState(1)
|
||||||
|
|
||||||
|
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'>
|
||||||
|
<img src={LogoSmallImage} className='w-8' />
|
||||||
|
|
||||||
|
<div className='flex flex-1 flex-col h-full justify-center'>
|
||||||
|
{
|
||||||
|
step === 1 ?
|
||||||
|
<ForgotPasswordStep1 onSuccess={() => setStep(2)} />
|
||||||
|
:
|
||||||
|
<ForgotPasswordStep2 onBack={() => setStep(1)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className='mt-6 flex justify-center gap-2 items-center text-sm'>
|
||||||
|
<p className='text-description'>
|
||||||
|
{t('auth.back_to_login')}
|
||||||
|
</p>
|
||||||
|
<Link to={Pages.auth.login}>
|
||||||
|
<div>{t('auth.login')}</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</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 ForgotPassword
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ForgotPasswordType } 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 { useForgotPassword } from '../hooks/useAuthData'
|
||||||
|
import { toast } from '../../../components/Toast';
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { isEmail } from '../../../config/func'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSuccess: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordStep1: FC<Props> = ({ onSuccess }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { email, setEmail } = useAuthStore()
|
||||||
|
const forgotPassword = useForgotPassword()
|
||||||
|
|
||||||
|
const formik = useFormik<ForgotPasswordType>({
|
||||||
|
initialValues: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
email: Yup.string()
|
||||||
|
.required(t('errors.required'))
|
||||||
|
.test('is-email', t('errors.required'), (value) => !!value && isEmail(value)),
|
||||||
|
}),
|
||||||
|
onSubmit(values) {
|
||||||
|
setEmail(values.email)
|
||||||
|
forgotPassword.mutate({ email: values.email }, {
|
||||||
|
onSuccess() {
|
||||||
|
toast(t('auth.otp_sent'), 'success')
|
||||||
|
onSuccess()
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='text-2xl font-bold'>
|
||||||
|
{t('auth.forgot_password')}
|
||||||
|
</h2>
|
||||||
|
<p className='text-description text-sm mt-2'>
|
||||||
|
{t('auth.forgot_password_description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.email')}
|
||||||
|
placeholder={t('auth.enter_email')}
|
||||||
|
type='text'
|
||||||
|
dir='ltr'
|
||||||
|
name='email'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.email}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
formik.touched.email && formik.errors.email &&
|
||||||
|
<Error
|
||||||
|
errorText={formik.errors.email}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.next')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={forgotPassword.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPasswordStep1
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ResetPasswordType } from '../types/AuthTypes'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Error from '../../../components/Error'
|
||||||
|
import OTPInput from 'react-otp-input'
|
||||||
|
import { useCountDown } from '../../../hooks/useCountDown'
|
||||||
|
import { Pages } from '../../../config/Pages'
|
||||||
|
import { useForgotPassword, useResetPassword } from '../hooks/useAuthData'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { toast } from '../../../components/Toast';
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordStep2: FC<Props> = ({ onBack }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { email } = useAuthStore()
|
||||||
|
const { value, reset } = useCountDown(2, true)
|
||||||
|
const resetPassword = useResetPassword()
|
||||||
|
const forgotPassword = useForgotPassword()
|
||||||
|
|
||||||
|
const formik = useFormik<ResetPasswordType & { confirmPassword: string }>({
|
||||||
|
initialValues: {
|
||||||
|
email: email,
|
||||||
|
code: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
code: Yup.string()
|
||||||
|
.required(t('errors.required')),
|
||||||
|
newPassword: Yup.string()
|
||||||
|
.required(t('errors.required')),
|
||||||
|
confirmPassword: Yup.string()
|
||||||
|
.required(t('errors.required'))
|
||||||
|
.oneOf([Yup.ref('newPassword')], t('auth.password_mismatch')),
|
||||||
|
}),
|
||||||
|
onSubmit(values) {
|
||||||
|
resetPassword.mutate({
|
||||||
|
email,
|
||||||
|
code: values.code,
|
||||||
|
newPassword: values.newPassword,
|
||||||
|
}, {
|
||||||
|
onSuccess() {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
navigate(Pages.auth.login)
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleResend = () => {
|
||||||
|
forgotPassword.mutate({ email }, {
|
||||||
|
onSuccess() {
|
||||||
|
reset()
|
||||||
|
toast(t('auth.otp_sent'), 'success')
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='lg:text-2xl font-bold'>
|
||||||
|
{t('auth.reset_password')}
|
||||||
|
</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 flex-wrap'>
|
||||||
|
<div>{t('auth.verfify_code_text_1')}</div>
|
||||||
|
<div>{email}</div>
|
||||||
|
<div>{t('auth.verfify_code_text_2')}</div>
|
||||||
|
</div>
|
||||||
|
<div onClick={onBack} className='text-black cursor-pointer'>
|
||||||
|
{t('auth.change_email')}
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{t('auth.verify_code')}
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||||
|
<OTPInput
|
||||||
|
shouldAutoFocus
|
||||||
|
value={formik.values.code}
|
||||||
|
numInputs={5}
|
||||||
|
inputType='tel'
|
||||||
|
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-between items-center'>
|
||||||
|
<div
|
||||||
|
onClick={handleResend}
|
||||||
|
className='text-xs text-black cursor-pointer'
|
||||||
|
>
|
||||||
|
{t('auth.resend_code')}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-1 text-description text-xs'>
|
||||||
|
<div>{value}</div>
|
||||||
|
<div>{t('auth.counter_otp')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.new_password')}
|
||||||
|
type='password'
|
||||||
|
className='text-right'
|
||||||
|
name='newPassword'
|
||||||
|
dir='ltr'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.newPassword}
|
||||||
|
error_text={formik.touched.newPassword && formik.errors.newPassword ? formik.errors.newPassword : ''}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
formik.touched.newPassword && formik.errors.newPassword &&
|
||||||
|
<Error errorText={formik.errors.newPassword} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.confirm_password')}
|
||||||
|
type='password'
|
||||||
|
className='text-right'
|
||||||
|
name='confirmPassword'
|
||||||
|
dir='ltr'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.confirmPassword}
|
||||||
|
error_text={formik.touched.confirmPassword && formik.errors.confirmPassword ? formik.errors.confirmPassword : ''}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
formik.touched.confirmPassword && formik.errors.confirmPassword &&
|
||||||
|
<Error errorText={formik.errors.confirmPassword} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.reset_password')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={resetPassword.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPasswordStep2
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import * as api from '../service/AuthService'
|
import * as api from '../service/AuthService'
|
||||||
import { CheckHasAccountPhoneType, CheckHasAccountType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType, RegisterType } from '../types/AuthTypes';
|
import { CheckHasAccountPhoneType, CheckHasAccountType, ForgotPasswordType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType, RegisterType, ResetPasswordType } from '../types/AuthTypes';
|
||||||
|
|
||||||
export const useLoginWithPassword = () => {
|
export const useLoginWithPassword = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@@ -37,3 +37,15 @@ export const useRegister = () => {
|
|||||||
mutationFn: (variables: RegisterType) => api.register(variables),
|
mutationFn: (variables: RegisterType) => api.register(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useForgotPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ForgotPasswordType) => api.forgotPassword(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResetPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ResetPasswordType) => api.resetPassword(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,11 +2,13 @@ import axios from "../../../config/axios";
|
|||||||
import {
|
import {
|
||||||
CheckHasAccountPhoneType,
|
CheckHasAccountPhoneType,
|
||||||
CheckHasAccountType,
|
CheckHasAccountType,
|
||||||
|
ForgotPasswordType,
|
||||||
LoginWithOtpType,
|
LoginWithOtpType,
|
||||||
LoginWithPasswordType,
|
LoginWithPasswordType,
|
||||||
OtpVerifyType,
|
OtpVerifyType,
|
||||||
RefreshTokenType,
|
RefreshTokenType,
|
||||||
RegisterType,
|
RegisterType,
|
||||||
|
ResetPasswordType,
|
||||||
} from "../types/AuthTypes";
|
} from "../types/AuthTypes";
|
||||||
|
|
||||||
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
||||||
@@ -43,3 +45,13 @@ export const refreshToken = async (params: RefreshTokenType) => {
|
|||||||
const { data } = await axios.post(`/auth/refresh`, params);
|
const { data } = await axios.post(`/auth/refresh`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const forgotPassword = async (params: ForgotPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/forgot-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (params: ResetPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/reset-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -52,3 +52,13 @@ export type RegisterType = {
|
|||||||
export type RefreshTokenType = {
|
export type RefreshTokenType = {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ForgotPasswordType = {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResetPasswordType = {
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
newPassword: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { FC } from 'react'
|
|||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
import Login from '../pages/auth/Login'
|
import Login from '../pages/auth/Login'
|
||||||
|
import ForgotPassword from '../pages/auth/ForgotPassword'
|
||||||
|
|
||||||
const AuthRouter: FC = () => {
|
const AuthRouter: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Pages.auth.login} element={<Login />} />
|
<Route path={Pages.auth.login} element={<Login />} />
|
||||||
|
<Route path={Pages.auth.forgotPassword} element={<ForgotPassword />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user