From 433bd65cb06915011ac55d757cd7b5a1f355f8ad Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 7 Feb 2026 12:01:28 +0330 Subject: [PATCH] forgot password --- src/langs/fa.json | 9 ++- src/pages/auth/Forgot.tsx | 42 +++++++++++ src/pages/auth/components/ForgotStep1.tsx | 69 ++++++++++++++++++ src/pages/auth/components/ForgotStep2.tsx | 89 +++++++++++++++++++++++ src/pages/auth/components/LoginStep1.tsx | 9 ++- src/pages/auth/hooks/useAuthData.tsx | 12 +++ src/pages/auth/service/AuthService.ts | 14 +++- src/pages/auth/types/AuthTypes.ts | 10 +++ src/router/Auth.tsx | 2 + 9 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 src/pages/auth/Forgot.tsx create mode 100644 src/pages/auth/components/ForgotStep1.tsx create mode 100644 src/pages/auth/components/ForgotStep2.tsx diff --git a/src/langs/fa.json b/src/langs/fa.json index 65c25a9..f36745a 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -42,12 +42,17 @@ "enter_mobile_number": "شماره موبایل خود را وارد کنید", "try_again": "ارسال مجدد", "toast_resend_code": "کد جدیدی برای شما ارسال شد", - "old_version": "نسخه قدیمی داشبورد" + "old_version": "نسخه قدیمی داشبورد", + "enter_info_forgot": "ایمیل خود را وارد کنید", + "welcome_forgot": "فراموشی رمز عبور", + "email_code": "کد تایید", + "newPassword": "پسورد جدید" }, "errors": { "required": "این فیلد اجباری می باشد", "password_not_match": "رمز عبور با تکرار آن مطابقت ندارد", - "is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد" + "is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد", + "format_email_error": "فرمت ایمیل اشتباه است" }, "description": "توضیحات", "sidebar": { diff --git a/src/pages/auth/Forgot.tsx b/src/pages/auth/Forgot.tsx new file mode 100644 index 0000000..8df48f8 --- /dev/null +++ b/src/pages/auth/Forgot.tsx @@ -0,0 +1,42 @@ +import { useState, type FC } from 'react' +import LogoImage from '../../assets/images/logo.svg' +import LogoSmallImage from '../../assets/images/logo-small.svg' +import ForgotStep1 from './components/ForgotStep1' +import ForgotStep2 from './components/ForgotStep2' + + +const Forgot: FC = () => { + + const [step, setStep] = useState(1) + const [email, setEmail] = useState('') + + return ( +
+
+
+
+ +
+ + + { + step === 1 ? + + : + + } +
+ + +
+
+ +
+ +
+
+
+ ) +} + +export default Forgot \ No newline at end of file diff --git a/src/pages/auth/components/ForgotStep1.tsx b/src/pages/auth/components/ForgotStep1.tsx new file mode 100644 index 0000000..15c10e6 --- /dev/null +++ b/src/pages/auth/components/ForgotStep1.tsx @@ -0,0 +1,69 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import Input from '../../../components/Input' +import { useFormik } from 'formik' +import { ForgotPasswordType } from '../types/AuthTypes' +import * as Yup from 'yup' +import Button from '../../../components/Button' +import { useForgotPassword } from '../hooks/useAuthData' +import { toast } from '../../../components/Toast' +import { ErrorType } from '../../../helpers/types' + +type Props = { + changeStep: (value: number) => void, + changeEmail: (value: string) => void, +} + +const ForgotStep1: FC = ({ changeStep, changeEmail }) => { + + const { t } = useTranslation('global') + const { mutate, isPending } = useForgotPassword() + const formik = useFormik({ + initialValues: { + email: '' + }, + validationSchema: Yup.object({ + email: Yup.string().email(t('errors.format_email_error')).required(t('errors.required')) + }), + onSubmit: (values) => { + mutate(values, { + onSuccess: (data) => { + toast(data?.data?.message) + changeEmail(values.email) + changeStep(2) + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error.message[0]) + } + }) + } + }) + + return ( +
+

+ {t('auth.welcome_forgot')} +

+

+ {t('auth.enter_info_forgot')} +

+ +
+ +
+ +
+ ) +} + +export default ForgotStep1 \ No newline at end of file diff --git a/src/pages/auth/components/ForgotStep2.tsx b/src/pages/auth/components/ForgotStep2.tsx new file mode 100644 index 0000000..bcb26d4 --- /dev/null +++ b/src/pages/auth/components/ForgotStep2.tsx @@ -0,0 +1,89 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import Input from '../../../components/Input' +import { useFormik } from 'formik' +import { ResetPasswordType } from '../types/AuthTypes' +import * as Yup from 'yup' +import Button from '../../../components/Button' +import { useResetPassword } from '../hooks/useAuthData' +import { toast } from '../../../components/Toast' +import { ErrorType } from '../../../helpers/types' +import { Pages } from '../../../config/Pages' + +type Props = { + changeStep: (value: number) => void, + email: string, +} + +const ForgotStep2: FC = ({ email }) => { + + const { t } = useTranslation('global') + const { mutate, isPending } = useResetPassword() + const formik = useFormik({ + initialValues: { + email: email, + code: '', + newPassword: '' + }, + validationSchema: Yup.object({ + email: Yup.string().email(t('errors.format_email_error')).required(t('errors.required')), + code: Yup.string().required(t('errors.required')), + newPassword: Yup.string().required(t('errors.required')), + }), + onSubmit: (values) => { + mutate(values, { + onSuccess: (data) => { + toast(data?.data?.message) + window.location.href = Pages.auth.login + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error.message[0]) + } + }) + } + }) + + return ( +
+

+ {t('auth.welcome_forgot')} +

+

+ {t('auth.enter_info_forgot')} +

+ +
+ +
+ +
+
+ +
+
+ +
+ ) +} + +export default ForgotStep2 \ No newline at end of file diff --git a/src/pages/auth/components/LoginStep1.tsx b/src/pages/auth/components/LoginStep1.tsx index 8e62495..2eac706 100644 --- a/src/pages/auth/components/LoginStep1.tsx +++ b/src/pages/auth/components/LoginStep1.tsx @@ -11,6 +11,9 @@ import { isEmail } from '../../../config/func' import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData' import { ErrorType } from '../../../helpers/types' import { toast } from '../../../components/Toast' +import { ArrowLeft } from 'iconsax-react' +import { Link } from 'react-router-dom' +import { Pages } from '../../../config/Pages' const LoginStep1: FC = () => { const { t } = useTranslation('global') @@ -100,12 +103,12 @@ const LoginStep1: FC = () => { */} - {/* +
- {t('auth.old_version')} + {t('auth.forgot_password')}
-
*/} + ) } diff --git a/src/pages/auth/hooks/useAuthData.tsx b/src/pages/auth/hooks/useAuthData.tsx index d9db212..716aa82 100644 --- a/src/pages/auth/hooks/useAuthData.tsx +++ b/src/pages/auth/hooks/useAuthData.tsx @@ -42,4 +42,16 @@ export const useLogout = () => { return useMutation({ mutationFn: (_) => api.logout(), }); +}; + +export const useForgotPassword = () => { + return useMutation({ + mutationFn: api.forgotPassword, + }); +}; + +export const useResetPassword = () => { + return useMutation({ + mutationFn: api.resetPassword, + }); }; \ No newline at end of file diff --git a/src/pages/auth/service/AuthService.ts b/src/pages/auth/service/AuthService.ts index e176ba9..fc9025b 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) => { @@ -29,7 +31,7 @@ export const checkHasAccount = async (params: CheckHasAccountType) => { }; export const checkHasAccountRegister = async ( - params: CheckHasAccountPhoneType + params: CheckHasAccountPhoneType, ) => { const { data } = await axios.post(`/auth/register/initiate`, params); return data; @@ -48,3 +50,13 @@ export const logout = async () => { const { data } = await axios.post(`/auth/logout`); 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 85eb78a..d4a7057 100644 --- a/src/pages/auth/types/AuthTypes.ts +++ b/src/pages/auth/types/AuthTypes.ts @@ -48,3 +48,13 @@ export type RegisterType = { export type RefreshTokenType = { refreshToken: string; }; + +export type ForgotPasswordType = { + email: string; +}; + +export type ResetPasswordType = { + newPassword: string; + email: string; + code: string; +}; diff --git a/src/router/Auth.tsx b/src/router/Auth.tsx index f8c893c..3c4d31d 100644 --- a/src/router/Auth.tsx +++ b/src/router/Auth.tsx @@ -2,6 +2,7 @@ import { FC } from 'react' import { Route, Routes } from 'react-router-dom' import { Pages } from '../config/Pages' import Login from '../pages/auth/Login' +import Forgot from '../pages/auth/Forgot' // import Register from '../pages/auth/Register' const AuthRouter: FC = () => { @@ -9,6 +10,7 @@ const AuthRouter: FC = () => { } /> } /> + } /> ) }