diff --git a/src/App.tsx b/src/App.tsx index f1f5aa2..585b205 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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() { } /> } /> + } /> , diff --git a/src/features/authorization/api/authorizationAPI.ts b/src/features/authorization/api/authorizationAPI.ts index 290a736..47a8786 100644 --- a/src/features/authorization/api/authorizationAPI.ts +++ b/src/features/authorization/api/authorizationAPI.ts @@ -77,7 +77,7 @@ export const sendForgetPassCode = async (body: SendForgetPassCodeRequest) => { return fetchRequest('User/SendForgetPassCode', body); }; -export const ConfirmForgetPassCode = async ( +export const confirmForgetPassCode = async ( body: ConfirmForgetPassCodeRequest, ) => { return fetchRequest('User/ConfirmForgetPassCode', body); diff --git a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx index ccfc6c1..8d8d496 100644 --- a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx +++ b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx @@ -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'); // } diff --git a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx index 748f7e9..776f96b 100644 --- a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx +++ b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx @@ -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); diff --git a/src/features/authorization/components/ForgetPassword/ChangePassword.tsx b/src/features/authorization/components/ForgetPassword/ChangePassword.tsx index a83732b..27877a8 100644 --- a/src/features/authorization/components/ForgetPassword/ChangePassword.tsx +++ b/src/features/authorization/components/ForgetPassword/ChangePassword.tsx @@ -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); } }; diff --git a/src/features/authorization/components/ForgetPassword/ForgetPasswordContainer.tsx b/src/features/authorization/components/ForgetPassword/ForgetPasswordContainer.tsx index a8112fb..a980bed 100644 --- a/src/features/authorization/components/ForgetPassword/ForgetPasswordContainer.tsx +++ b/src/features/authorization/components/ForgetPassword/ForgetPasswordContainer.tsx @@ -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(''); + const [infoCountryCode, setInfoCountryCode] = useState('+98'); const [infoType, setInfoType] = useState('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' && ( { onEditInfo={handleEditInfo} forgettedPasswordInfo={forgettedPasswordInfo} onPasswordChanged={handlePasswordChanged} + infoType={infoType} + countryCode={infoCountryCode} /> )} diff --git a/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx b/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx index 62c81cb..275a79a 100644 --- a/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx +++ b/src/features/authorization/components/ForgetPassword/ForgetPasswordOtp.tsx @@ -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(false); - const [verifyAlertOpen, setVerifyAlertOpen] = useState(false); + const [verifyAlertMessage, setVerifyAlertMessage] = useState(); const { t } = useTranslation('authentication'); const [resendTimer, setResendTimer] = useState(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({ setVerifyAlertOpen(false)} + open={!!verifyAlertMessage} + onClose={() => setVerifyAlertMessage(undefined)} color={'error'} > - {t('verify.theVerificationCodeIsIncorrect')} + {verifyAlertMessage} ; infoType: AuthType; setInfoType: Dispatch; - onSendOtp: (value: string) => void; + onVerifyOtp: (value: string) => void; + countryCode: CountryCode; + setCountryCode: Dispatch; } 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(null); const inputRef = useRef(null); const dir = i18n.dir(); const [error, setError] = useState(); const [touched, setTouched] = useState(false); + const [errorMessage, setErrorMessage] = useState(); + const [sendCodeLoading, setSendCodeLoading] = useState(false); const inputError: boolean = touched && !!error; const handleInputChange = (event: React.ChangeEvent) => { @@ -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 ( + setErrorMessage(undefined)} + open={!!errorMessage} + > + {errorMessage} + + {t('forgetPassword.forgetPassword')} @@ -144,7 +178,9 @@ export function ForgettedPasswordInfo({ /> - + ); diff --git a/src/features/authorization/routes/ForgetPasswordPage.tsx b/src/features/authorization/routes/ForgetPasswordPage.tsx new file mode 100644 index 0000000..d30290e --- /dev/null +++ b/src/features/authorization/routes/ForgetPasswordPage.tsx @@ -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 ( + + + + + ); +} diff --git a/src/features/authorization/types/userTypes.ts b/src/features/authorization/types/userTypes.ts index 74b5e1d..4f0a8eb 100644 --- a/src/features/authorization/types/userTypes.ts +++ b/src/features/authorization/types/userTypes.ts @@ -86,8 +86,8 @@ export interface SendForgetPassCodeRequest { // ConfirmForgetPassCode export interface ConfirmForgetPassCodeRequest { - email: string; - phoneNumber: string; + email?: string; + phoneNumber?: string; code: string; }