feat: login with password apis added

This commit is contained in:
مهرزاد قدرتی
2025-08-10 12:21:41 +03:30
parent 08bfe5c979
commit 945aa379ea
8 changed files with 108 additions and 23 deletions

View File

@@ -12,8 +12,8 @@ function App() {
<LanguageManager /> <LanguageManager />
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Navigate to="auth" />} /> <Route path="/" element={<Navigate to="login" />} />
<Route path="/auth" element={<AuthenticationPage />} /> <Route path="/login" element={<AuthenticationPage />} />
<Route path="/forget-password" element={<ForgetPasswordPage />} /> <Route path="/forget-password" element={<ForgetPasswordPage />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>

View File

@@ -12,6 +12,7 @@ import type {
LoginOrSignUpWithGoogleResponse, LoginOrSignUpWithGoogleResponse,
LoginRequest, LoginRequest,
LoginResponse, LoginResponse,
PasswordLoginRequest,
ResetPasswordRequest, ResetPasswordRequest,
ResetPasswordResponse, ResetPasswordResponse,
SendEmailOtpRequest, SendEmailOtpRequest,
@@ -49,7 +50,7 @@ export const loginOrSignUpWithOtp = async (body: LoginRequest) => {
return fetchRequest<LoginResponse>('User/LoginOrSignUpWithOtp', body); return fetchRequest<LoginResponse>('User/LoginOrSignUpWithOtp', body);
}; };
export const loginWithPassword = async (body: LoginRequest) => { export const loginWithPassword = async (body: PasswordLoginRequest) => {
return fetchRequest<LoginResponse>('User/LoginWithPassword', body); return fetchRequest<LoginResponse>('User/LoginWithPassword', body);
}; };

View File

@@ -5,7 +5,11 @@ import { OtpVerifyForm } from './OtpVerifyForm';
import { isNumeric } from '@/utils/regexes/isNumeric'; import { isNumeric } from '@/utils/regexes/isNumeric';
import { CompleteSignUp } from './CompleteSignUp'; import { CompleteSignUp } from './CompleteSignUp';
import { EnterPasswordForm } from './EnterPasswordForm'; import { EnterPasswordForm } from './EnterPasswordForm';
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI'; import {
getUserStatusByPhoneNumberOrEmail,
sendEmailOtp,
sendSmsOtp,
} from '../../api/authorizationAPI';
import { UserStatus } from '../../types/userTypes'; import { UserStatus } from '../../types/userTypes';
import type { CountryCode, GUID } from '@/types/commonTypes'; import type { CountryCode, GUID } from '@/types/commonTypes';
import { VerifyPhoneNumber } from './VerifyPhoneNumber'; import { VerifyPhoneNumber } from './VerifyPhoneNumber';
@@ -51,11 +55,16 @@ export const AuthenticationSteps = (): JSX.Element => {
const handleOTPVerfied = ( const handleOTPVerfied = (
registeredWithoutPhoneNumber: boolean = false, registeredWithoutPhoneNumber: boolean = false,
userId: GUID, userId: GUID,
returnUrl?: string,
) => { ) => {
localStorage.setItem('userID', userId); localStorage.setItem('userID', userId);
// if (registeredWithoutPhoneNumber) { // if (registeredWithoutPhoneNumber) {
// setCurrentStep('addPhoneNumber'); // setCurrentStep('addPhoneNumber');
// } // }
if (returnUrl) {
location.href = returnUrl;
}
}; };
const handleEditValue = () => { const handleEditValue = () => {
@@ -74,7 +83,17 @@ export const AuthenticationSteps = (): JSX.Element => {
setCurrentStep('emailOrPhone'); setCurrentStep('emailOrPhone');
}; };
const handleLoggedInWithPassowrd = () => {}; const handleLoggedInWithPassowrd = (userId: GUID, returnUrl?: string) => {
localStorage.setItem('userID', userId);
if (returnUrl) {
location.href = returnUrl;
}
};
const handleLoginWithOtpInsteadOfPassword = async () => {
setCurrentStep('verify');
};
return ( return (
<> <>
@@ -103,9 +122,12 @@ export const AuthenticationSteps = (): JSX.Element => {
{currentStep === 'enterPassword' && ( {currentStep === 'enterPassword' && (
<EnterPasswordForm <EnterPasswordForm
loginRegisterValue={loginRegisterValue}
countryCode={countryCode}
authType={authType}
onLoggedIn={handleLoggedInWithPassowrd} onLoggedIn={handleLoggedInWithPassowrd}
onEditValue={handleEditValue} onEditValue={handleEditValue}
onLoginWithOTP={() => setCurrentStep('verify')} onLoginWithOTP={handleLoginWithOtpInsteadOfPassword}
emailOrPhone={loginRegisterValue} emailOrPhone={loginRegisterValue}
/> />
)} )}

View File

@@ -11,12 +11,24 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Toast } from '@/components/Toast'; import { Toast } from '@/components/Toast';
import { Link, Navigate, useSearchParams } from 'react-router';
import type { AuthType } from '../../types/authTypes';
import type { CountryCode, GUID } from '@/types/commonTypes';
import {
loginWithPassword,
sendEmailOtp,
sendSmsOtp,
} from '../../api/authorizationAPI';
import type { LoginRequest, PasswordLoginRequest } from '../../types/userTypes';
export interface EnterPasswordFormProps { export interface EnterPasswordFormProps {
onEditValue: () => void; onEditValue: () => void;
onLoginWithOTP: () => void; onLoginWithOTP: () => void;
onLoggedIn: () => void; onLoggedIn: (userId: GUID, returnUrl?: string) => void;
emailOrPhone: string; emailOrPhone: string;
authType: AuthType;
loginRegisterValue: string;
countryCode: CountryCode;
} }
export const EnterPasswordForm = ({ export const EnterPasswordForm = ({
@@ -24,7 +36,12 @@ export const EnterPasswordForm = ({
onLoginWithOTP, onLoginWithOTP,
onLoggedIn, onLoggedIn,
emailOrPhone, emailOrPhone,
authType,
loginRegisterValue,
countryCode,
}: EnterPasswordFormProps) => { }: EnterPasswordFormProps) => {
const DEFAULT_RETURN_URL = 'https://account.business-harmony.com/';
const [searchParams] = useSearchParams();
const { t } = useTranslation('authentication'); const { t } = useTranslation('authentication');
const [passValue, setPassValue] = useState<string>(''); const [passValue, setPassValue] = useState<string>('');
const [inputTouched, setInputTouched] = useState<boolean>(false); const [inputTouched, setInputTouched] = useState<boolean>(false);
@@ -34,29 +51,53 @@ export const EnterPasswordForm = ({
const [loginStatus, setLoginStatus] = useState<'success' | 'failed'>(); const [loginStatus, setLoginStatus] = useState<'success' | 'failed'>();
const [loginAlertOpen, setLoginAlertOpen] = useState<boolean>(false); const [loginAlertOpen, setLoginAlertOpen] = useState<boolean>(false);
const [loginFailedMessage, setLoginFailedMessage] = useState<string>(''); const [loginFailedMessage, setLoginFailedMessage] = useState<string>('');
const [sendOtpLoading, setSendOtpLoading] = useState<boolean>(false);
const handleBlur = () => { const handleBlur = () => {
setInputTouched(true); setInputTouched(true);
}; };
const handleSubmit = () => { const handleSubmit = async () => {
if (!passValue) { if (!passValue) {
inputRef.current?.focus(); inputRef.current?.focus();
} else { } else {
setLoginLoading(true); setLoginLoading(true);
// Change setTimeout to api call const apiRequest: PasswordLoginRequest = {
setTimeout(() => { phoneNumber:
setLoginAlertOpen(true); authType === 'phone' ? countryCode + loginRegisterValue : undefined,
// setLoginStatus('success'); email: authType === 'email' ? loginRegisterValue : undefined,
password: passValue,
returnUrl: searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL,
};
const result = await loginWithPassword(apiRequest);
const jsonRes = await result.json();
if (jsonRes.success) {
setLoginStatus('success');
onLoggedIn(jsonRes.userId, jsonRes.returnUrl);
} else {
setLoginStatus('failed'); setLoginStatus('failed');
setLoginFailedMessage('رمز عبور اشتباه میباشد'); setLoginFailedMessage(jsonRes.message);
onLoggedIn(); }
setLoginLoading(false); setLoginAlertOpen(true);
}, 1000); setLoginLoading(false);
} }
}; };
const handleLoginWithOtp = async () => {
setSendOtpLoading(true);
if (authType === 'phone') {
await sendSmsOtp({ phoneNumber: countryCode + loginRegisterValue });
} else {
await sendEmailOtp({ email: loginRegisterValue });
}
setSendOtpLoading(false);
onLoginWithOTP();
};
return ( return (
<AuthenticationCard> <AuthenticationCard>
<Toast <Toast
@@ -127,9 +168,10 @@ export const EnterPasswordForm = ({
/> />
<Button <Button
onClick={onLoginWithOTP} onClick={handleLoginWithOtp}
sx={{ width: 'auto', mb: 2 }} sx={{ width: 'auto', mb: 2 }}
variant="text" variant="text"
loading={sendOtpLoading}
endIcon={<ArrowLeft />} endIcon={<ArrowLeft />}
> >
{t('enterPassword.loginWithOneTimeCode')} {t('enterPassword.loginWithOneTimeCode')}
@@ -139,7 +181,9 @@ export const EnterPasswordForm = ({
<Button loading={loginLoading} onClick={handleSubmit}> <Button loading={loginLoading} onClick={handleSubmit}>
{t('verify.confirmAndLogin')} {t('verify.confirmAndLogin')}
</Button> </Button>
<Button variant="text">{t('enterPassword.iForgotMyPassword')}</Button> <Link to="/forget-password">
<Button variant="text">{t('enterPassword.iForgotMyPassword')}</Button>
</Link>
</Stack> </Stack>
</AuthenticationCard> </AuthenticationCard>
); );

View File

@@ -21,7 +21,11 @@ interface OtpVerifyFormProps {
authType: AuthType; authType: AuthType;
authMode: AuthMode; authMode: AuthMode;
onEditValue: () => void; onEditValue: () => void;
onOTPVerified: (registeredWithoutPhoneNumber: boolean, userID: GUID) => void; onOTPVerified: (
registeredWithoutPhoneNumber: boolean,
userID: GUID,
returnUrl?: string,
) => void;
} }
export function OtpVerifyForm({ export function OtpVerifyForm({
@@ -32,6 +36,7 @@ export function OtpVerifyForm({
onEditValue, onEditValue,
onOTPVerified, onOTPVerified,
}: OtpVerifyFormProps) { }: OtpVerifyFormProps) {
const DEFAULT_RETURN_URL = 'https://account.business-harmony.com/';
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const [otpCode, setOtpCode] = useState<string>(''); const [otpCode, setOtpCode] = useState<string>('');
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false); const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
@@ -97,14 +102,18 @@ export function OtpVerifyForm({
otpCode: otpCode, otpCode: otpCode,
phoneNumber: authType === 'phone' ? countryCode + value : undefined, phoneNumber: authType === 'phone' ? countryCode + value : undefined,
email: authType === 'email' ? value : undefined, email: authType === 'email' ? value : undefined,
returnUrl: searchParams.get('returnUrl') ?? '/', returnUrl: searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL,
}; };
const result = await loginOrSignUpWithOtp(loginRequest); const result = await loginOrSignUpWithOtp(loginRequest);
const jsonRes = await result.json(); const jsonRes = await result.json();
if (jsonRes.success) { if (jsonRes.success) {
setVerifyStatus('success'); setVerifyStatus('success');
onOTPVerified(jsonRes.registeredWithOutPhoneNumber, jsonRes.userId); onOTPVerified(
jsonRes.registeredWithOutPhoneNumber,
jsonRes.userId,
jsonRes.returnUrl,
);
} else { } else {
setVerifyStatus('failed'); setVerifyStatus('failed');
setErrorMessage(jsonRes.message); setErrorMessage(jsonRes.message);

View File

@@ -241,7 +241,7 @@ export const ChangePassword = ({
color="primary" color="primary"
onClick={() => setShowConfirmPassword(!showConfirmPassword)} onClick={() => setShowConfirmPassword(!showConfirmPassword)}
> >
{showPassword ? <Eye /> : <EyeSlash />} {showConfirmPassword ? <Eye /> : <EyeSlash />}
</IconButton> </IconButton>
) : ( ) : (
'' ''

View File

@@ -137,7 +137,9 @@ export function ForgetPasswordOtp({
endIcon={<Edit2 />} endIcon={<Edit2 />}
onClick={onEditInfo} onClick={onEditInfo}
> >
{forgettedPasswordInfo} {infoType === 'phone'
? countryCode + forgettedPasswordInfo
: forgettedPasswordInfo}
</Button> </Button>
</Box> </Box>

View File

@@ -28,6 +28,13 @@ export interface LoginRequest {
returnUrl: string; returnUrl: string;
} }
export interface PasswordLoginRequest {
phoneNumber?: string;
email?: string;
password: string;
returnUrl: string;
}
export interface LoginResponse extends ApiResponse { export interface LoginResponse extends ApiResponse {
returnUrl: string; returnUrl: string;
userId: GUID; userId: GUID;