diff --git a/src/langs/fa.json b/src/langs/fa.json
index ff1909d..9817035 100644
--- a/src/langs/fa.json
+++ b/src/langs/fa.json
@@ -35,6 +35,14 @@
"enter_password_step3": "رمز عبور را وارد کنید",
"enter_your_password": "رمز عبور خود را وارد کنید.",
"forgot_password": "فراموشی رمز عبور",
+ "forgot_password_description": "ایمیل خود را وارد کنید تا کد بازیابی برای شما ارسال شود.",
+ "reset_password": "بازیابی رمز عبور",
+ "new_password": "رمز عبور جدید",
+ "confirm_password": "تکرار رمز عبور",
+ "password_mismatch": "رمز عبور و تکرار آن یکسان نیستند",
+ "change_email": "تغییر ایمیل",
+ "resend_code": "ارسال مجدد کد",
+ "back_to_login": "بازگشت به صفحه ورود",
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
"otp_sent": "رمز یک بار مصرف برای شما ارسال شد"
},
diff --git a/src/pages/auth/ForgotPassword.tsx b/src/pages/auth/ForgotPassword.tsx
new file mode 100644
index 0000000..1a25783
--- /dev/null
+++ b/src/pages/auth/ForgotPassword.tsx
@@ -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 (
+
+
+
+
+

+
+
+ {
+ step === 1 ?
+
setStep(2)} />
+ :
+ setStep(1)} />
+ }
+
+
+
+ {t('auth.back_to_login')}
+
+
+
{t('auth.login')}
+
+
+
+
+
+
+
+

+
+
+
+ )
+}
+
+export default ForgotPassword
diff --git a/src/pages/auth/components/ForgotPasswordStep1.tsx b/src/pages/auth/components/ForgotPasswordStep1.tsx
new file mode 100644
index 0000000..2dcfdb6
--- /dev/null
+++ b/src/pages/auth/components/ForgotPasswordStep1.tsx
@@ -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 = ({ onSuccess }) => {
+
+ const { t } = useTranslation('global')
+ const { email, setEmail } = useAuthStore()
+ const forgotPassword = useForgotPassword()
+
+ const formik = useFormik({
+ 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 (
+
+
+
+ {t('auth.forgot_password')}
+
+
+ {t('auth.forgot_password_description')}
+
+
+
+
+
+
+ {
+ formik.touched.email && formik.errors.email &&
+
+ }
+
+
+
+ )
+}
+
+export default ForgotPasswordStep1
diff --git a/src/pages/auth/components/ForgotPasswordStep2.tsx b/src/pages/auth/components/ForgotPasswordStep2.tsx
new file mode 100644
index 0000000..3dfe4f0
--- /dev/null
+++ b/src/pages/auth/components/ForgotPasswordStep2.tsx
@@ -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 = ({ 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({
+ 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 (
+
+
+
+ {t('auth.reset_password')}
+
+
+
+
{t('auth.verfify_code_text_1')}
+
{email}
+
{t('auth.verfify_code_text_2')}
+
+
+ {t('auth.change_email')}
+
+
+
+
+
+
+ {t('auth.verify_code')}
+
+
+ formik.setFieldValue('code', otp)}
+ renderInput={(props) => }
+ />
+
+
+ {
+ formik.touched.code && formik.errors.code &&
+
{formik.errors.code}
+ }
+
+
+
+ {t('auth.resend_code')}
+
+
+
{value}
+
{t('auth.counter_otp')}
+
+
+
+
+
+
+ {
+ formik.touched.newPassword && formik.errors.newPassword &&
+
+ }
+
+
+
+
+ {
+ formik.touched.confirmPassword && formik.errors.confirmPassword &&
+
+ }
+
+
+
+ )
+}
+
+export default ForgotPasswordStep2
diff --git a/src/pages/auth/hooks/useAuthData.tsx b/src/pages/auth/hooks/useAuthData.tsx
index 0c480e9..6ea1549 100644
--- a/src/pages/auth/hooks/useAuthData.tsx
+++ b/src/pages/auth/hooks/useAuthData.tsx
@@ -1,6 +1,6 @@
import { useMutation } from '@tanstack/react-query';
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 = () => {
return useMutation({
@@ -36,4 +36,16 @@ export const useRegister = () => {
return useMutation({
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),
+ });
};
\ No newline at end of file
diff --git a/src/pages/auth/service/AuthService.ts b/src/pages/auth/service/AuthService.ts
index 41f9f20..d25bdbc 100644
--- a/src/pages/auth/service/AuthService.ts
+++ b/src/pages/auth/service/AuthService.ts
@@ -2,11 +2,13 @@ import axios from "../../../config/axios";
import {
CheckHasAccountPhoneType,
CheckHasAccountType,
+ ForgotPasswordType,
LoginWithOtpType,
LoginWithPasswordType,
OtpVerifyType,
RefreshTokenType,
RegisterType,
+ ResetPasswordType,
} from "../types/AuthTypes";
export const loginWithPassword = async (params: LoginWithPasswordType) => {
@@ -43,3 +45,13 @@ export const refreshToken = async (params: RefreshTokenType) => {
const { data } = await axios.post(`/auth/refresh`, params);
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;
+};
diff --git a/src/pages/auth/types/AuthTypes.ts b/src/pages/auth/types/AuthTypes.ts
index a9c1019..8259d32 100644
--- a/src/pages/auth/types/AuthTypes.ts
+++ b/src/pages/auth/types/AuthTypes.ts
@@ -52,3 +52,13 @@ export type RegisterType = {
export type RefreshTokenType = {
refreshToken: string;
};
+
+export type ForgotPasswordType = {
+ email: string;
+};
+
+export type ResetPasswordType = {
+ email: string;
+ code: string;
+ newPassword: string;
+};
diff --git a/src/router/Auth.tsx b/src/router/Auth.tsx
index 12f7a30..b2c4b75 100644
--- a/src/router/Auth.tsx
+++ b/src/router/Auth.tsx
@@ -2,13 +2,15 @@ import { FC } from 'react'
import { Route, Routes } from 'react-router-dom'
import { Pages } from '../config/Pages'
import Login from '../pages/auth/Login'
+import ForgotPassword from '../pages/auth/ForgotPassword'
const AuthRouter: FC = () => {
return (
} />
+ } />
)
}
-export default AuthRouter
\ No newline at end of file
+export default AuthRouter