7
.env
7
.env
@@ -1 +1,6 @@
|
|||||||
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
|
||||||
|
VITE_API_URL=https://accounts.business-harmony.com/api/
|
||||||
|
VITE_IDENTITY_URL=https://accounts.business-harmony.com/connect/token
|
||||||
|
VITE_IDENTITY_CLIENT_ID=harmony_identity
|
||||||
|
VITE_IDENTITY_SCOPE=openid profile offline_access harmony_identity
|
||||||
1049
package-lock.json
generated
1049
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,8 @@
|
|||||||
"loginWithGoogle": "Login with google",
|
"loginWithGoogle": "Login with google",
|
||||||
"emailIsInvalid": "Email is invalid",
|
"emailIsInvalid": "Email is invalid",
|
||||||
"phoneNumberIsInvalid": "Phone number 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": "Verify",
|
"verify": "Verify",
|
||||||
|
|||||||
@@ -246,5 +246,9 @@
|
|||||||
"yemen": "Yemen",
|
"yemen": "Yemen",
|
||||||
"zambia": "Zambia",
|
"zambia": "Zambia",
|
||||||
"zimbabwe": "Zimbabwe"
|
"zimbabwe": "Zimbabwe"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"noResualtFound": "No result found.",
|
||||||
|
"serverError": "Internal server error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"loginWithGoogle": "ورود با گوگل",
|
"loginWithGoogle": "ورود با گوگل",
|
||||||
"emailIsInvalid": "ایمیل وارد شده نامعتبر میباشد",
|
"emailIsInvalid": "ایمیل وارد شده نامعتبر میباشد",
|
||||||
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
||||||
"thisFieldIsRequired": "این فیلد الزامی است"
|
"thisFieldIsRequired": "این فیلد الزامی است",
|
||||||
|
"googleAuthenticationFailed": "ورود با گوگل با خطا مواجه شد"
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"verify": "اعتبارسنجی",
|
"verify": "اعتبارسنجی",
|
||||||
|
|||||||
@@ -185,7 +185,8 @@
|
|||||||
"zimbabwe": "زیمبابوه"
|
"zimbabwe": "زیمبابوه"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"noResualtFound": "نتیجه ای یافت نشد."
|
"noResualtFound": "نتیجه ای یافت نشد.",
|
||||||
|
"serverError": "خطای سمت سرور"
|
||||||
},
|
},
|
||||||
"side": {
|
"side": {
|
||||||
"account": "حساب کاربری",
|
"account": "حساب کاربری",
|
||||||
|
|||||||
13
src/components/ProtectedRoute.tsx
Normal file
13
src/components/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { getAccessToken } from '@/lib/apiClient';
|
||||||
|
import { type PropsWithChildren } from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const ProtectedRoute = ({ children }: PropsWithChildren) => {
|
||||||
|
if (!getAccessToken()) {
|
||||||
|
// If no token, redirect to login page
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If token exists, render the children components
|
||||||
|
return children;
|
||||||
|
};
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { ApiResponse } from '@/types/apiResponse';
|
import type { ApiResponse } from '@/types/apiResponse';
|
||||||
import type { FetchPromise } from '@/types/fetchPromise';
|
|
||||||
import type {
|
import type {
|
||||||
CompleteUserInformationRequest,
|
|
||||||
ConfirmEmailOtpRequest,
|
ConfirmEmailOtpRequest,
|
||||||
ConfirmForgetPassCodeRequest,
|
ConfirmForgetPassCodeRequest,
|
||||||
ConfirmOtpResponse,
|
ConfirmOtpResponse,
|
||||||
@@ -19,86 +17,66 @@ import type {
|
|||||||
SendForgetPassCodeRequest,
|
SendForgetPassCodeRequest,
|
||||||
SendSmsOtpRequest,
|
SendSmsOtpRequest,
|
||||||
} from '../types/userTypes';
|
} from '../types/userTypes';
|
||||||
|
import apiClient from '@/lib/apiClient';
|
||||||
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',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// GetUserStatusByPhoneNumberOrEmail
|
// GetUserStatusByPhoneNumberOrEmail
|
||||||
|
|
||||||
export const getUserStatusByPhoneNumberOrEmail = async (
|
export const getUserStatusByPhoneNumberOrEmail = async (
|
||||||
body: GetUserStatusByPhoneNumberOrEmailRequest,
|
body: GetUserStatusByPhoneNumberOrEmailRequest,
|
||||||
) => {
|
) => {
|
||||||
return fetchRequest<GetUserStatusByPhoneNumberOrEmailResponse>(
|
return apiClient.post<GetUserStatusByPhoneNumberOrEmailResponse>(
|
||||||
'User/GetUserStatusByPhoneNumberOrEmail',
|
'User/GetUserStatusByPhoneNumberOrEmail',
|
||||||
body,
|
body,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginOrSignUpWithOtp = async (body: LoginRequest) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
export const sendForgetPassCode = async (body: SendForgetPassCodeRequest) => {
|
||||||
return fetchRequest<ApiResponse>('User/SendForgetPassCode', body);
|
return apiClient.post<ApiResponse>('User/SendForgetPassCode', body);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const confirmForgetPassCode = async (
|
export const confirmForgetPassCode = async (
|
||||||
body: ConfirmForgetPassCodeRequest,
|
body: ConfirmForgetPassCodeRequest,
|
||||||
) => {
|
) => {
|
||||||
return fetchRequest<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body);
|
return apiClient.post<ConfirmOtpResponse>('User/ConfirmForgetPassCode', body);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginOrSignUpWithGoogle = async (
|
export const loginOrSignUpWithGoogle = async (
|
||||||
body: LoginOrSignUpWithGoogleRequest,
|
body: LoginOrSignUpWithGoogleRequest,
|
||||||
) => {
|
) => {
|
||||||
return fetchRequest<LoginOrSignUpWithGoogleResponse>(
|
return apiClient.post<LoginOrSignUpWithGoogleResponse>(
|
||||||
'User/LoginOrSignUpWithGoogle',
|
'User/LoginOrSignUpWithGoogle',
|
||||||
body,
|
body,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const completeUserInformation = async (
|
|
||||||
body: CompleteUserInformationRequest,
|
|
||||||
) => {
|
|
||||||
return fetchRequest<ApiResponse>('User/CompleteUserInformation', body);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const logOut = async () => {
|
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 => {
|
export const AuthenticationSteps = (): JSX.Element => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const DEFAULT_RETURN_URL = '/profile';
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const authReturnUrl: string =
|
const authReturnUrl: string | null = searchParams.get('returnUrl');
|
||||||
searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL;
|
const authReturnUrlOrDefault: string =
|
||||||
|
authReturnUrl ?? import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL;
|
||||||
const [authMode, setAuthMode] = useState<AuthMode>('register');
|
const [authMode, setAuthMode] = useState<AuthMode>('register');
|
||||||
const [authType, setAuthType] = useState<AuthType>('phone');
|
const [authType, setAuthType] = useState<AuthType>('phone');
|
||||||
const [currentStep, setCurrentStep] = useState<AuthStep>('emailOrPhone');
|
const [currentStep, setCurrentStep] = useState<AuthStep>('emailOrPhone');
|
||||||
@@ -66,8 +66,8 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const redirectToReturnUrl = () => {
|
const redirectToReturnUrl = () => {
|
||||||
if (authReturnUrl === DEFAULT_RETURN_URL) {
|
if (!authReturnUrl) {
|
||||||
navigate(DEFAULT_RETURN_URL);
|
navigate(import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL);
|
||||||
} else {
|
} else {
|
||||||
location.href = authReturnUrl;
|
location.href = authReturnUrl;
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
<>
|
<>
|
||||||
{currentStep === 'emailOrPhone' && (
|
{currentStep === 'emailOrPhone' && (
|
||||||
<LoginRegisterForm
|
<LoginRegisterForm
|
||||||
authReturnUrl={authReturnUrl}
|
authReturnUrl={authReturnUrlOrDefault}
|
||||||
onGoogleAuthenticated={handleUserLoggedIn}
|
onGoogleAuthenticated={handleUserLoggedIn}
|
||||||
countryCode={countryCode}
|
countryCode={countryCode}
|
||||||
setCountryCode={setCountryCode}
|
setCountryCode={setCountryCode}
|
||||||
@@ -92,7 +92,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
{currentStep === 'verify' && (
|
{currentStep === 'verify' && (
|
||||||
<OtpVerifyForm
|
<OtpVerifyForm
|
||||||
onVerifyPhoneNumber={handleConfrimPhoneNumber}
|
onVerifyPhoneNumber={handleConfrimPhoneNumber}
|
||||||
authReturnUrl={authReturnUrl}
|
authReturnUrl={authReturnUrlOrDefault}
|
||||||
countryCode={countryCode}
|
countryCode={countryCode}
|
||||||
onOTPVerified={handleUserLoggedIn}
|
onOTPVerified={handleUserLoggedIn}
|
||||||
onEditValue={() => setCurrentStep('emailOrPhone')}
|
onEditValue={() => setCurrentStep('emailOrPhone')}
|
||||||
@@ -104,7 +104,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
|
|
||||||
{currentStep === 'enterPassword' && (
|
{currentStep === 'enterPassword' && (
|
||||||
<EnterPasswordForm
|
<EnterPasswordForm
|
||||||
authReturnUrl={authReturnUrl}
|
authReturnUrl={authReturnUrlOrDefault}
|
||||||
loginRegisterValue={loginRegisterValue}
|
loginRegisterValue={loginRegisterValue}
|
||||||
countryCode={countryCode}
|
countryCode={countryCode}
|
||||||
authType={authType}
|
authType={authType}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { AuthenticationCard } from '../AuthenticationCard';
|
|||||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||||
import { sendSmsOtp } from '../../api/authorizationAPI';
|
import { sendSmsOtp } from '../../api/authorizationAPI';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface CompleteSignUpProps {
|
export interface CompleteSignUpProps {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -30,7 +31,7 @@ export const CompleteSignUp = ({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [touched, setTouched] = useState<boolean>(false);
|
const [touched, setTouched] = useState<boolean>(false);
|
||||||
const inputError: boolean = touched && !!error;
|
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 isPhoneValid = (code: string, phone: string) => {
|
||||||
const phoneNumber = parsePhoneNumberFromString(code + phone);
|
const phoneNumber = parsePhoneNumberFromString(code + phone);
|
||||||
@@ -61,12 +62,8 @@ export const CompleteSignUp = ({
|
|||||||
if (!value || !isPhoneValid(countryCode, value)) {
|
if (!value || !isPhoneValid(countryCode, value)) {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
setSendOtpLoading(true);
|
await sendSmsCall({ phoneNumber: countryCode + value });
|
||||||
|
|
||||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
|
||||||
onCompleteSignUp(countryCode, value);
|
onCompleteSignUp(countryCode, value);
|
||||||
|
|
||||||
setSendOtpLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +107,7 @@ export const CompleteSignUp = ({
|
|||||||
sx={{ my: 4 }}
|
sx={{ my: 4 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button loading={sendOtpLoading} onClick={handleCompleteSignUp}>
|
<Button loading={sendSmsLoading} onClick={handleCompleteSignUp}>
|
||||||
{t('verify.confirmAndContinue')}
|
{t('verify.confirmAndContinue')}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Toast } from '@/components/Toast';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { AuthType } from '../../types/authTypes';
|
import type { AuthType } from '../../types/authTypes';
|
||||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||||
@@ -20,6 +19,8 @@ import {
|
|||||||
sendSmsOtp,
|
sendSmsOtp,
|
||||||
} from '../../api/authorizationAPI';
|
} from '../../api/authorizationAPI';
|
||||||
import type { PasswordLoginRequest } from '../../types/userTypes';
|
import type { PasswordLoginRequest } from '../../types/userTypes';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface EnterPasswordFormProps {
|
export interface EnterPasswordFormProps {
|
||||||
onEditValue: () => void;
|
onEditValue: () => void;
|
||||||
@@ -47,11 +48,16 @@ export const EnterPasswordForm = ({
|
|||||||
const [inputTouched, setInputTouched] = useState<boolean>(false);
|
const [inputTouched, setInputTouched] = useState<boolean>(false);
|
||||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [loginLoading, setLoginLoading] = useState<boolean>(false);
|
const toast = useToast();
|
||||||
const [isLoginStatusSuccess, setIsLoginStatusSuccess] = useState<boolean>();
|
const { loading: smsResendLoading, execute: smsResendCall } =
|
||||||
const [loginAlertOpen, setLoginAlertOpen] = useState<boolean>(false);
|
useApi(sendSmsOtp);
|
||||||
const [loginFailedMessage, setLoginFailedMessage] = useState<string>('');
|
const { loading: emailResendLoading, execute: emailResendCall } =
|
||||||
const [sendOtpLoading, setSendOtpLoading] = useState<boolean>(false);
|
useApi(sendEmailOtp);
|
||||||
|
const {
|
||||||
|
data: loginWithPassResult,
|
||||||
|
loading: loginWithPassLoading,
|
||||||
|
execute: loginWithPassCall,
|
||||||
|
} = useApi(loginWithPassword);
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
setInputTouched(true);
|
setInputTouched(true);
|
||||||
@@ -61,8 +67,6 @@ export const EnterPasswordForm = ({
|
|||||||
if (!passValue) {
|
if (!passValue) {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
setLoginLoading(true);
|
|
||||||
|
|
||||||
const apiRequest: PasswordLoginRequest = {
|
const apiRequest: PasswordLoginRequest = {
|
||||||
phoneNumber:
|
phoneNumber:
|
||||||
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
||||||
@@ -70,46 +74,38 @@ export const EnterPasswordForm = ({
|
|||||||
password: passValue,
|
password: passValue,
|
||||||
returnUrl: authReturnUrl,
|
returnUrl: authReturnUrl,
|
||||||
};
|
};
|
||||||
const result = await loginWithPassword(apiRequest);
|
await loginWithPassCall(apiRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!loginWithPassResult) return;
|
||||||
setIsLoginStatusSuccess(true);
|
|
||||||
onLoggedIn(jsonRes.userId);
|
if (loginWithPassResult.success) {
|
||||||
|
onLoggedIn(loginWithPassResult.userId);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
message: t('verify.youHaveSuccessfullyLoggedIn'),
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsLoginStatusSuccess(false);
|
toast({
|
||||||
setLoginFailedMessage(jsonRes.message);
|
message: loginWithPassResult.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setLoginAlertOpen(true);
|
|
||||||
setLoginLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWithOtp = async () => {
|
const handleLoginWithOtp = async () => {
|
||||||
setSendOtpLoading(true);
|
|
||||||
|
|
||||||
if (authType === 'phone') {
|
if (authType === 'phone') {
|
||||||
await sendSmsOtp({ phoneNumber: countryCode + loginRegisterValue });
|
await smsResendCall({ phoneNumber: countryCode + loginRegisterValue });
|
||||||
} else {
|
} else {
|
||||||
await sendEmailOtp({ email: loginRegisterValue });
|
await emailResendCall({ email: loginRegisterValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
setSendOtpLoading(false);
|
|
||||||
onLoginWithOTP();
|
onLoginWithOTP();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
open={loginAlertOpen}
|
|
||||||
onClose={() => setLoginAlertOpen(false)}
|
|
||||||
color={!isLoginStatusSuccess ? 'error' : 'success'}
|
|
||||||
>
|
|
||||||
{!isLoginStatusSuccess
|
|
||||||
? loginFailedMessage
|
|
||||||
: t('verify.youHaveSuccessfullyLoggedIn')}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -128,7 +124,7 @@ export const EnterPasswordForm = ({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ width: 'auto' }}
|
sx={{ width: 'auto' }}
|
||||||
endIcon={<Edit2 />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditValue}
|
onClick={onEditValue}
|
||||||
>
|
>
|
||||||
{emailOrPhone}
|
{emailOrPhone}
|
||||||
@@ -159,7 +155,11 @@ export const EnterPasswordForm = ({
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
>
|
>
|
||||||
{showPassword ? <Eye /> : <EyeSlash />}
|
{showPassword ? (
|
||||||
|
<Icon Component={Eye} />
|
||||||
|
) : (
|
||||||
|
<Icon Component={EyeSlash} />
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -171,14 +171,14 @@ export const EnterPasswordForm = ({
|
|||||||
onClick={handleLoginWithOtp}
|
onClick={handleLoginWithOtp}
|
||||||
sx={{ width: 'auto', mb: 2 }}
|
sx={{ width: 'auto', mb: 2 }}
|
||||||
variant="text"
|
variant="text"
|
||||||
loading={sendOtpLoading}
|
loading={emailResendLoading || smsResendLoading}
|
||||||
endIcon={<ArrowLeft />}
|
endIcon={<Icon Component={ArrowLeft} />}
|
||||||
>
|
>
|
||||||
{t('enterPassword.loginWithOneTimeCode')}
|
{t('enterPassword.loginWithOneTimeCode')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Button loading={loginLoading} onClick={handleSubmit}>
|
<Button loading={loginWithPassLoading} onClick={handleSubmit}>
|
||||||
{t('verify.confirmAndLogin')}
|
{t('verify.confirmAndLogin')}
|
||||||
</Button>
|
</Button>
|
||||||
<Link to="/forget-password">
|
<Link to="/forget-password">
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { GoogleCodeClientResponse } from '../../types/userTypes';
|
import type { GoogleCodeClientResponse } from '../../types/userTypes';
|
||||||
import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
||||||
import type { GUID } from '@/types/commonTypes';
|
import type { GUID } from '@/types/commonTypes';
|
||||||
import { Google } from 'iconsax-react';
|
import { Google } from 'iconsax-react';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface GoogleAuthenticationProps {
|
export interface GoogleAuthenticationProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
@@ -18,9 +20,12 @@ export const GoogleAuthentication = ({
|
|||||||
onGoogleAuthenticated,
|
onGoogleAuthenticated,
|
||||||
}: GoogleAuthenticationProps) => {
|
}: GoogleAuthenticationProps) => {
|
||||||
const { t } = useTranslation('authentication');
|
const { t } = useTranslation('authentication');
|
||||||
const [loginWithGoogleLoading, setLoginWithGoogleLoading] =
|
const {
|
||||||
useState<boolean>(false);
|
data: loginWithGoogleResult,
|
||||||
|
loading: loginWithGoogleLoading,
|
||||||
|
execute: loginWithGoogleCall,
|
||||||
|
} = useApi(loginOrSignUpWithGoogle);
|
||||||
|
const toast = useToast();
|
||||||
const clientRef = useRef<any>(null);
|
const clientRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,21 +42,21 @@ export const GoogleAuthentication = ({
|
|||||||
ux_mode: 'popup',
|
ux_mode: 'popup',
|
||||||
response_type: 'id_token',
|
response_type: 'id_token',
|
||||||
callback: async (resp: GoogleCodeClientResponse) => {
|
callback: async (resp: GoogleCodeClientResponse) => {
|
||||||
setLoginWithGoogleLoading(true);
|
await loginWithGoogleCall({
|
||||||
|
|
||||||
const result = await loginOrSignUpWithGoogle({
|
|
||||||
idToken: resp.id_token,
|
idToken: resp.id_token,
|
||||||
returnUrl: authReturnUrl,
|
returnUrl: authReturnUrl,
|
||||||
});
|
});
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!loginWithGoogleResult) return;
|
||||||
onGoogleAuthenticated(jsonRes.userId);
|
|
||||||
|
if (loginWithGoogleResult.success) {
|
||||||
|
onGoogleAuthenticated(loginWithGoogleResult.userId);
|
||||||
} else {
|
} else {
|
||||||
// Todo: Add useToast to handle error
|
toast({
|
||||||
|
message: t('loginForm.googleAuthenticationFailed'),
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoginWithGoogleLoading(false);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -73,7 +78,7 @@ export const GoogleAuthentication = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
loading={loginWithGoogleLoading}
|
loading={loginWithGoogleLoading}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<Google variant="Bold" />}
|
startIcon={<Icon Component={Google} variant="Bold" />}
|
||||||
>
|
>
|
||||||
{t('loginForm.loginWithGoogle')}
|
{t('loginForm.loginWithGoogle')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { AuthenticationCard } from '../AuthenticationCard';
|
|||||||
import { CountryCodeSelector } from '../CountryCodeSelector';
|
import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||||
import type { UserStatus } from '../../types/userTypes';
|
import type { UserStatus } from '../../types/userTypes';
|
||||||
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
||||||
import { Toast } from '@/components/Toast';
|
|
||||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||||
import { GoogleAuthentication } from './GoogleAuthentication';
|
import { GoogleAuthentication } from './GoogleAuthentication';
|
||||||
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
||||||
|
import { useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface LoginRegisterFormProps {
|
export interface LoginRegisterFormProps {
|
||||||
loginRegisterValue: string;
|
loginRegisterValue: string;
|
||||||
@@ -36,14 +37,18 @@ export function LoginRegisterForm({
|
|||||||
authReturnUrl,
|
authReturnUrl,
|
||||||
onGoogleAuthenticated,
|
onGoogleAuthenticated,
|
||||||
}: LoginRegisterFormProps) {
|
}: LoginRegisterFormProps) {
|
||||||
const [checkStatusLoading, setCheckStatusLoading] = useState<boolean>(false);
|
|
||||||
const { t } = useTranslation('authentication');
|
const { t } = useTranslation('authentication');
|
||||||
const textFieldRef = useRef<HTMLDivElement>(null);
|
const textFieldRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
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 inputError: boolean = touched && !!error;
|
const inputError: boolean = touched && !!error;
|
||||||
|
const toast = useToast();
|
||||||
|
const {
|
||||||
|
data: userStatus,
|
||||||
|
loading: userStatusLoading,
|
||||||
|
execute: execUserStatus,
|
||||||
|
} = useApi(getUserStatusByPhoneNumberOrEmail);
|
||||||
|
|
||||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newValue = event.target.value;
|
const newValue = event.target.value;
|
||||||
@@ -88,20 +93,21 @@ export function LoginRegisterForm({
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (validateInput(loginRegisterValue, authType, false)) {
|
if (validateInput(loginRegisterValue, authType, false)) {
|
||||||
setCheckStatusLoading(true);
|
await execUserStatus({
|
||||||
const result = await getUserStatusByPhoneNumberOrEmail({
|
|
||||||
phoneNumber:
|
phoneNumber:
|
||||||
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
||||||
email: authType === 'email' ? loginRegisterValue : undefined,
|
email: authType === 'email' ? loginRegisterValue : undefined,
|
||||||
});
|
});
|
||||||
const jsonResult = await result.json();
|
|
||||||
|
|
||||||
if (jsonResult.success) {
|
if (!userStatus) {
|
||||||
onLoginRegisterSubmit(loginRegisterValue, jsonResult.userStatus);
|
return;
|
||||||
} else {
|
}
|
||||||
setErrorMessage(jsonResult.message);
|
|
||||||
|
if (userStatus.success) {
|
||||||
|
onLoginRegisterSubmit(loginRegisterValue, userStatus.userStatus);
|
||||||
|
} else {
|
||||||
|
toast({ message: userStatus.message, severity: 'error' });
|
||||||
}
|
}
|
||||||
setCheckStatusLoading(false);
|
|
||||||
} else {
|
} else {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
validateInput(loginRegisterValue, authType);
|
validateInput(loginRegisterValue, authType);
|
||||||
@@ -112,14 +118,6 @@ export function LoginRegisterForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
color="error"
|
|
||||||
onClose={() => setErrorMessage(undefined)}
|
|
||||||
open={!!errorMessage}
|
|
||||||
>
|
|
||||||
{errorMessage}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
@@ -155,14 +153,14 @@ export function LoginRegisterForm({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Button loading={checkStatusLoading} onClick={handleSubmit}>
|
<Button loading={userStatusLoading} onClick={handleSubmit}>
|
||||||
{t('loginForm.submitButton')}
|
{t('loginForm.submitButton')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<GoogleAuthentication
|
<GoogleAuthentication
|
||||||
authReturnUrl={authReturnUrl}
|
authReturnUrl={authReturnUrl}
|
||||||
onGoogleAuthenticated={onGoogleAuthenticated}
|
onGoogleAuthenticated={onGoogleAuthenticated}
|
||||||
disabled={checkStatusLoading}
|
disabled={userStatusLoading}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Edit2 } from 'iconsax-react';
|
|||||||
import DigitInput from '@/components/components/DigitsInput';
|
import DigitInput from '@/components/components/DigitsInput';
|
||||||
import type { AuthMode, AuthType } from '../../types/authTypes';
|
import type { AuthMode, AuthType } from '../../types/authTypes';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Toast } from '@/components/Toast';
|
|
||||||
import { AuthenticationCard } from '../AuthenticationCard';
|
import { AuthenticationCard } from '../AuthenticationCard';
|
||||||
import type { LoginRequest } from '../../types/userTypes';
|
import type { LoginRequest } from '../../types/userTypes';
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +12,8 @@ import {
|
|||||||
sendSmsOtp,
|
sendSmsOtp,
|
||||||
} from '../../api/authorizationAPI';
|
} from '../../api/authorizationAPI';
|
||||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
interface OtpVerifyFormProps {
|
interface OtpVerifyFormProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -37,15 +38,20 @@ export function OtpVerifyForm({
|
|||||||
}: OtpVerifyFormProps) {
|
}: OtpVerifyFormProps) {
|
||||||
const [otpCode, setOtpCode] = useState<string>('');
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
const [verifyStatus, setVerifyStatus] = useState<'success' | 'failed'>();
|
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false);
|
|
||||||
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);
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
@@ -61,17 +67,14 @@ export function OtpVerifyForm({
|
|||||||
}, [resendTimer]);
|
}, [resendTimer]);
|
||||||
|
|
||||||
const handleResendOTPCode = async () => {
|
const handleResendOTPCode = async () => {
|
||||||
setResendLoading(true);
|
|
||||||
|
|
||||||
if (authType === 'phone') {
|
if (authType === 'phone') {
|
||||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
await smsResendCall({ phoneNumber: countryCode + value });
|
||||||
} else {
|
} else {
|
||||||
await sendEmailOtp({ email: value });
|
await emailResendCall({ email: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
setResendTimer(120);
|
setResendTimer(120);
|
||||||
setCanResend(false);
|
setCanResend(false);
|
||||||
setResendLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
const formatTime = (seconds: number) => {
|
||||||
@@ -90,7 +93,6 @@ export function OtpVerifyForm({
|
|||||||
|
|
||||||
const handleLoginOrSignUp = async () => {
|
const handleLoginOrSignUp = async () => {
|
||||||
setOtpDigitInvalid(false);
|
setOtpDigitInvalid(false);
|
||||||
setVerifyStatusLoading(true);
|
|
||||||
|
|
||||||
const loginRequest: LoginRequest = {
|
const loginRequest: LoginRequest = {
|
||||||
otpCode: otpCode,
|
otpCode: otpCode,
|
||||||
@@ -98,27 +100,39 @@ export function OtpVerifyForm({
|
|||||||
email: authType === 'email' ? value : undefined,
|
email: authType === 'email' ? value : undefined,
|
||||||
returnUrl: authReturnUrl,
|
returnUrl: authReturnUrl,
|
||||||
};
|
};
|
||||||
const result = await loginOrSignUpWithOtp(loginRequest);
|
await loginSignUpCall(loginRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!loginSignUpResult) {
|
||||||
setVerifyStatus('success');
|
return;
|
||||||
|
|
||||||
if (jsonRes.registeredWithOutPhoneNumber) {
|
|
||||||
onVerifyPhoneNumber(jsonRes.userId);
|
|
||||||
} else {
|
|
||||||
onOTPVerified(jsonRes.userId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setVerifyStatus('failed');
|
|
||||||
setErrorMessage(jsonRes.message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerifyAlertOpen(true);
|
if (loginSignUpResult && loginSignUpResult.success) {
|
||||||
setVerifyStatusLoading(false);
|
setIsStatusSuccess(true);
|
||||||
|
|
||||||
|
if (loginSignUpResult.registeredWithOutPhoneNumber) {
|
||||||
|
onVerifyPhoneNumber(loginSignUpResult.userId);
|
||||||
|
} else {
|
||||||
|
onOTPVerified(loginSignUpResult.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
message:
|
||||||
|
authMode === 'login'
|
||||||
|
? t('verify.youHaveSuccessfullyLoggedIn')
|
||||||
|
: t('verify.youHaveSuccessfullySignedIn'),
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setIsStatusSuccess(false);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
message: loginSignUpResult.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const otpMessage = (): string => {
|
const otpMessageFn = (): string => {
|
||||||
if (authType === 'phone' && authMode === 'login') {
|
if (authType === 'phone' && authMode === 'login') {
|
||||||
return t(
|
return t(
|
||||||
'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
|
'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
|
||||||
@@ -140,26 +154,11 @@ export function OtpVerifyForm({
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const toastMessage =
|
const otpMessage = otpMessageFn();
|
||||||
verifyStatus === 'failed'
|
|
||||||
? (errorMessage ?? t('verify.theVerificationCodeIsIncorrect'))
|
|
||||||
: verifyStatus === 'success' && authMode === 'register'
|
|
||||||
? t('verify.youHaveSuccessfullySignedIn')
|
|
||||||
: verifyStatus === 'success' && authMode === 'login'
|
|
||||||
? t('verify.youHaveSuccessfullyLoggedIn')
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
open={verifyAlertOpen}
|
|
||||||
onClose={() => setVerifyAlertOpen(false)}
|
|
||||||
color={verifyStatus === 'failed' ? 'error' : 'success'}
|
|
||||||
>
|
|
||||||
{toastMessage}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -176,7 +175,7 @@ export function OtpVerifyForm({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
endIcon={<Edit2 />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditValue}
|
onClick={onEditValue}
|
||||||
>
|
>
|
||||||
{authType === 'phone' ? countryCode + value : value}
|
{authType === 'phone' ? countryCode + value : value}
|
||||||
@@ -184,16 +183,16 @@ export function OtpVerifyForm({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||||
{otpMessage()}
|
{otpMessage}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<DigitInput
|
<DigitInput
|
||||||
error={otpDigitInvalid || verifyStatus === 'failed'}
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
success={verifyStatus === 'success'}
|
success={isStatusSuccess === true}
|
||||||
onChange={(value) => setOtpCode(value)}
|
onChange={(value) => setOtpCode(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
<Button onClick={handleVerifyOTP} loading={loginSignUpLoading}>
|
||||||
{authMode === 'register'
|
{authMode === 'register'
|
||||||
? t('verify.confirmAndContinue')
|
? t('verify.confirmAndContinue')
|
||||||
: t('verify.confirmAndLogin')}
|
: t('verify.confirmAndLogin')}
|
||||||
@@ -212,7 +211,7 @@ export function OtpVerifyForm({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
loading={resendLoading}
|
loading={smsResendLoading || emailResendLoading}
|
||||||
sx={{ width: 'auto' }}
|
sx={{ width: 'auto' }}
|
||||||
onClick={canResend ? handleResendOTPCode : undefined}
|
onClick={canResend ? handleResendOTPCode : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { Box, Button, Stack, Typography } from '@mui/material';
|
|||||||
import { Edit2 } from 'iconsax-react';
|
import { Edit2 } from 'iconsax-react';
|
||||||
import DigitInput from '@/components/components/DigitsInput';
|
import DigitInput from '@/components/components/DigitsInput';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Toast } from '@/components/Toast';
|
|
||||||
import { AuthenticationCard } from '../AuthenticationCard';
|
import { AuthenticationCard } from '../AuthenticationCard';
|
||||||
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
|
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
|
||||||
import { confirmSmsOtp, sendSmsOtp } from '../../api/authorizationAPI';
|
import { confirmSmsOtp, sendSmsOtp } from '../../api/authorizationAPI';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
interface VerifyPhoneNumberProps {
|
interface VerifyPhoneNumberProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -24,15 +25,18 @@ export function VerifyPhoneNumber({
|
|||||||
}: VerifyPhoneNumberProps) {
|
}: VerifyPhoneNumberProps) {
|
||||||
const [otpCode, setOtpCode] = useState<string>('');
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
const [verifyStatus, setVerifyStatus] = useState<'success' | 'failed'>();
|
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false);
|
|
||||||
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);
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
@@ -48,13 +52,10 @@ export function VerifyPhoneNumber({
|
|||||||
}, [resendTimer]);
|
}, [resendTimer]);
|
||||||
|
|
||||||
const handleResendOTPCode = async () => {
|
const handleResendOTPCode = async () => {
|
||||||
setResendLoading(true);
|
await smsResendCall({ phoneNumber: countryCode + value });
|
||||||
|
|
||||||
await sendSmsOtp({ phoneNumber: countryCode + value });
|
|
||||||
|
|
||||||
setResendTimer(120);
|
setResendTimer(120);
|
||||||
setCanResend(false);
|
setCanResend(false);
|
||||||
setResendLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
const formatTime = (seconds: number) => {
|
||||||
@@ -68,47 +69,37 @@ export function VerifyPhoneNumber({
|
|||||||
setOtpDigitInvalid(true);
|
setOtpDigitInvalid(true);
|
||||||
} else {
|
} else {
|
||||||
setOtpDigitInvalid(false);
|
setOtpDigitInvalid(false);
|
||||||
setVerifyStatusLoading(true);
|
|
||||||
|
|
||||||
const confirmSmsOtpRequest: ConfirmSmsOtpRequest = {
|
const confirmSmsOtpRequest: ConfirmSmsOtpRequest = {
|
||||||
otpCode: otpCode,
|
otpCode: otpCode,
|
||||||
phoneNumber: countryCode + value,
|
phoneNumber: countryCode + value,
|
||||||
};
|
};
|
||||||
const result = await confirmSmsOtp(confirmSmsOtpRequest);
|
await confirmSmsOtpCall(confirmSmsOtpRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!confirmSmsOtpResult) return;
|
||||||
setVerifyStatus('success');
|
|
||||||
|
if (confirmSmsOtpResult.success) {
|
||||||
|
setIsStatusSuccess(true);
|
||||||
|
toast({
|
||||||
|
message: t('verify.youHaveSuccessfullyLoggedIn'),
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
onPhoneNumberVerified();
|
onPhoneNumberVerified();
|
||||||
} else {
|
} else {
|
||||||
setVerifyStatus('failed');
|
setIsStatusSuccess(false);
|
||||||
setErrorMessage(jsonRes.message);
|
toast({
|
||||||
|
message:
|
||||||
|
confirmSmsOtpResult.message ??
|
||||||
|
t('verify.theVerificationCodeIsIncorrect'),
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerifyAlertOpen(true);
|
|
||||||
setVerifyStatusLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const verifyAlertMessage = (): string => {
|
|
||||||
if (verifyStatus === 'failed') {
|
|
||||||
return errorMessage ?? t('verify.theVerificationCodeIsIncorrect');
|
|
||||||
} else {
|
|
||||||
return t('verify.youHaveSuccessfullyLoggedIn');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
open={verifyAlertOpen}
|
|
||||||
onClose={() => setVerifyAlertOpen(false)}
|
|
||||||
color={verifyStatus === 'failed' ? 'error' : 'success'}
|
|
||||||
>
|
|
||||||
{verifyAlertMessage()}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -125,7 +116,7 @@ export function VerifyPhoneNumber({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
endIcon={<Edit2 />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditValue}
|
onClick={onEditValue}
|
||||||
>
|
>
|
||||||
{countryCode + value}
|
{countryCode + value}
|
||||||
@@ -139,12 +130,12 @@ export function VerifyPhoneNumber({
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<DigitInput
|
<DigitInput
|
||||||
error={otpDigitInvalid || verifyStatus === 'failed'}
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
success={verifyStatus === 'success'}
|
success={isStatusSuccess === true}
|
||||||
onChange={(value) => setOtpCode(value)}
|
onChange={(value) => setOtpCode(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
<Button onClick={handleVerifyOTP} loading={confirmSmsOtpLoading}>
|
||||||
{t('verify.confirmAndLogin')}
|
{t('verify.confirmAndLogin')}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
@@ -161,7 +152,7 @@ export function VerifyPhoneNumber({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
loading={resendLoading}
|
loading={smsResendLoading}
|
||||||
sx={{ width: 'auto' }}
|
sx={{ width: 'auto' }}
|
||||||
onClick={canResend ? handleResendOTPCode : undefined}
|
onClick={canResend ? handleResendOTPCode : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ReactCountryFlag from 'react-country-flag';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { countries, type Country } from '../../../countries';
|
import { countries, type Country } from '../../../countries';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
|
import { Icon } from '@rkheftan/harmony-ui';
|
||||||
interface CountryCodeSelectorProps {
|
interface CountryCodeSelectorProps {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
value: CountryCode;
|
value: CountryCode;
|
||||||
@@ -114,7 +115,7 @@ export function CountryCodeSelector({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* This inner Box prevents the content from being squeezed during the transition */}
|
{/* This inner Box prevents the content from being squeezed during the transition */}
|
||||||
<ArrowDown2 size="24" variant="Bold" />
|
<Icon Component={ArrowDown2} size="medium" variant="Bold" />
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import type { ResetPasswordRequest } from '../../types/userTypes';
|
|||||||
import type { AuthType } from '../../types/authTypes';
|
import type { AuthType } from '../../types/authTypes';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
import { resetPassword } from '../../api/authorizationAPI';
|
import { resetPassword } from '../../api/authorizationAPI';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface ChangePasswordProps {
|
export interface ChangePasswordProps {
|
||||||
onEditInfo: () => void;
|
onEditInfo: () => void;
|
||||||
@@ -48,15 +50,12 @@ export const ChangePassword = ({
|
|||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const confirmInputRef = useRef<HTMLInputElement>(null);
|
const confirmInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [changePasswordLoading, setChangePasswordLoading] =
|
const toast = useToast();
|
||||||
useState<boolean>(false);
|
const {
|
||||||
const [changePasswordStatus, setChangePasswordStatus] = useState<
|
data: resetPasswordData,
|
||||||
'success' | 'failed'
|
loading: resetPasswordLoading,
|
||||||
>();
|
execute: resetPasswordCall,
|
||||||
const [changePassAlertOpen, setChangePassAlertOpen] =
|
} = useApi(resetPassword);
|
||||||
useState<boolean>(false);
|
|
||||||
const [changePassFailedMessage, setChangePassFailedMessage] =
|
|
||||||
useState<string>('');
|
|
||||||
|
|
||||||
const passwordValidationRules = [
|
const passwordValidationRules = [
|
||||||
{ title: t('forgetPassword.includingANumber'), validator: containsNumber },
|
{ title: t('forgetPassword.includingANumber'), validator: containsNumber },
|
||||||
@@ -84,8 +83,6 @@ export const ChangePassword = ({
|
|||||||
setConfirmInputTouched(true);
|
setConfirmInputTouched(true);
|
||||||
confirmInputRef.current?.focus();
|
confirmInputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
setChangePasswordLoading(true);
|
|
||||||
|
|
||||||
const apiRequest: ResetPasswordRequest = {
|
const apiRequest: ResetPasswordRequest = {
|
||||||
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||||
phoneNumber:
|
phoneNumber:
|
||||||
@@ -96,19 +93,22 @@ export const ChangePassword = ({
|
|||||||
confirmNewPassword: confirmPassValue,
|
confirmNewPassword: confirmPassValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await resetPassword(apiRequest);
|
await resetPasswordCall(apiRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!resetPasswordData) return;
|
||||||
setChangePasswordStatus('success');
|
|
||||||
|
if (resetPasswordData.success) {
|
||||||
onPasswordChanged();
|
onPasswordChanged();
|
||||||
|
toast({
|
||||||
|
message: t('forgetPassword.passwordChangedSuccessfully'),
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setChangePasswordStatus('failed');
|
toast({
|
||||||
setChangePassFailedMessage(jsonRes.message);
|
message: resetPasswordData.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setChangePassAlertOpen(true);
|
|
||||||
|
|
||||||
setChangePasswordLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,16 +123,6 @@ export const ChangePassword = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
open={changePassAlertOpen}
|
|
||||||
onClose={() => setChangePassAlertOpen(false)}
|
|
||||||
color={changePasswordStatus === 'failed' ? 'error' : 'success'}
|
|
||||||
>
|
|
||||||
{changePasswordStatus === 'failed'
|
|
||||||
? changePassFailedMessage
|
|
||||||
: t('forgetPassword.passwordChangedSuccessfully')}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -151,7 +141,7 @@ export const ChangePassword = ({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
endIcon={<Edit2 />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditInfo}
|
onClick={onEditInfo}
|
||||||
>
|
>
|
||||||
{forgettedPasswordInfo}
|
{forgettedPasswordInfo}
|
||||||
@@ -177,14 +167,22 @@ export const ChangePassword = ({
|
|||||||
startAdornment: confirmPassValue &&
|
startAdornment: confirmPassValue &&
|
||||||
isValidPassword(passValue) &&
|
isValidPassword(passValue) &&
|
||||||
passValue === confirmPassValue && (
|
passValue === confirmPassValue && (
|
||||||
<TickCircle variant="Bold" color={theme.palette.success.main} />
|
<Icon
|
||||||
|
Component={TickCircle}
|
||||||
|
variant="Bold"
|
||||||
|
color="success.main"
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
endAdornment: passValue ? (
|
endAdornment: passValue ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
>
|
>
|
||||||
{showPassword ? <Eye /> : <EyeSlash />}
|
{showPassword ? (
|
||||||
|
<Icon Component={Eye} />
|
||||||
|
) : (
|
||||||
|
<Icon Component={EyeSlash} />
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
@@ -198,12 +196,11 @@ export const ChangePassword = ({
|
|||||||
<Stack spacing={1} sx={{ mt: 2 }}>
|
<Stack spacing={1} sx={{ mt: 2 }}>
|
||||||
{passwordValidationRules.map((rule) => (
|
{passwordValidationRules.map((rule) => (
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||||
<TickCircle
|
<Icon
|
||||||
|
Component={TickCircle}
|
||||||
variant={rule.validator(passValue) ? 'Bold' : 'Linear'}
|
variant={rule.validator(passValue) ? 'Bold' : 'Linear'}
|
||||||
color={
|
color={
|
||||||
rule.validator(passValue)
|
rule.validator(passValue) ? 'success.main' : 'primary.light'
|
||||||
? theme.palette.success.main
|
|
||||||
: theme.palette.primary.light
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -227,14 +224,22 @@ export const ChangePassword = ({
|
|||||||
startAdornment: confirmPassValue &&
|
startAdornment: confirmPassValue &&
|
||||||
isValidPassword(passValue) &&
|
isValidPassword(passValue) &&
|
||||||
passValue === confirmPassValue && (
|
passValue === confirmPassValue && (
|
||||||
<TickCircle variant="Bold" color={theme.palette.success.main} />
|
<Icon
|
||||||
|
Component={TickCircle}
|
||||||
|
variant="Bold"
|
||||||
|
color="success.main"
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
endAdornment: confirmPassValue ? (
|
endAdornment: confirmPassValue ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
>
|
>
|
||||||
{showConfirmPassword ? <Eye /> : <EyeSlash />}
|
{showConfirmPassword ? (
|
||||||
|
<Icon Component={Eye} />
|
||||||
|
) : (
|
||||||
|
<Icon Component={EyeSlash} />
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
@@ -245,7 +250,7 @@ export const ChangePassword = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Button loading={changePasswordLoading} onClick={handleSubmit}>
|
<Button loading={resetPasswordLoading} onClick={handleSubmit}>
|
||||||
{t('forgetPassword.confirm')}
|
{t('forgetPassword.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ 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';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export const ForgetPasswordContainer = () => {
|
export const ForgetPasswordContainer = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [forgetPassCurrentStep, setForgetPassCurrentStep] = useState<
|
const [forgetPassCurrentStep, setForgetPassCurrentStep] = useState<
|
||||||
'enterInfo' | 'verifyOtp' | 'setPassword'
|
'enterInfo' | 'verifyOtp' | 'setPassword'
|
||||||
>('enterInfo');
|
>('enterInfo');
|
||||||
@@ -27,7 +29,7 @@ export const ForgetPasswordContainer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordChanged = () => {
|
const handlePasswordChanged = () => {
|
||||||
console.log('changingPasswordTo');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,9 +6,17 @@ import type { 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 {
|
||||||
|
ConfirmForgetPassCodeRequest,
|
||||||
|
SendForgetPassCodeRequest,
|
||||||
|
} from '../../types/userTypes';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
import { confirmForgetPassCode } from '../../api/authorizationAPI';
|
import {
|
||||||
|
confirmForgetPassCode,
|
||||||
|
sendForgetPassCode,
|
||||||
|
} from '../../api/authorizationAPI';
|
||||||
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
interface ForgetPasswordOtpProps {
|
interface ForgetPasswordOtpProps {
|
||||||
forgettedPasswordInfo: string;
|
forgettedPasswordInfo: string;
|
||||||
@@ -27,14 +35,21 @@ export function ForgetPasswordOtp({
|
|||||||
}: ForgetPasswordOtpProps) {
|
}: ForgetPasswordOtpProps) {
|
||||||
const [otpCode, setOtpCode] = useState<string>('');
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
const [verifyStatus, setVerifyStatus] = useState<'failed' | 'success'>();
|
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||||
const [verifyStatusLoading, setVerifyStatusLoading] =
|
|
||||||
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);
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
@@ -49,18 +64,16 @@ export function ForgetPasswordOtp({
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [resendTimer]);
|
}, [resendTimer]);
|
||||||
|
|
||||||
const handleResendOTPCode = () => {
|
const handleResendOTPCode = async () => {
|
||||||
setResendLoading(true);
|
const sendCodeRequest: SendForgetPassCodeRequest = {
|
||||||
|
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||||
|
phoneNumber:
|
||||||
|
infoType === 'phone' ? countryCode + forgettedPasswordInfo : undefined,
|
||||||
|
};
|
||||||
|
await sendForgetPassCodeCall(sendCodeRequest);
|
||||||
|
|
||||||
// TODO: Call API here instead of settimeout
|
setResendTimer(120);
|
||||||
|
setCanResend(false);
|
||||||
setTimeout(() => {
|
|
||||||
console.log('resended');
|
|
||||||
|
|
||||||
setResendTimer(120);
|
|
||||||
setCanResend(false);
|
|
||||||
setResendLoading(false);
|
|
||||||
}, 1000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
const formatTime = (seconds: number) => {
|
||||||
@@ -74,7 +87,6 @@ export function ForgetPasswordOtp({
|
|||||||
setOtpDigitInvalid(true);
|
setOtpDigitInvalid(true);
|
||||||
} else {
|
} else {
|
||||||
setOtpDigitInvalid(false);
|
setOtpDigitInvalid(false);
|
||||||
setVerifyStatusLoading(true);
|
|
||||||
|
|
||||||
// Change setTimeout to api call
|
// Change setTimeout to api call
|
||||||
const apiRequest: ConfirmForgetPassCodeRequest = {
|
const apiRequest: ConfirmForgetPassCodeRequest = {
|
||||||
@@ -86,32 +98,26 @@ export function ForgetPasswordOtp({
|
|||||||
code: otpCode,
|
code: otpCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await confirmForgetPassCode(apiRequest);
|
confirmForgetPassCodeCall(apiRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (jsonRes.success) {
|
if (!confirmForgetPassCodeData) return;
|
||||||
setVerifyStatus('success');
|
|
||||||
|
if (confirmForgetPassCodeData.success) {
|
||||||
|
setIsStatusSuccess(true);
|
||||||
onOTPVerified(otpCode);
|
onOTPVerified(otpCode);
|
||||||
} else {
|
} else {
|
||||||
setVerifyStatus('failed');
|
setIsStatusSuccess(false);
|
||||||
setVerifyAlertMessage(jsonRes.message);
|
toast({
|
||||||
|
message: confirmForgetPassCodeData.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerifyStatusLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Toast
|
|
||||||
open={!!verifyAlertMessage}
|
|
||||||
onClose={() => setVerifyAlertMessage(undefined)}
|
|
||||||
color={'error'}
|
|
||||||
>
|
|
||||||
{verifyAlertMessage}
|
|
||||||
</Toast>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -130,7 +136,7 @@ export function ForgetPasswordOtp({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
endIcon={<Edit2 />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditInfo}
|
onClick={onEditInfo}
|
||||||
>
|
>
|
||||||
{infoType === 'phone'
|
{infoType === 'phone'
|
||||||
@@ -150,12 +156,15 @@ export function ForgetPasswordOtp({
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<DigitInput
|
<DigitInput
|
||||||
error={otpDigitInvalid || verifyStatus === 'failed'}
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
success={verifyStatus === 'success'}
|
success={isStatusSuccess === true}
|
||||||
onChange={(value) => setOtpCode(value)}
|
onChange={(value) => setOtpCode(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
<Button
|
||||||
|
onClick={handleVerifyOTP}
|
||||||
|
loading={confirmForgetPassCodeLoading}
|
||||||
|
>
|
||||||
{t('forgetPassword.confirm')}
|
{t('forgetPassword.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
@@ -172,7 +181,7 @@ export function ForgetPasswordOtp({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
loading={resendLoading}
|
loading={sendForgetPassCodeLoading}
|
||||||
sx={{ width: 'auto' }}
|
sx={{ width: 'auto' }}
|
||||||
onClick={canResend ? handleResendOTPCode : undefined}
|
onClick={canResend ? handleResendOTPCode : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { sendForgetPassCode } from '../../api/authorizationAPI';
|
|||||||
import type { SendForgetPassCodeRequest } from '../../types/userTypes';
|
import type { SendForgetPassCodeRequest } from '../../types/userTypes';
|
||||||
import { Toast } from '@/components/Toast';
|
import { Toast } from '@/components/Toast';
|
||||||
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
||||||
|
import { useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
|
||||||
export interface ForgettedPasswordInfoProps {
|
export interface ForgettedPasswordInfoProps {
|
||||||
forgettedPasswordInfo: string;
|
forgettedPasswordInfo: string;
|
||||||
@@ -36,9 +38,13 @@ export function ForgettedPasswordInfo({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
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 toast = useToast();
|
||||||
|
const {
|
||||||
|
data: sendForgetPassCodeData,
|
||||||
|
loading: sendForgetPassCodeLoading,
|
||||||
|
execute: sendForgetPassCodeCall,
|
||||||
|
} = useApi(sendForgetPassCode);
|
||||||
|
|
||||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newValue = event.target.value;
|
const newValue = event.target.value;
|
||||||
@@ -79,8 +85,6 @@ export function ForgettedPasswordInfo({
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (validateInput(forgettedPasswordInfo, infoType, false)) {
|
if (validateInput(forgettedPasswordInfo, infoType, false)) {
|
||||||
setSendCodeLoading(true);
|
|
||||||
|
|
||||||
const sendCodeRequest: SendForgetPassCodeRequest = {
|
const sendCodeRequest: SendForgetPassCodeRequest = {
|
||||||
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
email: infoType === 'email' ? forgettedPasswordInfo : undefined,
|
||||||
phoneNumber:
|
phoneNumber:
|
||||||
@@ -88,14 +92,17 @@ export function ForgettedPasswordInfo({
|
|||||||
? countryCode + forgettedPasswordInfo
|
? countryCode + forgettedPasswordInfo
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
const result = await sendForgetPassCode(sendCodeRequest);
|
sendForgetPassCodeCall(sendCodeRequest);
|
||||||
const jsonRes = await result.json();
|
|
||||||
|
|
||||||
if (!jsonRes.success) {
|
if (!sendForgetPassCodeData) return;
|
||||||
setErrorMessage(jsonRes.message);
|
|
||||||
|
if (!sendForgetPassCodeData.success) {
|
||||||
|
toast({
|
||||||
|
message: sendForgetPassCodeData.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setSendCodeLoading(false);
|
|
||||||
onVerifyOtp(forgettedPasswordInfo);
|
onVerifyOtp(forgettedPasswordInfo);
|
||||||
} else {
|
} else {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
@@ -108,14 +115,6 @@ 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')}
|
||||||
@@ -155,7 +154,7 @@ export function ForgettedPasswordInfo({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Button loading={sendCodeLoading} onClick={handleSubmit}>
|
<Button loading={sendForgetPassCodeLoading} onClick={handleSubmit}>
|
||||||
{t('forgetPassword.confirm')}
|
{t('forgetPassword.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './routes/AuthenticationPage';
|
||||||
|
export * from './routes/ForgetPasswordPage';
|
||||||
|
|||||||
@@ -115,24 +115,3 @@ export interface LoginOrSignUpWithGoogleResponse extends ApiResponse {
|
|||||||
completedUserInformation: boolean;
|
completedUserInformation: boolean;
|
||||||
returnUrl: string;
|
returnUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompleteUserInformation
|
|
||||||
|
|
||||||
export interface CompleteUserInformationRequest {
|
|
||||||
firstName?: string;
|
|
||||||
lastName?: string;
|
|
||||||
gender?: Gender;
|
|
||||||
nationalCode?: string;
|
|
||||||
savePassword?: boolean;
|
|
||||||
password?: string;
|
|
||||||
saveEmail?: boolean;
|
|
||||||
email?: string;
|
|
||||||
birthDate?: string;
|
|
||||||
countryCode?: string;
|
|
||||||
userId?: GUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Gender {
|
|
||||||
Male = 1,
|
|
||||||
Female = 2,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
// FIXME: all the responses should extend this interface
|
||||||
|
// import type { ApiResponse } from '@/types/apiResponse';
|
||||||
|
import { useToast } from '@rkheftan/harmony-ui';
|
||||||
|
import type { AxiosError } from 'axios';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
// Define options for the hook
|
// Define options for the hook
|
||||||
interface UseApiOptions {
|
interface UseApiOptions {
|
||||||
// If true, the API call will be executed immediately on mount
|
// If true, the API call will be executed immediately on mount
|
||||||
@@ -9,9 +15,13 @@ export function useApi<T, P extends any[]>(
|
|||||||
apiFunction: (...args: P) => Promise<{ data: T }>,
|
apiFunction: (...args: P) => Promise<{ data: T }>,
|
||||||
options: UseApiOptions = {},
|
options: UseApiOptions = {},
|
||||||
) {
|
) {
|
||||||
|
const toast = useToast();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [data, setData] = useState<T | null>(null);
|
const [data, setData] = useState<T | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<unknown>(null);
|
const [error, setError] = useState<unknown>(null);
|
||||||
|
|
||||||
const execute = useCallback(
|
const execute = useCallback(
|
||||||
async (...args: P) => {
|
async (...args: P) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -21,15 +31,28 @@ export function useApi<T, P extends any[]>(
|
|||||||
const response = await apiFunction(...args);
|
const response = await apiFunction(...args);
|
||||||
|
|
||||||
setData(response.data);
|
setData(response.data);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
// TODO: can handle some common errors here, 400 and 500 errors
|
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);
|
setError(err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiFunction],
|
[apiFunction, navigate, t, toast],
|
||||||
);
|
)
|
||||||
|
|
||||||
|
|
||||||
// If the 'immediate' option is true, execute the function on mount
|
// If the 'immediate' option is true, execute the function on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// Function to get the token from local storage or state management
|
// Function to get the token from local storage or state management
|
||||||
const getToken = () => localStorage.getItem('authToken');
|
export const ACCESS_TOKEN_KEY: 'access_token' = 'access_token' as const;
|
||||||
|
export const getAccessToken = () => sessionStorage.getItem(ACCESS_TOKEN_KEY);
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
// Define the base URL for all API requests
|
// Define the base URL for all API requests
|
||||||
baseURL: 'https://accounts.business-harmony.com/api/',
|
baseURL: import.meta.env.VITE_API_URL,
|
||||||
|
|
||||||
// Set a timeout for requests (e.g., 10 seconds)
|
// Set a timeout for requests (e.g., 10 seconds)
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
@@ -21,7 +22,7 @@ const apiClient = axios.create({
|
|||||||
// This runs BEFORE each request is sent
|
// This runs BEFORE each request is sent
|
||||||
apiClient.interceptors.request.use(
|
apiClient.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = getToken();
|
const token = getAccessToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
// Add the authorization token to the headers
|
// Add the authorization token to the headers
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
|||||||
14
src/lib/identityClient.ts
Normal file
14
src/lib/identityClient.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const identityClient = axios.create({
|
||||||
|
// Define the base URL for all API requests
|
||||||
|
baseURL: import.meta.env.VITE_IDENTITY_URL,
|
||||||
|
|
||||||
|
// Set a timeout for requests (e.g., 10 seconds)
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
// Set default headers
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Layout } from '@/components/Layout/Layout';
|
import { Layout } from '@/components/Layout/Layout';
|
||||||
|
import {
|
||||||
|
AuthenticationPage,
|
||||||
|
ForgetPasswordPage,
|
||||||
|
} from '@/features/authorization';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Devices,
|
Devices,
|
||||||
@@ -18,6 +22,7 @@ import { Navigate } from 'react-router-dom';
|
|||||||
export interface RouteConfig {
|
export interface RouteConfig {
|
||||||
path: string;
|
path: string;
|
||||||
element?: ReactNode;
|
element?: ReactNode;
|
||||||
|
authorize?: boolean;
|
||||||
navConfig?: {
|
navConfig?: {
|
||||||
title: string; // Translation key
|
title: string; // Translation key
|
||||||
icon?: Icon;
|
icon?: Icon;
|
||||||
@@ -27,6 +32,20 @@ export interface RouteConfig {
|
|||||||
|
|
||||||
// can lazy load component if needed (ex. lazy(() => import('@/features/home/routes/HomePage'));)
|
// can lazy load component if needed (ex. lazy(() => import('@/features/home/routes/HomePage'));)
|
||||||
export const appRoutes: RouteConfig[] = [
|
export const appRoutes: RouteConfig[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Navigate to="/setting/profile" replace />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
element: <AuthenticationPage />,
|
||||||
|
authorize: l,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/forget-password',
|
||||||
|
element: <ForgetPasswordPage />,
|
||||||
|
authorize: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <Navigate to="/setting/profile" replace />,
|
element: <Navigate to="/setting/profile" replace />,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Suspense, type ReactNode } from 'react';
|
import { Suspense, type ReactNode } from 'react';
|
||||||
import { createBrowserRouter, type RouteObject } from 'react-router-dom';
|
import { createBrowserRouter, type RouteObject } from 'react-router-dom';
|
||||||
import { appRoutes, type RouteConfig } from './config';
|
import { appRoutes, type RouteConfig } from './config';
|
||||||
|
import { ProtectedRoute } from '@/components/ProtectedRoute';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A recursive function to map our custom route config to the format
|
* A recursive function to map our custom route config to the format
|
||||||
@@ -18,10 +19,9 @@ function mapRoutes(routes: RouteConfig[]): RouteObject[] {
|
|||||||
// element = <route.layout>{element}</route.layout>;
|
// element = <route.layout>{element}</route.layout>;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Conditionally wrap the element in the authentication guard
|
if (route.authorize) {
|
||||||
// if (route.authRequired) {
|
element = <ProtectedRoute>{element}</ProtectedRoute>;
|
||||||
// element = <ProtectedRoute>{element}</ProtectedRoute>;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: route.path,
|
path: route.path,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Palette } from './color.type';
|
|||||||
export const PALETTE: Palette = {
|
export const PALETTE: Palette = {
|
||||||
primary: {
|
primary: {
|
||||||
light: {
|
light: {
|
||||||
main: blue[400],
|
main: blue.A400,
|
||||||
dark: blue[700],
|
dark: blue[700],
|
||||||
light: blue[100],
|
light: blue[100],
|
||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export type FetchPromise<T = ''> = Promise<FetchResponse<T>>;
|
|
||||||
|
|
||||||
export interface FetchResponse<T> extends Response {
|
|
||||||
json(): Promise<T>;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user