chore: useApi added to the components
This commit is contained in:
3
.env
3
.env
@@ -1 +1,2 @@
|
||||
VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com/
|
||||
VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com/
|
||||
VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile
|
||||
@@ -7,7 +7,8 @@
|
||||
"loginWithGoogle": "Login with google",
|
||||
"emailIsInvalid": "Email is invalid",
|
||||
"phoneNumberIsInvalid": "Phone number is invalid",
|
||||
"thisFieldIsRequired": "This field is requried"
|
||||
"thisFieldIsRequired": "This field is requried",
|
||||
"googleAuthenticationFailed": "Login with google failed"
|
||||
},
|
||||
"verify": {
|
||||
"verify": "Verify",
|
||||
|
||||
@@ -246,5 +246,9 @@
|
||||
"yemen": "Yemen",
|
||||
"zambia": "Zambia",
|
||||
"zimbabwe": "Zimbabwe"
|
||||
},
|
||||
"messages": {
|
||||
"noResualtFound": "No result found.",
|
||||
"serverError": "Internal server error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"loginWithGoogle": "ورود با گوگل",
|
||||
"emailIsInvalid": "ایمیل وارد شده نامعتبر میباشد",
|
||||
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
||||
"thisFieldIsRequired": "این فیلد الزامی است"
|
||||
"thisFieldIsRequired": "این فیلد الزامی است",
|
||||
"googleAuthenticationFailed": "ورود با گوگل با خطا مواجه شد"
|
||||
},
|
||||
"verify": {
|
||||
"verify": "اعتبارسنجی",
|
||||
|
||||
@@ -185,7 +185,8 @@
|
||||
"zimbabwe": "زیمبابوه"
|
||||
},
|
||||
"messages": {
|
||||
"noResualtFound": "نتیجه ای یافت نشد."
|
||||
"noResualtFound": "نتیجه ای یافت نشد.",
|
||||
"serverError": "خطای سمت سرور"
|
||||
},
|
||||
"side": {
|
||||
"account": "حساب کاربری",
|
||||
|
||||
@@ -19,75 +19,61 @@ import type {
|
||||
SendForgetPassCodeRequest,
|
||||
SendSmsOtpRequest,
|
||||
} from '../types/userTypes';
|
||||
|
||||
const API_URL = 'https://accounts.business-harmony.com/api';
|
||||
|
||||
export const fetchRequest = <T = ApiResponse>(
|
||||
url: string,
|
||||
body: Object | null,
|
||||
): FetchPromise<T> => {
|
||||
return fetch(`${API_URL}/${url}`, {
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
};
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
// GetUserStatusByPhoneNumberOrEmail
|
||||
|
||||
export const getUserStatusByPhoneNumberOrEmail = async (
|
||||
body: GetUserStatusByPhoneNumberOrEmailRequest,
|
||||
) => {
|
||||
return fetchRequest<GetUserStatusByPhoneNumberOrEmailResponse>(
|
||||
return apiClient.post<GetUserStatusByPhoneNumberOrEmailResponse>(
|
||||
'User/GetUserStatusByPhoneNumberOrEmail',
|
||||
body,
|
||||
);
|
||||
};
|
||||
|
||||
export const loginOrSignUpWithOtp = async (body: LoginRequest) => {
|
||||
return fetchRequest<LoginResponse>('User/LoginOrSignUpWithOtp', body);
|
||||
return apiClient.post<LoginResponse>('User/LoginOrSignUpWithOtp', body);
|
||||
};
|
||||
|
||||
export const loginWithPassword = async (body: PasswordLoginRequest) => {
|
||||
return fetchRequest<LoginResponse>('User/LoginWithPassword', body);
|
||||
return apiClient.post<LoginResponse>('User/LoginWithPassword', body);
|
||||
};
|
||||
|
||||
export const sendSmsOtp = async (body: SendSmsOtpRequest) => {
|
||||
return fetchRequest<ApiResponse>('User/SendSmsOtp', body);
|
||||
return apiClient.post<ApiResponse>('User/SendSmsOtp', body);
|
||||
};
|
||||
|
||||
export const sendEmailOtp = async (body: SendEmailOtpRequest) => {
|
||||
return fetchRequest<ApiResponse>('User/SendEmailOtp', body);
|
||||
return apiClient.post<ApiResponse>('User/SendEmailOtp', body);
|
||||
};
|
||||
|
||||
export const confirmSmsOtp = async (body: ConfirmSmsOtpRequest) => {
|
||||
return fetchRequest<ConfirmOtpResponse>('User/ConfirmSmsOtp', body);
|
||||
return apiClient.post<ConfirmOtpResponse>('User/ConfirmSmsOtp', body);
|
||||
};
|
||||
|
||||
export const confirmEmailOtp = async (body: ConfirmEmailOtpRequest) => {
|
||||
return fetchRequest<ConfirmOtpResponse>('User/ConfirmEmailOtp', body);
|
||||
return apiClient.post<ConfirmOtpResponse>('User/ConfirmEmailOtp', body);
|
||||
};
|
||||
|
||||
export const resetPassword = async (body: ResetPasswordRequest) => {
|
||||
return fetchRequest<ResetPasswordResponse>('User/ResetPassword', body);
|
||||
return apiClient.post<ResetPasswordResponse>('User/ResetPassword', body);
|
||||
};
|
||||
|
||||
export const sendForgetPassCode = async (body: SendForgetPassCodeRequest) => {
|
||||
return fetchRequest<ApiResponse>('User/SendForgetPassCode', body);
|
||||
return apiClient.post<ApiResponse>('User/SendForgetPassCode', body);
|
||||
};
|
||||
|
||||
export const confirmForgetPassCode = async (
|
||||
body: ConfirmForgetPassCodeRequest,
|
||||
) => {
|
||||
return fetchRequest<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body);
|
||||
return apiClient.post<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body);
|
||||
};
|
||||
|
||||
export const loginOrSignUpWithGoogle = async (
|
||||
body: LoginOrSignUpWithGoogleRequest,
|
||||
) => {
|
||||
return fetchRequest<LoginOrSignUpWithGoogleResponse>(
|
||||
return apiClient.post<LoginOrSignUpWithGoogleResponse>(
|
||||
'User/LoginOrSignUpWithGoogle',
|
||||
body,
|
||||
);
|
||||
@@ -96,9 +82,9 @@ export const loginOrSignUpWithGoogle = async (
|
||||
export const completeUserInformation = async (
|
||||
body: CompleteUserInformationRequest,
|
||||
) => {
|
||||
return fetchRequest<ApiResponse>('User/CompleteUserInformation', body);
|
||||
return apiClient.post<ApiResponse>('User/CompleteUserInformation', body);
|
||||
};
|
||||
|
||||
export const logOut = async () => {
|
||||
return fetchRequest<ApiResponse>('User/LogOut', {});
|
||||
return apiClient.post<ApiResponse>('User/LogOut', {});
|
||||
};
|
||||
|
||||
@@ -12,10 +12,10 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
export const AuthenticationSteps = (): JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
const DEFAULT_RETURN_URL = '/profile';
|
||||
const [searchParams] = useSearchParams();
|
||||
const authReturnUrl: string =
|
||||
searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL;
|
||||
const authReturnUrl: string | null = searchParams.get('returnUrl');
|
||||
const authReturnUrlOrDefault: string =
|
||||
authReturnUrl ?? import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL;
|
||||
const [authMode, setAuthMode] = useState<AuthMode>('register');
|
||||
const [authType, setAuthType] = useState<AuthType>('phone');
|
||||
const [currentStep, setCurrentStep] = useState<AuthStep>('emailOrPhone');
|
||||
@@ -66,8 +66,8 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
};
|
||||
|
||||
const redirectToReturnUrl = () => {
|
||||
if (authReturnUrl === DEFAULT_RETURN_URL) {
|
||||
navigate(DEFAULT_RETURN_URL);
|
||||
if (!authReturnUrl) {
|
||||
navigate(import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL);
|
||||
} else {
|
||||
location.href = authReturnUrl;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
<>
|
||||
{currentStep === 'emailOrPhone' && (
|
||||
<LoginRegisterForm
|
||||
authReturnUrl={authReturnUrl}
|
||||
authReturnUrl={authReturnUrlOrDefault}
|
||||
onGoogleAuthenticated={handleUserLoggedIn}
|
||||
countryCode={countryCode}
|
||||
setCountryCode={setCountryCode}
|
||||
@@ -92,7 +92,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
{currentStep === 'verify' && (
|
||||
<OtpVerifyForm
|
||||
onVerifyPhoneNumber={handleConfrimPhoneNumber}
|
||||
authReturnUrl={authReturnUrl}
|
||||
authReturnUrl={authReturnUrlOrDefault}
|
||||
countryCode={countryCode}
|
||||
onOTPVerified={handleUserLoggedIn}
|
||||
onEditValue={() => setCurrentStep('emailOrPhone')}
|
||||
@@ -104,7 +104,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
|
||||
{currentStep === 'enterPassword' && (
|
||||
<EnterPasswordForm
|
||||
authReturnUrl={authReturnUrl}
|
||||
authReturnUrl={authReturnUrlOrDefault}
|
||||
loginRegisterValue={loginRegisterValue}
|
||||
countryCode={countryCode}
|
||||
authType={authType}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import { sendSmsOtp } from '../../api/authorizationAPI';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface CompleteSignUpProps {
|
||||
email: string;
|
||||
@@ -30,7 +31,7 @@ export const CompleteSignUp = ({
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [touched, setTouched] = useState<boolean>(false);
|
||||
const inputError: boolean = touched && !!error;
|
||||
const [sendOtpLoading, setSendOtpLoading] = useState<boolean>(false);
|
||||
const { loading: sendSmsLoading, execute: sendSmsCall } = useApi(sendSmsOtp);
|
||||
|
||||
const isPhoneValid = (code: string, phone: string) => {
|
||||
const phoneNumber = parsePhoneNumberFromString(code + phone);
|
||||
@@ -61,12 +62,8 @@ export const CompleteSignUp = ({
|
||||
if (!value || !isPhoneValid(countryCode, value)) {
|
||||
inputRef.current?.focus();
|
||||
} else {
|
||||
setSendOtpLoading(true);
|
||||
|
||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
||||
await sendSmsCall({ phoneNumber: countryCode + value });
|
||||
onCompleteSignUp(countryCode, value);
|
||||
|
||||
setSendOtpLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,7 +107,7 @@ export const CompleteSignUp = ({
|
||||
sx={{ my: 4 }}
|
||||
/>
|
||||
|
||||
<Button loading={sendOtpLoading} onClick={handleCompleteSignUp}>
|
||||
<Button loading={sendSmsLoading} onClick={handleCompleteSignUp}>
|
||||
{t('verify.confirmAndContinue')}
|
||||
</Button>
|
||||
</AuthenticationCard>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from '../../api/authorizationAPI';
|
||||
import type { PasswordLoginRequest } from '../../types/userTypes';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface EnterPasswordFormProps {
|
||||
onEditValue: () => void;
|
||||
@@ -47,10 +48,16 @@ export const EnterPasswordForm = ({
|
||||
const [inputTouched, setInputTouched] = useState<boolean>(false);
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [loginLoading, setLoginLoading] = useState<boolean>(false);
|
||||
const [isLoginStatusSuccess, setIsLoginStatusSuccess] = useState<boolean>();
|
||||
const [sendOtpLoading, setSendOtpLoading] = useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
const { loading: smsResendLoading, execute: smsResendCall } =
|
||||
useApi(sendSmsOtp);
|
||||
const { loading: emailResendLoading, execute: emailResendCall } =
|
||||
useApi(sendEmailOtp);
|
||||
const {
|
||||
data: loginWithPassResult,
|
||||
loading: loginWithPassLoading,
|
||||
execute: loginWithPassCall,
|
||||
} = useApi(loginWithPassword);
|
||||
|
||||
const handleBlur = () => {
|
||||
setInputTouched(true);
|
||||
@@ -60,8 +67,6 @@ export const EnterPasswordForm = ({
|
||||
if (!passValue) {
|
||||
inputRef.current?.focus();
|
||||
} else {
|
||||
setLoginLoading(true);
|
||||
|
||||
const apiRequest: PasswordLoginRequest = {
|
||||
phoneNumber:
|
||||
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
||||
@@ -69,38 +74,33 @@ export const EnterPasswordForm = ({
|
||||
password: passValue,
|
||||
returnUrl: authReturnUrl,
|
||||
};
|
||||
const result = await loginWithPassword(apiRequest);
|
||||
const jsonRes = await result.json();
|
||||
await loginWithPassCall(apiRequest);
|
||||
|
||||
if (jsonRes.success) {
|
||||
setIsLoginStatusSuccess(true);
|
||||
onLoggedIn(jsonRes.userId);
|
||||
if (!loginWithPassResult) return;
|
||||
|
||||
if (loginWithPassResult.success) {
|
||||
onLoggedIn(loginWithPassResult.userId);
|
||||
|
||||
toast({
|
||||
message: t('verify.youHaveSuccessfullyLoggedIn'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
setIsLoginStatusSuccess(false);
|
||||
toast({
|
||||
message: jsonRes.message,
|
||||
message: loginWithPassResult.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
setLoginLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginWithOtp = async () => {
|
||||
setSendOtpLoading(true);
|
||||
|
||||
if (authType === 'phone') {
|
||||
await sendSmsOtp({ phoneNumber: countryCode + loginRegisterValue });
|
||||
await smsResendCall({ phoneNumber: countryCode + loginRegisterValue });
|
||||
} else {
|
||||
await sendEmailOtp({ email: loginRegisterValue });
|
||||
await emailResendCall({ email: loginRegisterValue });
|
||||
}
|
||||
|
||||
setSendOtpLoading(false);
|
||||
onLoginWithOTP();
|
||||
};
|
||||
|
||||
@@ -171,14 +171,14 @@ export const EnterPasswordForm = ({
|
||||
onClick={handleLoginWithOtp}
|
||||
sx={{ width: 'auto', mb: 2 }}
|
||||
variant="text"
|
||||
loading={sendOtpLoading}
|
||||
loading={emailResendLoading || smsResendLoading}
|
||||
endIcon={<Icon Component={ArrowLeft} />}
|
||||
>
|
||||
{t('enterPassword.loginWithOneTimeCode')}
|
||||
</Button>
|
||||
|
||||
<Stack spacing={1}>
|
||||
<Button loading={loginLoading} onClick={handleSubmit}>
|
||||
<Button loading={loginWithPassLoading} onClick={handleSubmit}>
|
||||
{t('verify.confirmAndLogin')}
|
||||
</Button>
|
||||
<Link to="/forget-password">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { GoogleCodeClientResponse } from '../../types/userTypes';
|
||||
import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
||||
import type { GUID } from '@/types/commonTypes';
|
||||
import { Google } from 'iconsax-react';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface GoogleAuthenticationProps {
|
||||
disabled: boolean;
|
||||
@@ -19,9 +20,12 @@ export const GoogleAuthentication = ({
|
||||
onGoogleAuthenticated,
|
||||
}: GoogleAuthenticationProps) => {
|
||||
const { t } = useTranslation('authentication');
|
||||
const [loginWithGoogleLoading, setLoginWithGoogleLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const {
|
||||
data: loginWithGoogleResult,
|
||||
loading: loginWithGoogleLoading,
|
||||
execute: loginWithGoogleCall,
|
||||
} = useApi(loginOrSignUpWithGoogle);
|
||||
const toast = useToast();
|
||||
const clientRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -38,21 +42,21 @@ export const GoogleAuthentication = ({
|
||||
ux_mode: 'popup',
|
||||
response_type: 'id_token',
|
||||
callback: async (resp: GoogleCodeClientResponse) => {
|
||||
setLoginWithGoogleLoading(true);
|
||||
|
||||
const result = await loginOrSignUpWithGoogle({
|
||||
await loginWithGoogleCall({
|
||||
idToken: resp.id_token,
|
||||
returnUrl: authReturnUrl,
|
||||
});
|
||||
const jsonRes = await result.json();
|
||||
|
||||
if (jsonRes.success) {
|
||||
onGoogleAuthenticated(jsonRes.userId);
|
||||
if (!loginWithGoogleResult) return;
|
||||
|
||||
if (loginWithGoogleResult.success) {
|
||||
onGoogleAuthenticated(loginWithGoogleResult.userId);
|
||||
} else {
|
||||
// Todo: Add useToast to handle error
|
||||
toast({
|
||||
message: t('loginForm.googleAuthenticationFailed'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setLoginWithGoogleLoading(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,11 +8,11 @@ import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import type { UserStatus } from '../../types/userTypes';
|
||||
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||
import { GoogleAuthentication } from './GoogleAuthentication';
|
||||
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface LoginRegisterFormProps {
|
||||
loginRegisterValue: string;
|
||||
@@ -37,7 +37,6 @@ export function LoginRegisterForm({
|
||||
authReturnUrl,
|
||||
onGoogleAuthenticated,
|
||||
}: LoginRegisterFormProps) {
|
||||
const [checkStatusLoading, setCheckStatusLoading] = useState<boolean>(false);
|
||||
const { t } = useTranslation('authentication');
|
||||
const textFieldRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -45,6 +44,11 @@ export function LoginRegisterForm({
|
||||
const [touched, setTouched] = useState<boolean>(false);
|
||||
const inputError: boolean = touched && !!error;
|
||||
const toast = useToast();
|
||||
const {
|
||||
data: userStatus,
|
||||
loading: userStatusLoading,
|
||||
execute: execUserStatus,
|
||||
} = useApi(getUserStatusByPhoneNumberOrEmail);
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
@@ -89,23 +93,21 @@ export function LoginRegisterForm({
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (validateInput(loginRegisterValue, authType, false)) {
|
||||
setCheckStatusLoading(true);
|
||||
const result = await getUserStatusByPhoneNumberOrEmail({
|
||||
await execUserStatus({
|
||||
phoneNumber:
|
||||
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
||||
email: authType === 'email' ? loginRegisterValue : undefined,
|
||||
});
|
||||
const jsonResult = await result.json();
|
||||
|
||||
if (jsonResult.success) {
|
||||
onLoginRegisterSubmit(loginRegisterValue, jsonResult.userStatus);
|
||||
} else {
|
||||
toast({
|
||||
message: jsonResult.message,
|
||||
severity: 'error',
|
||||
});
|
||||
if (!userStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userStatus.success) {
|
||||
onLoginRegisterSubmit(loginRegisterValue, userStatus.userStatus);
|
||||
} else {
|
||||
toast({ message: userStatus.message, severity: 'error' });
|
||||
}
|
||||
setCheckStatusLoading(false);
|
||||
} else {
|
||||
inputRef.current?.focus();
|
||||
validateInput(loginRegisterValue, authType);
|
||||
@@ -151,14 +153,14 @@ export function LoginRegisterForm({
|
||||
/>
|
||||
|
||||
<Stack spacing={2}>
|
||||
<Button loading={checkStatusLoading} onClick={handleSubmit}>
|
||||
<Button loading={userStatusLoading} onClick={handleSubmit}>
|
||||
{t('loginForm.submitButton')}
|
||||
</Button>
|
||||
|
||||
<GoogleAuthentication
|
||||
authReturnUrl={authReturnUrl}
|
||||
onGoogleAuthenticated={onGoogleAuthenticated}
|
||||
disabled={checkStatusLoading}
|
||||
disabled={userStatusLoading}
|
||||
/>
|
||||
</Stack>
|
||||
</AuthenticationCard>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Edit2 } from 'iconsax-react';
|
||||
import DigitInput from '@/components/components/DigitsInput';
|
||||
import type { AuthMode, AuthType } from '../../types/authTypes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import type { LoginRequest } from '../../types/userTypes';
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
} from '../../api/authorizationAPI';
|
||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
interface OtpVerifyFormProps {
|
||||
value: string;
|
||||
@@ -39,13 +39,19 @@ export function OtpVerifyForm({
|
||||
const [otpCode, setOtpCode] = useState<string>('');
|
||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
||||
useState<boolean>(false);
|
||||
const { t } = useTranslation('authentication');
|
||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [resendLoading, setResendLoading] = useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
const { loading: smsResendLoading, execute: smsResendCall } =
|
||||
useApi(sendSmsOtp);
|
||||
const { loading: emailResendLoading, execute: emailResendCall } =
|
||||
useApi(sendEmailOtp);
|
||||
const {
|
||||
data: loginSignUpResult,
|
||||
loading: loginSignUpLoading,
|
||||
execute: loginSignUpCall,
|
||||
} = useApi(loginOrSignUpWithOtp);
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
@@ -61,17 +67,14 @@ export function OtpVerifyForm({
|
||||
}, [resendTimer]);
|
||||
|
||||
const handleResendOTPCode = async () => {
|
||||
setResendLoading(true);
|
||||
|
||||
if (authType === 'phone') {
|
||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
||||
await smsResendCall({ phoneNumber: countryCode + value });
|
||||
} else {
|
||||
await sendEmailOtp({ email: value });
|
||||
await emailResendCall({ email: value });
|
||||
}
|
||||
|
||||
setResendTimer(120);
|
||||
setCanResend(false);
|
||||
setResendLoading(false);
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
@@ -90,7 +93,6 @@ export function OtpVerifyForm({
|
||||
|
||||
const handleLoginOrSignUp = async () => {
|
||||
setOtpDigitInvalid(false);
|
||||
setVerifyStatusLoading(true);
|
||||
|
||||
const loginRequest: LoginRequest = {
|
||||
otpCode: otpCode,
|
||||
@@ -98,16 +100,19 @@ export function OtpVerifyForm({
|
||||
email: authType === 'email' ? value : undefined,
|
||||
returnUrl: authReturnUrl,
|
||||
};
|
||||
const result = await loginOrSignUpWithOtp(loginRequest);
|
||||
const jsonRes = await result.json();
|
||||
await loginSignUpCall(loginRequest);
|
||||
|
||||
if (jsonRes.success) {
|
||||
if (!loginSignUpResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginSignUpResult && loginSignUpResult.success) {
|
||||
setIsStatusSuccess(true);
|
||||
|
||||
if (jsonRes.registeredWithOutPhoneNumber) {
|
||||
onVerifyPhoneNumber(jsonRes.userId);
|
||||
if (loginSignUpResult.registeredWithOutPhoneNumber) {
|
||||
onVerifyPhoneNumber(loginSignUpResult.userId);
|
||||
} else {
|
||||
onOTPVerified(jsonRes.userId);
|
||||
onOTPVerified(loginSignUpResult.userId);
|
||||
}
|
||||
|
||||
toast({
|
||||
@@ -121,12 +126,10 @@ export function OtpVerifyForm({
|
||||
setIsStatusSuccess(false);
|
||||
|
||||
toast({
|
||||
message: jsonRes.message,
|
||||
message: loginSignUpResult.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setVerifyStatusLoading(false);
|
||||
};
|
||||
|
||||
const otpMessageFn = (): string => {
|
||||
@@ -189,7 +192,7 @@ export function OtpVerifyForm({
|
||||
onChange={(value) => setOtpCode(value)}
|
||||
/>
|
||||
|
||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
||||
<Button onClick={handleVerifyOTP} loading={loginSignUpLoading}>
|
||||
{authMode === 'register'
|
||||
? t('verify.confirmAndContinue')
|
||||
: t('verify.confirmAndLogin')}
|
||||
@@ -208,7 +211,7 @@ export function OtpVerifyForm({
|
||||
|
||||
<Button
|
||||
variant="text"
|
||||
loading={resendLoading}
|
||||
loading={smsResendLoading || emailResendLoading}
|
||||
sx={{ width: 'auto' }}
|
||||
onClick={canResend ? handleResendOTPCode : undefined}
|
||||
>
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Box, Button, Stack, Typography } from '@mui/material';
|
||||
import { Edit2 } from 'iconsax-react';
|
||||
import DigitInput from '@/components/components/DigitsInput';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { AuthenticationCard } from '../AuthenticationCard';
|
||||
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
|
||||
import { confirmSmsOtp, sendSmsOtp } from '../../api/authorizationAPI';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
interface VerifyPhoneNumberProps {
|
||||
value: string;
|
||||
@@ -26,13 +26,17 @@ export function VerifyPhoneNumber({
|
||||
const [otpCode, setOtpCode] = useState<string>('');
|
||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
||||
useState<boolean>(false);
|
||||
const { t } = useTranslation('authentication');
|
||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [resendLoading, setResendLoading] = useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
const { loading: smsResendLoading, execute: smsResendCall } =
|
||||
useApi(sendSmsOtp);
|
||||
const {
|
||||
data: confirmSmsOtpResult,
|
||||
loading: confirmSmsOtpLoading,
|
||||
execute: confirmSmsOtpCall,
|
||||
} = useApi(confirmSmsOtp);
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
@@ -48,13 +52,10 @@ export function VerifyPhoneNumber({
|
||||
}, [resendTimer]);
|
||||
|
||||
const handleResendOTPCode = async () => {
|
||||
setResendLoading(true);
|
||||
|
||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
||||
await smsResendCall({ phoneNumber: countryCode + value });
|
||||
|
||||
setResendTimer(120);
|
||||
setCanResend(false);
|
||||
setResendLoading(false);
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
@@ -68,16 +69,16 @@ export function VerifyPhoneNumber({
|
||||
setOtpDigitInvalid(true);
|
||||
} else {
|
||||
setOtpDigitInvalid(false);
|
||||
setVerifyStatusLoading(true);
|
||||
|
||||
const confirmSmsOtpRequest: ConfirmSmsOtpRequest = {
|
||||
otpCode: otpCode,
|
||||
phoneNumber: countryCode + value,
|
||||
};
|
||||
const result = await confirmSmsOtp(confirmSmsOtpRequest);
|
||||
const jsonRes = await result.json();
|
||||
await confirmSmsOtpCall(confirmSmsOtpRequest);
|
||||
|
||||
if (jsonRes.success) {
|
||||
if (!confirmSmsOtpResult) return;
|
||||
|
||||
if (confirmSmsOtpResult.success) {
|
||||
setIsStatusSuccess(true);
|
||||
toast({
|
||||
message: t('verify.youHaveSuccessfullyLoggedIn'),
|
||||
@@ -88,11 +89,11 @@ export function VerifyPhoneNumber({
|
||||
setIsStatusSuccess(false);
|
||||
toast({
|
||||
message:
|
||||
jsonRes.message ?? t('verify.theVerificationCodeIsIncorrect'),
|
||||
confirmSmsOtpResult.message ??
|
||||
t('verify.theVerificationCodeIsIncorrect'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
setVerifyStatusLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,7 +135,7 @@ export function VerifyPhoneNumber({
|
||||
onChange={(value) => setOtpCode(value)}
|
||||
/>
|
||||
|
||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
||||
<Button onClick={handleVerifyOTP} loading={confirmSmsOtpLoading}>
|
||||
{t('verify.confirmAndLogin')}
|
||||
</Button>
|
||||
</AuthenticationCard>
|
||||
@@ -151,7 +152,7 @@ export function VerifyPhoneNumber({
|
||||
|
||||
<Button
|
||||
variant="text"
|
||||
loading={resendLoading}
|
||||
loading={smsResendLoading}
|
||||
sx={{ width: 'auto' }}
|
||||
onClick={canResend ? handleResendOTPCode : undefined}
|
||||
>
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { AuthType } from '../../types/authTypes';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import { resetPassword } from '../../api/authorizationAPI';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface ChangePasswordProps {
|
||||
onEditInfo: () => void;
|
||||
@@ -49,9 +50,12 @@ export const ChangePassword = ({
|
||||
useState<boolean>(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const confirmInputRef = useRef<HTMLInputElement>(null);
|
||||
const [changePasswordLoading, setChangePasswordLoading] =
|
||||
useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
const {
|
||||
data: resetPasswordData,
|
||||
loading: resetPasswordLoading,
|
||||
execute: resetPasswordCall,
|
||||
} = useApi(resetPassword);
|
||||
|
||||
const passwordValidationRules = [
|
||||
{ title: t('forgetPassword.includingANumber'), validator: containsNumber },
|
||||
@@ -79,8 +83,6 @@ export const ChangePassword = ({
|
||||
setConfirmInputTouched(true);
|
||||
confirmInputRef.current?.focus();
|
||||
} else {
|
||||
setChangePasswordLoading(true);
|
||||
|
||||
const apiRequest: ResetPasswordRequest = {
|
||||
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||
phoneNumber:
|
||||
@@ -91,10 +93,11 @@ export const ChangePassword = ({
|
||||
confirmNewPassword: confirmPassValue,
|
||||
};
|
||||
|
||||
const result = await resetPassword(apiRequest);
|
||||
const jsonRes = await result.json();
|
||||
await resetPasswordCall(apiRequest);
|
||||
|
||||
if (jsonRes.success) {
|
||||
if (!resetPasswordData) return;
|
||||
|
||||
if (resetPasswordData.success) {
|
||||
onPasswordChanged();
|
||||
toast({
|
||||
message: t('forgetPassword.passwordChangedSuccessfully'),
|
||||
@@ -102,12 +105,10 @@ export const ChangePassword = ({
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
message: jsonRes.message,
|
||||
message: resetPasswordData.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setChangePasswordLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -249,7 +250,7 @@ export const ChangePassword = ({
|
||||
/>
|
||||
|
||||
<Stack spacing={1}>
|
||||
<Button loading={changePasswordLoading} onClick={handleSubmit}>
|
||||
<Button loading={resetPasswordLoading} onClick={handleSubmit}>
|
||||
{t('forgetPassword.confirm')}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
sendForgetPassCode,
|
||||
} from '../../api/authorizationAPI';
|
||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
interface ForgetPasswordOtpProps {
|
||||
forgettedPasswordInfo: string;
|
||||
@@ -35,13 +36,20 @@ export function ForgetPasswordOtp({
|
||||
const [otpCode, setOtpCode] = useState<string>('');
|
||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
||||
useState<boolean>(false);
|
||||
const { t } = useTranslation('authentication');
|
||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [resendLoading, setResendLoading] = useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
const {
|
||||
data: sendForgetPassCodeData,
|
||||
loading: sendForgetPassCodeLoading,
|
||||
execute: sendForgetPassCodeCall,
|
||||
} = useApi(sendForgetPassCode);
|
||||
const {
|
||||
data: confirmForgetPassCodeData,
|
||||
loading: confirmForgetPassCodeLoading,
|
||||
execute: confirmForgetPassCodeCall,
|
||||
} = useApi(confirmForgetPassCode);
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
@@ -57,18 +65,15 @@ export function ForgetPasswordOtp({
|
||||
}, [resendTimer]);
|
||||
|
||||
const handleResendOTPCode = async () => {
|
||||
setResendLoading(true);
|
||||
|
||||
const sendCodeRequest: SendForgetPassCodeRequest = {
|
||||
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||
phoneNumber:
|
||||
infoType === 'phone' ? countryCode + forgettedPasswordInfo : undefined,
|
||||
};
|
||||
const result = await sendForgetPassCode(sendCodeRequest);
|
||||
await sendForgetPassCodeCall(sendCodeRequest);
|
||||
|
||||
setResendTimer(120);
|
||||
setCanResend(false);
|
||||
setResendLoading(false);
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
@@ -82,7 +87,6 @@ export function ForgetPasswordOtp({
|
||||
setOtpDigitInvalid(true);
|
||||
} else {
|
||||
setOtpDigitInvalid(false);
|
||||
setVerifyStatusLoading(true);
|
||||
|
||||
// Change setTimeout to api call
|
||||
const apiRequest: ConfirmForgetPassCodeRequest = {
|
||||
@@ -94,21 +98,20 @@ export function ForgetPasswordOtp({
|
||||
code: otpCode,
|
||||
};
|
||||
|
||||
const result = await confirmForgetPassCode(apiRequest);
|
||||
const jsonRes = await result.json();
|
||||
confirmForgetPassCodeCall(apiRequest);
|
||||
|
||||
if (jsonRes.success) {
|
||||
if (!confirmForgetPassCodeData) return;
|
||||
|
||||
if (confirmForgetPassCodeData.success) {
|
||||
setIsStatusSuccess(true);
|
||||
onOTPVerified(otpCode);
|
||||
} else {
|
||||
setIsStatusSuccess(false);
|
||||
toast({
|
||||
message: jsonRes.message,
|
||||
message: confirmForgetPassCodeData.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setVerifyStatusLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,7 +161,10 @@ export function ForgetPasswordOtp({
|
||||
onChange={(value) => setOtpCode(value)}
|
||||
/>
|
||||
|
||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
||||
<Button
|
||||
onClick={handleVerifyOTP}
|
||||
loading={confirmForgetPassCodeLoading}
|
||||
>
|
||||
{t('forgetPassword.confirm')}
|
||||
</Button>
|
||||
</AuthenticationCard>
|
||||
@@ -175,7 +181,7 @@ export function ForgetPasswordOtp({
|
||||
|
||||
<Button
|
||||
variant="text"
|
||||
loading={resendLoading}
|
||||
loading={sendForgetPassCodeLoading}
|
||||
sx={{ width: 'auto' }}
|
||||
onClick={canResend ? handleResendOTPCode : undefined}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { SendForgetPassCodeRequest } from '../../types/userTypes';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
|
||||
export interface ForgettedPasswordInfoProps {
|
||||
forgettedPasswordInfo: string;
|
||||
@@ -37,9 +38,13 @@ export function ForgettedPasswordInfo({
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [error, setError] = useState<string>();
|
||||
const [touched, setTouched] = useState<boolean>(false);
|
||||
const [sendCodeLoading, setSendCodeLoading] = useState<boolean>(false);
|
||||
const inputError: boolean = touched && !!error;
|
||||
const toast = useToast();
|
||||
const {
|
||||
data: sendForgetPassCodeData,
|
||||
loading: sendForgetPassCodeLoading,
|
||||
execute: sendForgetPassCodeCall,
|
||||
} = useApi(sendForgetPassCode);
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
@@ -80,8 +85,6 @@ export function ForgettedPasswordInfo({
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (validateInput(forgettedPasswordInfo, infoType, false)) {
|
||||
setSendCodeLoading(true);
|
||||
|
||||
const sendCodeRequest: SendForgetPassCodeRequest = {
|
||||
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||
phoneNumber:
|
||||
@@ -89,17 +92,17 @@ export function ForgettedPasswordInfo({
|
||||
? countryCode + forgettedPasswordInfo
|
||||
: undefined,
|
||||
};
|
||||
const result = await sendForgetPassCode(sendCodeRequest);
|
||||
const jsonRes = await result.json();
|
||||
sendForgetPassCodeCall(sendCodeRequest);
|
||||
|
||||
if (!jsonRes.success) {
|
||||
if (!sendForgetPassCodeData) return;
|
||||
|
||||
if (!sendForgetPassCodeData.success) {
|
||||
toast({
|
||||
message: jsonRes.message,
|
||||
message: sendForgetPassCodeData.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setSendCodeLoading(false);
|
||||
onVerifyOtp(forgettedPasswordInfo);
|
||||
} else {
|
||||
inputRef.current?.focus();
|
||||
@@ -151,7 +154,7 @@ export function ForgettedPasswordInfo({
|
||||
/>
|
||||
|
||||
<Stack spacing={2}>
|
||||
<Button loading={sendCodeLoading} onClick={handleSubmit}>
|
||||
<Button loading={sendForgetPassCodeLoading} onClick={handleSubmit}>
|
||||
{t('forgetPassword.confirm')}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -1,24 +1,65 @@
|
||||
import { useState } from 'react';
|
||||
import type { ApiResponse } from '@/types/apiResponse';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import type { AxiosError } from 'axios';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
type ApiFunction<T, P> = (params?: P) => Promise<{ data: T }>;
|
||||
// Define options for the hook
|
||||
interface UseApiOptions {
|
||||
// If true, the API call will be executed immediately on mount
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export function useApi<T, P>(apiFunction: ApiFunction<T, P>) {
|
||||
export function useApi<T extends ApiResponse, P extends any[]>(
|
||||
apiFunction: (...args: P) => Promise<{ data: T }>,
|
||||
options: UseApiOptions = {},
|
||||
) {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const execute = async (params?: P) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await apiFunction(params);
|
||||
setData(response.data);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const execute = useCallback(
|
||||
async (...args: P) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await apiFunction(...args);
|
||||
|
||||
setData(response.data);
|
||||
} catch (err: unknown) {
|
||||
const axisoError: AxiosError = err as AxiosError;
|
||||
|
||||
if (axisoError.response?.status === 401) {
|
||||
navigate('/login');
|
||||
}
|
||||
|
||||
if (axisoError.response?.status === 500) {
|
||||
toast({
|
||||
message: t('messages.serverError'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[apiFunction, navigate, t, toast],
|
||||
);
|
||||
|
||||
// If the 'immediate' option is true, execute the function on mount
|
||||
useEffect(() => {
|
||||
if (options.immediate) {
|
||||
// We pass undefined as params for the initial call.
|
||||
execute(...(undefined as unknown as P));
|
||||
}
|
||||
};
|
||||
}, [execute, options.immediate]);
|
||||
|
||||
return { data, loading, error, execute };
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Function to get the token from local storage or state management
|
||||
const getToken = () => localStorage.getItem('authToken');
|
||||
const getToken = () => sessionStorage.getItem('authToken');
|
||||
|
||||
const apiClient = axios.create({
|
||||
// Define the base URL for all API requests
|
||||
baseURL: 'https://accounts.business-harmony.com/swagger/index.html',
|
||||
baseURL: 'https://accounts.business-harmony.com/api/',
|
||||
|
||||
// Set a timeout for requests (e.g., 10 seconds)
|
||||
timeout: 10000,
|
||||
|
||||
Reference in New Issue
Block a user