forget pass apis added

This commit is contained in:
2025-08-10 00:27:37 +03:30
parent cd86254ce1
commit 08bfe5c979
10 changed files with 150 additions and 38 deletions

View File

@@ -3,6 +3,7 @@ import './App.css';
import { LanguageManager } from './components/LanguageManager'; import { LanguageManager } from './components/LanguageManager';
import { AuthenticationPage } from './features/authorization/routes/AuthenticationPage'; import { AuthenticationPage } from './features/authorization/routes/AuthenticationPage';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router';
import { ForgetPasswordPage } from './features/authorization/routes/ForgetPasswordPage';
function App() { function App() {
return ( return (
@@ -13,6 +14,7 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<Navigate to="auth" />} /> <Route path="/" element={<Navigate to="auth" />} />
<Route path="/auth" element={<AuthenticationPage />} /> <Route path="/auth" element={<AuthenticationPage />} />
<Route path="/forget-password" element={<ForgetPasswordPage />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
, ,

View File

@@ -77,7 +77,7 @@ export const sendForgetPassCode = async (body: SendForgetPassCodeRequest) => {
return fetchRequest<ApiResponse>('User/SendForgetPassCode', body); return fetchRequest<ApiResponse>('User/SendForgetPassCode', body);
}; };
export const ConfirmForgetPassCode = async ( export const confirmForgetPassCode = async (
body: ConfirmForgetPassCodeRequest, body: ConfirmForgetPassCodeRequest,
) => { ) => {
return fetchRequest<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body); return fetchRequest<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body);

View File

@@ -7,7 +7,7 @@ import { CompleteSignUp } from './CompleteSignUp';
import { EnterPasswordForm } from './EnterPasswordForm'; import { EnterPasswordForm } from './EnterPasswordForm';
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI'; import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
import { UserStatus } from '../../types/userTypes'; import { UserStatus } from '../../types/userTypes';
import type { CountryCode } from '@/types/commonTypes'; import type { CountryCode, GUID } from '@/types/commonTypes';
import { VerifyPhoneNumber } from './VerifyPhoneNumber'; import { VerifyPhoneNumber } from './VerifyPhoneNumber';
export const AuthenticationSteps = (): JSX.Element => { export const AuthenticationSteps = (): JSX.Element => {
@@ -48,7 +48,11 @@ export const AuthenticationSteps = (): JSX.Element => {
} }
}; };
const handleOTPVerfied = (registeredWithoutPhoneNumber: boolean = false) => { const handleOTPVerfied = (
registeredWithoutPhoneNumber: boolean = false,
userId: GUID,
) => {
localStorage.setItem('userID', userId);
// if (registeredWithoutPhoneNumber) { // if (registeredWithoutPhoneNumber) {
// setCurrentStep('addPhoneNumber'); // setCurrentStep('addPhoneNumber');
// } // }

View File

@@ -13,7 +13,7 @@ import {
sendEmailOtp, sendEmailOtp,
sendSmsOtp, sendSmsOtp,
} from '../../api/authorizationAPI'; } from '../../api/authorizationAPI';
import type { CountryCode } from '@/types/commonTypes'; import type { CountryCode, GUID } from '@/types/commonTypes';
interface OtpVerifyFormProps { interface OtpVerifyFormProps {
value: string; value: string;
@@ -21,7 +21,7 @@ interface OtpVerifyFormProps {
authType: AuthType; authType: AuthType;
authMode: AuthMode; authMode: AuthMode;
onEditValue: () => void; onEditValue: () => void;
onOTPVerified: (registeredWithoutPhoneNumber: boolean) => void; onOTPVerified: (registeredWithoutPhoneNumber: boolean, userID: GUID) => void;
} }
export function OtpVerifyForm({ export function OtpVerifyForm({
@@ -104,7 +104,7 @@ export function OtpVerifyForm({
if (jsonRes.success) { if (jsonRes.success) {
setVerifyStatus('success'); setVerifyStatus('success');
onOTPVerified(jsonRes.registeredWithOutPhoneNumber); onOTPVerified(jsonRes.registeredWithOutPhoneNumber, jsonRes.userId);
} else { } else {
setVerifyStatus('failed'); setVerifyStatus('failed');
setErrorMessage(jsonRes.message); setErrorMessage(jsonRes.message);

View File

@@ -23,17 +23,25 @@ import { containsNumber } from '@/utils/regexes/containsNumber';
import { containsSymbol } from '@/utils/regexes/containsSymbol'; import { containsSymbol } from '@/utils/regexes/containsSymbol';
import { least8Chars } from '@/utils/regexes/least8Chars'; import { least8Chars } from '@/utils/regexes/least8Chars';
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter'; import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
import type { ResetPasswordRequest } from '../../types/userTypes';
import type { AuthType } from '../../types/authTypes';
import type { CountryCode } from '@/types/commonTypes';
import { resetPassword } from '../../api/authorizationAPI';
export interface ChangePasswordProps { export interface ChangePasswordProps {
onEditInfo: () => void; onEditInfo: () => void;
onPasswordChanged: () => void; onPasswordChanged: () => void;
forgettedPasswordInfo: string; forgettedPasswordInfo: string;
infoType: AuthType;
countryCode: CountryCode;
} }
export const ChangePassword = ({ export const ChangePassword = ({
onEditInfo, onEditInfo,
onPasswordChanged, onPasswordChanged,
forgettedPasswordInfo, forgettedPasswordInfo,
infoType,
countryCode,
}: ChangePasswordProps) => { }: ChangePasswordProps) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation('authentication'); const { t } = useTranslation('authentication');
@@ -75,7 +83,7 @@ export const ChangePassword = ({
setConfirmInputTouched(true); setConfirmInputTouched(true);
}; };
const handleSubmit = () => { const handleSubmit = async () => {
if (!passValue || !isValidPassword(passValue)) { if (!passValue || !isValidPassword(passValue)) {
setInputTouched(true); setInputTouched(true);
inputRef.current?.focus(); inputRef.current?.focus();
@@ -85,15 +93,29 @@ export const ChangePassword = ({
} else { } else {
setChangePasswordLoading(true); setChangePasswordLoading(true);
// Change setTimeout to api call const apiRequest: ResetPasswordRequest = {
setTimeout(() => { email: infoType === 'email' ? forgettedPasswordInfo : undefined,
setChangePassAlertOpen(true); phoneNumber:
// setLoginStatus('success'); infoType === 'phone'
// setLoginStatus('failed'); ? countryCode + forgettedPasswordInfo
// setLoginFailedMessage('رمز عبور اشتباه میباشد'); : undefined,
newPassword: passValue,
confirmNewPassword: confirmPassValue,
};
const result = await resetPassword(apiRequest);
const jsonRes = await result.json();
if (jsonRes.success) {
setChangePasswordStatus('success');
onPasswordChanged(); onPasswordChanged();
} else {
setChangePasswordStatus('failed');
setChangePassFailedMessage(jsonRes.message);
}
setChangePassAlertOpen(true);
setChangePasswordLoading(false); setChangePasswordLoading(false);
}, 1000);
} }
}; };

View File

@@ -3,6 +3,7 @@ import type { AuthType } from '../../types/authTypes';
import { ForgettedPasswordInfo } from './ForgettedPasswordInfo'; import { ForgettedPasswordInfo } from './ForgettedPasswordInfo';
import { ForgetPasswordOtp } from './ForgetPasswordOtp'; import { ForgetPasswordOtp } from './ForgetPasswordOtp';
import { ChangePassword } from './ChangePassword'; import { ChangePassword } from './ChangePassword';
import type { CountryCode } from '@/types/commonTypes';
export const ForgetPasswordContainer = () => { export const ForgetPasswordContainer = () => {
const [forgetPassCurrentStep, setForgetPassCurrentStep] = useState< const [forgetPassCurrentStep, setForgetPassCurrentStep] = useState<
@@ -10,10 +11,10 @@ export const ForgetPasswordContainer = () => {
>('enterInfo'); >('enterInfo');
const [forgettedPasswordInfo, setForgettedPasswordInfo] = const [forgettedPasswordInfo, setForgettedPasswordInfo] =
useState<string>(''); useState<string>('');
const [infoCountryCode, setInfoCountryCode] = useState<CountryCode>('+98');
const [infoType, setInfoType] = useState<AuthType>('email'); const [infoType, setInfoType] = useState<AuthType>('email');
const handleSendForgetPassOtp = (value: string) => { const handleVerifyOtp = (value: string) => {
console.log(value);
setForgetPassCurrentStep('verifyOtp'); setForgetPassCurrentStep('verifyOtp');
}; };
@@ -37,12 +38,15 @@ export const ForgetPasswordContainer = () => {
setInfoType={setInfoType} setInfoType={setInfoType}
forgettedPasswordInfo={forgettedPasswordInfo} forgettedPasswordInfo={forgettedPasswordInfo}
setForgettedPasswordInfo={setForgettedPasswordInfo} setForgettedPasswordInfo={setForgettedPasswordInfo}
onSendOtp={handleSendForgetPassOtp} onVerifyOtp={handleVerifyOtp}
countryCode={infoCountryCode}
setCountryCode={setInfoCountryCode}
/> />
)} )}
{forgetPassCurrentStep === 'verifyOtp' && ( {forgetPassCurrentStep === 'verifyOtp' && (
<ForgetPasswordOtp <ForgetPasswordOtp
countryCode={infoCountryCode}
infoType={infoType} infoType={infoType}
onEditInfo={handleEditInfo} onEditInfo={handleEditInfo}
forgettedPasswordInfo={forgettedPasswordInfo} forgettedPasswordInfo={forgettedPasswordInfo}
@@ -55,6 +59,8 @@ export const ForgetPasswordContainer = () => {
onEditInfo={handleEditInfo} onEditInfo={handleEditInfo}
forgettedPasswordInfo={forgettedPasswordInfo} forgettedPasswordInfo={forgettedPasswordInfo}
onPasswordChanged={handlePasswordChanged} onPasswordChanged={handlePasswordChanged}
infoType={infoType}
countryCode={infoCountryCode}
/> />
)} )}
</> </>

View File

@@ -6,10 +6,14 @@ import type { AuthMode, AuthType } from '../../types/authTypes';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Toast } from '@/components/Toast'; import { Toast } from '@/components/Toast';
import { AuthenticationCard } from '../AuthenticationCard'; import { AuthenticationCard } from '../AuthenticationCard';
import type { ConfirmForgetPassCodeRequest } from '../../types/userTypes';
import type { CountryCode } from '@/types/commonTypes';
import { confirmForgetPassCode } from '../../api/authorizationAPI';
interface ForgetPasswordOtpProps { interface ForgetPasswordOtpProps {
forgettedPasswordInfo: string; forgettedPasswordInfo: string;
infoType: AuthType; infoType: AuthType;
countryCode: CountryCode;
onEditInfo: () => void; onEditInfo: () => void;
onOTPVerified: (otpCode: string) => void; onOTPVerified: (otpCode: string) => void;
} }
@@ -17,6 +21,7 @@ interface ForgetPasswordOtpProps {
export function ForgetPasswordOtp({ export function ForgetPasswordOtp({
forgettedPasswordInfo, forgettedPasswordInfo,
infoType, infoType,
countryCode,
onEditInfo, onEditInfo,
onOTPVerified, onOTPVerified,
}: ForgetPasswordOtpProps) { }: ForgetPasswordOtpProps) {
@@ -25,7 +30,7 @@ export function ForgetPasswordOtp({
const [verifyStatus, setVerifyStatus] = useState<'failed' | 'success'>(); const [verifyStatus, setVerifyStatus] = useState<'failed' | 'success'>();
const [verifyStatusLoading, setVerifyStatusLoading] = const [verifyStatusLoading, setVerifyStatusLoading] =
useState<boolean>(false); useState<boolean>(false);
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false); const [verifyAlertMessage, setVerifyAlertMessage] = useState<string>();
const { t } = useTranslation('authentication'); const { t } = useTranslation('authentication');
const [resendTimer, setResendTimer] = useState<number>(120); const [resendTimer, setResendTimer] = useState<number>(120);
const [canResend, setCanResend] = useState(false); const [canResend, setCanResend] = useState(false);
@@ -70,7 +75,7 @@ export function ForgetPasswordOtp({
setOtpCode(formattedValue); setOtpCode(formattedValue);
}; };
const handleVerifyOTP = () => { const handleVerifyOTP = async () => {
if (!otpCode || otpCode.length < 4) { if (!otpCode || otpCode.length < 4) {
setOtpDigitInvalid(true); setOtpDigitInvalid(true);
} else { } else {
@@ -78,11 +83,25 @@ export function ForgetPasswordOtp({
setVerifyStatusLoading(true); setVerifyStatusLoading(true);
// Change setTimeout to api call // Change setTimeout to api call
setTimeout(() => { const apiRequest: ConfirmForgetPassCodeRequest = {
setVerifyAlertOpen(false); email: infoType === 'email' ? forgettedPasswordInfo : undefined,
phoneNumber:
infoType === 'phone'
? countryCode + forgettedPasswordInfo
: undefined,
code: otpCode,
};
const result = await confirmForgetPassCode(apiRequest);
const jsonRes = await result.json();
if (jsonRes.success) {
onOTPVerified(otpCode); onOTPVerified(otpCode);
} else {
setVerifyAlertMessage(jsonRes.message);
}
setVerifyStatusLoading(false); setVerifyStatusLoading(false);
}, 1000);
} }
}; };
@@ -90,11 +109,11 @@ export function ForgetPasswordOtp({
<Stack alignItems="center"> <Stack alignItems="center">
<AuthenticationCard> <AuthenticationCard>
<Toast <Toast
open={verifyAlertOpen} open={!!verifyAlertMessage}
onClose={() => setVerifyAlertOpen(false)} onClose={() => setVerifyAlertMessage(undefined)}
color={'error'} color={'error'}
> >
{t('verify.theVerificationCodeIsIncorrect')} {verifyAlertMessage}
</Toast> </Toast>
<Box <Box

View File

@@ -15,13 +15,19 @@ import { isEmail } from '@/utils/regexes/isEmail';
import parsePhoneNumberFromString from 'libphonenumber-js'; import parsePhoneNumberFromString from 'libphonenumber-js';
import { AuthenticationCard } from '../AuthenticationCard'; import { AuthenticationCard } from '../AuthenticationCard';
import { CountryCodeSelector } from '../CountryCodeSelector'; import { CountryCodeSelector } from '../CountryCodeSelector';
import type { CountryCode } from '@/types/commonTypes';
import { sendForgetPassCode } from '../../api/authorizationAPI';
import type { SendForgetPassCodeRequest } from '../../types/userTypes';
import { Toast } from '@/components/Toast';
export interface ForgettedPasswordInfoProps { export interface ForgettedPasswordInfoProps {
forgettedPasswordInfo: string; forgettedPasswordInfo: string;
setForgettedPasswordInfo: Dispatch<string>; setForgettedPasswordInfo: Dispatch<string>;
infoType: AuthType; infoType: AuthType;
setInfoType: Dispatch<AuthType>; setInfoType: Dispatch<AuthType>;
onSendOtp: (value: string) => void; onVerifyOtp: (value: string) => void;
countryCode: CountryCode;
setCountryCode: Dispatch<CountryCode>;
} }
export function ForgettedPasswordInfo({ export function ForgettedPasswordInfo({
@@ -29,15 +35,18 @@ export function ForgettedPasswordInfo({
setForgettedPasswordInfo, setForgettedPasswordInfo,
infoType, infoType,
setInfoType, setInfoType,
onSendOtp, onVerifyOtp,
countryCode,
setCountryCode,
}: ForgettedPasswordInfoProps) { }: ForgettedPasswordInfoProps) {
const { t, i18n } = useTranslation('authentication'); const { t, i18n } = useTranslation('authentication');
const [countryCode, setCountryCode] = useState('+98');
const textFieldRef = useRef<HTMLDivElement>(null); const textFieldRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const dir = i18n.dir(); const dir = i18n.dir();
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
const [touched, setTouched] = useState<boolean>(false); const [touched, setTouched] = useState<boolean>(false);
const [errorMessage, setErrorMessage] = useState<string>();
const [sendCodeLoading, setSendCodeLoading] = useState<boolean>(false);
const inputError: boolean = touched && !!error; const inputError: boolean = touched && !!error;
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -75,25 +84,42 @@ export function ForgettedPasswordInfo({
return phoneNumber && phoneNumber.isValid(); return phoneNumber && phoneNumber.isValid();
}; };
const isInputValid = (value: string, authType: AuthType): boolean => { const isInputValid = (value: string, infoType: AuthType): boolean => {
if (!value) { if (!value) {
return false; return false;
} }
if (authType === 'email' && !isEmail(value)) { if (infoType === 'email' && !isEmail(value)) {
return false; return false;
} }
if (authType === 'phone' && !isPhoneValid(countryCode, value)) { if (infoType === 'phone' && !isPhoneValid(countryCode, value)) {
return false; return false;
} }
return true; return true;
}; };
const handleSubmit = () => { const handleSubmit = async () => {
if (isInputValid(forgettedPasswordInfo, infoType)) { if (isInputValid(forgettedPasswordInfo, infoType)) {
onSendOtp(forgettedPasswordInfo); setSendCodeLoading(true);
const sendCodeRequest: SendForgetPassCodeRequest = {
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
phoneNumber:
infoType === 'phone'
? countryCode + forgettedPasswordInfo
: undefined,
};
const result = await sendForgetPassCode(sendCodeRequest);
const jsonRes = await result.json();
if (!jsonRes.success) {
setErrorMessage(jsonRes.message);
}
setSendCodeLoading(false);
onVerifyOtp(forgettedPasswordInfo);
} else { } else {
inputRef.current?.focus(); inputRef.current?.focus();
validateInput(forgettedPasswordInfo, infoType); validateInput(forgettedPasswordInfo, infoType);
@@ -105,6 +131,14 @@ export function ForgettedPasswordInfo({
return ( return (
<AuthenticationCard> <AuthenticationCard>
<Toast
color="error"
onClose={() => setErrorMessage(undefined)}
open={!!errorMessage}
>
{errorMessage}
</Toast>
<Stack spacing={1}> <Stack spacing={1}>
<Typography variant="h5"> <Typography variant="h5">
{t('forgetPassword.forgetPassword')} {t('forgetPassword.forgetPassword')}
@@ -144,7 +178,9 @@ export function ForgettedPasswordInfo({
/> />
<Stack spacing={2}> <Stack spacing={2}>
<Button onClick={handleSubmit}>{t('forgetPassword.confirm')}</Button> <Button loading={sendCodeLoading} onClick={handleSubmit}>
{t('forgetPassword.confirm')}
</Button>
</Stack> </Stack>
</AuthenticationCard> </AuthenticationCard>
); );

View File

@@ -0,0 +1,23 @@
import { FlexBox } from '@/components/components/common/FlexBox';
import Logo from '@/components/Logo';
import { Paper } from '@mui/material';
import { useState } from 'react';
import { AuthenticationSteps } from '../components/AuthenticationSteps/AuthenticationSteps';
import { ForgetPasswordContainer } from '../components/ForgetPassword/ForgetPasswordContainer';
export function ForgetPasswordPage() {
return (
<FlexBox
direction="column"
align="center"
justify="center"
sx={{
minHeight: '100vh',
gap: 3,
}}
>
<Logo />
<ForgetPasswordContainer />
</FlexBox>
);
}

View File

@@ -86,8 +86,8 @@ export interface SendForgetPassCodeRequest {
// ConfirmForgetPassCode // ConfirmForgetPassCode
export interface ConfirmForgetPassCodeRequest { export interface ConfirmForgetPassCodeRequest {
email: string; email?: string;
phoneNumber: string; phoneNumber?: string;
code: string; code: string;
} }