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

View File

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

View File

@@ -7,7 +7,7 @@ import { CompleteSignUp } from './CompleteSignUp';
import { EnterPasswordForm } from './EnterPasswordForm';
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
import { UserStatus } from '../../types/userTypes';
import type { CountryCode } from '@/types/commonTypes';
import type { CountryCode, GUID } from '@/types/commonTypes';
import { VerifyPhoneNumber } from './VerifyPhoneNumber';
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) {
// setCurrentStep('addPhoneNumber');
// }

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import type { AuthType } from '../../types/authTypes';
import { ForgettedPasswordInfo } from './ForgettedPasswordInfo';
import { ForgetPasswordOtp } from './ForgetPasswordOtp';
import { ChangePassword } from './ChangePassword';
import type { CountryCode } from '@/types/commonTypes';
export const ForgetPasswordContainer = () => {
const [forgetPassCurrentStep, setForgetPassCurrentStep] = useState<
@@ -10,10 +11,10 @@ export const ForgetPasswordContainer = () => {
>('enterInfo');
const [forgettedPasswordInfo, setForgettedPasswordInfo] =
useState<string>('');
const [infoCountryCode, setInfoCountryCode] = useState<CountryCode>('+98');
const [infoType, setInfoType] = useState<AuthType>('email');
const handleSendForgetPassOtp = (value: string) => {
console.log(value);
const handleVerifyOtp = (value: string) => {
setForgetPassCurrentStep('verifyOtp');
};
@@ -37,12 +38,15 @@ export const ForgetPasswordContainer = () => {
setInfoType={setInfoType}
forgettedPasswordInfo={forgettedPasswordInfo}
setForgettedPasswordInfo={setForgettedPasswordInfo}
onSendOtp={handleSendForgetPassOtp}
onVerifyOtp={handleVerifyOtp}
countryCode={infoCountryCode}
setCountryCode={setInfoCountryCode}
/>
)}
{forgetPassCurrentStep === 'verifyOtp' && (
<ForgetPasswordOtp
countryCode={infoCountryCode}
infoType={infoType}
onEditInfo={handleEditInfo}
forgettedPasswordInfo={forgettedPasswordInfo}
@@ -55,6 +59,8 @@ export const ForgetPasswordContainer = () => {
onEditInfo={handleEditInfo}
forgettedPasswordInfo={forgettedPasswordInfo}
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 { Toast } from '@/components/Toast';
import { AuthenticationCard } from '../AuthenticationCard';
import type { ConfirmForgetPassCodeRequest } from '../../types/userTypes';
import type { CountryCode } from '@/types/commonTypes';
import { confirmForgetPassCode } from '../../api/authorizationAPI';
interface ForgetPasswordOtpProps {
forgettedPasswordInfo: string;
infoType: AuthType;
countryCode: CountryCode;
onEditInfo: () => void;
onOTPVerified: (otpCode: string) => void;
}
@@ -17,6 +21,7 @@ interface ForgetPasswordOtpProps {
export function ForgetPasswordOtp({
forgettedPasswordInfo,
infoType,
countryCode,
onEditInfo,
onOTPVerified,
}: ForgetPasswordOtpProps) {
@@ -25,7 +30,7 @@ export function ForgetPasswordOtp({
const [verifyStatus, setVerifyStatus] = useState<'failed' | 'success'>();
const [verifyStatusLoading, setVerifyStatusLoading] =
useState<boolean>(false);
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false);
const [verifyAlertMessage, setVerifyAlertMessage] = useState<string>();
const { t } = useTranslation('authentication');
const [resendTimer, setResendTimer] = useState<number>(120);
const [canResend, setCanResend] = useState(false);
@@ -70,7 +75,7 @@ export function ForgetPasswordOtp({
setOtpCode(formattedValue);
};
const handleVerifyOTP = () => {
const handleVerifyOTP = async () => {
if (!otpCode || otpCode.length < 4) {
setOtpDigitInvalid(true);
} else {
@@ -78,11 +83,25 @@ export function ForgetPasswordOtp({
setVerifyStatusLoading(true);
// Change setTimeout to api call
setTimeout(() => {
setVerifyAlertOpen(false);
const apiRequest: ConfirmForgetPassCodeRequest = {
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);
setVerifyStatusLoading(false);
}, 1000);
} else {
setVerifyAlertMessage(jsonRes.message);
}
setVerifyStatusLoading(false);
}
};
@@ -90,11 +109,11 @@ export function ForgetPasswordOtp({
<Stack alignItems="center">
<AuthenticationCard>
<Toast
open={verifyAlertOpen}
onClose={() => setVerifyAlertOpen(false)}
open={!!verifyAlertMessage}
onClose={() => setVerifyAlertMessage(undefined)}
color={'error'}
>
{t('verify.theVerificationCodeIsIncorrect')}
{verifyAlertMessage}
</Toast>
<Box

View File

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