[FEAT]: add accesstoken interceptor

This commit is contained in:
behnamrhp 2023-05-22 21:26:45 +03:00
parent 0fc508c56b
commit c30186778d
20 changed files with 258 additions and 36 deletions

View File

@ -2,6 +2,7 @@ VITE_API_ORIGIN = https://admin.dev.dipal.ru/api/v1
VITE_API_AUTH_ORIGIN = https://auth.dev.dipal.ru/api/v1/auth
VITE_API_AUTH_PHONENUMBER = /start-challenge
VITE_API_AUTH_LOGIN = /login
VITE_API_AUTH_REFRESH = /refresh-token
VITE_API_PLACES = /place
VITE_API_USERS = /profile
VITE_API_USERS_ACCOUNT = /account

View File

@ -4,9 +4,21 @@ import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { IOtpAuthDrivenPort } from '~/business-logic/generic/admin-user/authentication/otp-auth/port';
import { OtpAuthResponse } from '~/business-logic/generic/admin-user/authentication/otp-auth/data/reponse-object/otpAuthRO';
import { OtpAuthDTOReturnType } from '~/business-logic/generic/admin-user/authentication/otp-auth/data/dto/otpAuthDto';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
const authAdminLogin = (): IOtpAuthDrivenPort => {
const httpProvider = new HTTPPovider();
const authAdminLogin = (
userAdmin: AdminUserModel | null,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IOtpAuthDrivenPort => {
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin && userAdmin.adminUserData.accessToken && null,
refreshToken: userAdmin && userAdmin.adminUserData.refreshToken && null,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = (data: OtpAuthDTOReturnType): Promise<OtpAuthResponse> => {
const url = apiUrls.generic.authLogin;
const options: HttpOptionsType = {

View File

@ -4,12 +4,24 @@ import { PhonenumberAuthDTOReturnType } from '~/business-logic/generic/admin-use
import { IPhonenumberAuthPort } from '~/business-logic/generic/admin-user/authentication/phonnumber-auth/port';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
const authAdminPhoneNumberDriven = (): IPhonenumberAuthPort => {
const authAdminPhoneNumberDriven = (
userAdmin: AdminUserModel | null,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IPhonenumberAuthPort => {
// make http handler ready for the phone number
const wrongPhoneNumberMessage = staticMessages.global.errors.phonenumber;
const httpProvider = new HTTPPovider();
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin && userAdmin?.adminUserData.accessToken && null,
refreshToken: userAdmin && userAdmin?.adminUserData.refreshToken && null,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = (data: PhonenumberAuthDTOReturnType) => {
const url = apiUrls.generic.authPhonenumber;
const options: HttpOptionsType = {

View File

@ -1,15 +1,27 @@
import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols';
import { CreateAcountResponseApi } from '~/business-logic/core/users/create-user/create-account/data/response-object/protocols';
import createUserPort from '~/business-logic/core/users/create-user/ports';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
const createAccountAdapter = (): createUserPort['httpAccountHandler'] => {
const createAccountAdapter = (
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): createUserPort['httpAccountHandler'] => {
// make url
const url = apiUrls.core.createUserAccount;
// call http provider
const httpProvider = new HTTPPovider();
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin.adminUserData.accessToken,
refreshToken: userAdmin.adminUserData.refreshToken,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = (newUserData: INewUserData) => {
// api options

View File

@ -1,14 +1,26 @@
import { CreateProfileDtoReturnType } from '~/business-logic/core/users/create-user/create-profile/data/dto/protocols';
import createUserPort from '~/business-logic/core/users/create-user/ports';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
const createProfileAdapter = (): createUserPort['httpProfileHandler'] => {
const createProfileAdapter = (
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): createUserPort['httpProfileHandler'] => {
// make url
const url = apiUrls.core.createUserProfile;
// call http provider
const httpProvider = new HTTPPovider();
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin.adminUserData.accessToken,
refreshToken: userAdmin.adminUserData.refreshToken,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = (newAccountData: CreateProfileDtoReturnType) => {
// api options

View File

@ -3,9 +3,14 @@ import IGetPlacesPort from '~/business-logic/core/places/get-places/port';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { getPlacesAdapterReturnType } from './protocols';
const getPlacesAdapter = (): IGetPlacesPort & getPlacesAdapterReturnType => {
const getPlacesAdapter = (
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IGetPlacesPort & getPlacesAdapterReturnType => {
// url of api
const url = apiUrls.core.getPlaces;
// make the options of request
@ -14,7 +19,14 @@ const getPlacesAdapter = (): IGetPlacesPort & getPlacesAdapterReturnType => {
method: 'GET',
};
// make the httpHandler
const httpProvider = new HTTPPovider();
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin.adminUserData.accessToken,
refreshToken: userAdmin.adminUserData.refreshToken,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = async () => httpProvider.request<GetPlacesResponse>(options);

View File

@ -3,9 +3,14 @@ import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { GetUsersResponse } from '~/business-logic/core/users/get-users/data/response-object/protocols';
import IGetUsersPort from '~/business-logic/core/users/get-users/ports';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { getUsersAdapterReturnType } from './protocols';
const getUsersAdapter = (): IGetUsersPort & getUsersAdapterReturnType => {
const getUsersAdapter = (
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IGetUsersPort & getUsersAdapterReturnType => {
// url of api
const url = apiUrls.core.getUsers;
// make the options of request
@ -14,7 +19,14 @@ const getUsersAdapter = (): IGetUsersPort & getUsersAdapterReturnType => {
method: 'GET',
};
// make the httpHandler
const httpProvider = new HTTPPovider();
const httpProvider = new HTTPPovider(
{
accessToken: userAdmin.adminUserData.accessToken,
refreshToken: userAdmin.adminUserData.refreshToken,
},
updateAccessToken,
navigateToAuth,
);
const httpHandler = async () => httpProvider.request<GetUsersResponse>(options);

View File

@ -1,22 +1,88 @@
import axios from 'axios';
/* eslint-disable consistent-return */
/* eslint-disable no-param-reassign */
import axios, { AxiosInstance } from 'axios';
import { staticMessages } from '~/driven/utils/constants/staticMessages';
import { ApiGlobalResponseObject } from '~/driven/utils/protocols/serviceProtocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import { HttpOptionsType } from './protocols';
interface IUserTokens {
accessToken: string | null;
refreshToken: string | null;
}
export class HTTPPovider {
private userTokens: IUserTokens;
private updateAccessToken: (newAccessToken: string) => void;
private navigateToAuth: () => void;
constructor(
userTokens: IUserTokens,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
) {
this.userTokens = userTokens;
this.updateAccessToken = updateAccessToken;
this.navigateToAuth = navigateToAuth;
}
private initalizeAxiosInstance() {
const instance = axios.create();
return instance;
}
private handleRequestInterceptor() {
const axiosInstance = this.initalizeAxiosInstance();
axiosInstance.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${this.userTokens.accessToken}`;
return config;
});
return axiosInstance;
}
async request<R>(customOptions: HttpOptionsType) {
const options: HttpOptionsType = {
...customOptions,
headers: {
...customOptions.headers,
mode: 'cors',
credentials: 'include',
Authorization: `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4NXh0WnA5eThxVDBXVDkwUFpuUkRja3N4LWw0clVyM0tHQW5JSU9DckJNIn0.eyJleHAiOjE2ODQ4MzM5NzcsImlhdCI6MTY4NDc0NzU3NywianRpIjoiYjE5MDEyNGItYzRjNC00NzlkLThkYWItN2VjODc1MjljZWQyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9kaXBhbF9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiY2RmYzY3YzQtZGJkOC00NGVhLWI0OWEtYjQ3MjZhMzNmOTAxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY29tZm9ydGVjaCIsInNlc3Npb25fc3RhdGUiOiJiYjFmNjc3OC0xMzlhLTRmNzItOWM3Ny01NjAwMWU0NDYzNjQiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiY29tZm9ydGVjaCI6eyJyb2xlcyI6WyJ1c2VyIiwib3BlcmF0b3IiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6ImJiMWY2Nzc4LTEzOWEtNGY3Mi05Yzc3LTU2MDAxZTQ0NjM2NCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiKzc3Nzc3Nzc3Nzc3In0.qJS5_c9g2AADwusGpquWw7zMvc42tzJ0yUMcM6jI6F2MNH2tFDqMhvG0nEnCwXIxJA54DZL8HHPDoxkhq_xyP2SSRKEU-S7pncpa2acNzOYT68pLxBD6s3W-akQxxJVlr92RtegqaHf2BAZMwdMJl4VreX_avPCrEdPzv2dEMX7a2wxteYgzQJsYtaaVyCO4QADMiNVMWgXE00Hnn5Rxuhpe9Y7Kl9cWCO5JY63gYXGFC9yUBEqEYl6o9d6XKMkuiaLJRE2l4k5ycKuJWUjhvCaL7J_f68vJzNhkiuMqmX5q08SDlgktNHzyKTVXkndKz2EpQemzM6SXPLnohPwjAg`,
},
};
const response = await axios<ApiGlobalResponseObject<R>>(options);
const axiosInstance = this.handleRequestInterceptor();
this.responseIncepter(axiosInstance);
const response = await axiosInstance<ApiGlobalResponseObject<R>>(customOptions);
if (!response) throw new Error(staticMessages.service.errors[500]);
return response.data.data;
}
/**
* @todo should be handled in business logic
*/
private async refreshAccessToken() {
try {
const response = await axios.post(apiUrls.generic.authRefresh, {
refresh_token: this.userTokens.refreshToken,
});
this.updateAccessToken(response.data.access_token as string);
return response.data.access_token;
} catch (err) {
this.navigateToAuth();
}
}
private responseIncepter(axiosInstance: AxiosInstance) {
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
const originalRequest = error.config;
if (error.response.status === 401 && error.response.message === 'Unauthorized') {
const newAccessToken = this.refreshAccessToken().then(() => {
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return axios(originalRequest);
});
}
return Promise.reject(error);
},
);
}
}

View File

@ -36,5 +36,6 @@ export const apiUrls = {
generic: {
authPhonenumber: `${ENVs.apiAuthOrigin}${ENVs.apiAuthPhonenumber}`,
authLogin: `${ENVs.apiAuthOrigin}${ENVs.apiAuthLogin}`,
authRefresh: `${ENVs.apiAuthOrigin}${ENVs.apiAuthRefresh}`,
},
};

View File

@ -3,6 +3,7 @@ export const ENVs = {
apiAuthOrigin: process.env.VITE_API_AUTH_ORIGIN,
apiAuthPhonenumber: process.env.VITE_API_AUTH_PHONENUMBER,
apiAuthLogin: process.env.VITE_API_AUTH_LOGIN,
apiAuthRefresh: process.env.VITE_API_AUTH_REFRESH,
apiGetPlaces: process.env.VITE_API_PLACES,
apiGetUsers: process.env.VITE_API_USERS,
apiCreateUserAccount: process.env.VITE_API_USERS_ACCOUNT,

View File

@ -2,7 +2,7 @@
import React, { useState } from 'react';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
interface IUserContext {
export interface IUserContext {
user: AdminUserModel | null;
setUser: React.Dispatch<React.SetStateAction<AdminUserModel | null>> | null;
}

View File

@ -1,5 +1,9 @@
import StateManagementService from '~/driven/boundaries/state-management';
import StorageService from '~/driven/boundaries/storage-boundary';
import { NavigateFunction } from 'react-router-dom';
import { appConfig, routes } from '../configs/appConfig';
import { errorHandlingStateTypes, UIErrorHandling } from './protocols/globalHelpersProtocols';
import { IUserContext } from './contexts/userContext';
export const UIErrorHandlingFactory = <DATA_RESPONSE>({
state,
@ -26,3 +30,21 @@ export const prepareStateManagementForVM = <ReturnType>(apiUrl: string, model: (
export const checkPhoneNumberInput = (newValue: string) => {
return (Number.isFinite(+newValue) || newValue === '+') && newValue.length <= 12;
};
export const updateAccessToken = (newAccessToken: string, userContext: IUserContext) => {
const { setUser, user } = userContext;
if (!user || !setUser) return;
const storage = StorageService.localStorage();
user.adminUserData.accessToken = newAccessToken;
storage.setData(appConfig.adminUserStorageKey, user);
setUser(user);
};
export const navigateToAuth = (userCtx: IUserContext, navigate: NavigateFunction) => {
const { setUser } = userCtx;
const storage = StorageService.localStorage();
storage.deleteData(appConfig.adminUserStorageKey);
if (!setUser) return;
setUser(null);
navigate(routes.authentication);
};

View File

@ -0,0 +1,24 @@
import { useNavigate } from 'react-router-dom';
import { useUser } from '../contexts/userContext';
import { navigateToAuth, updateAccessToken } from '../globalHelpers';
const useGetNavigatorAndTokenUpdater = () => {
const userData = useUser();
const navigate = useNavigate();
const notLoginAuth = () => {
navigateToAuth(userData, navigate);
};
const accessTokenUpdateHandler = (newAccessToken: string) => {
updateAccessToken(newAccessToken, userData);
};
return {
notLoginAuth,
accessTokenUpdateHandler,
userData,
navigate,
};
};
export default useGetNavigatorAndTokenUpdater;

View File

@ -1,3 +1,4 @@
/* eslint-disable react/no-array-index-key */
import React from 'react';
import RowItem from './table-row-item/view/RowItem';
import { ITableRowProps } from './protocols';
@ -6,7 +7,14 @@ export default function TableRowView(props: ITableRowProps) {
const { isSelected, setSelectedRowId, rowData } = props;
const { rowId, rowItemsTitle } = rowData;
const columns = rowItemsTitle.map((rowItemTitle, index) => {
return <RowItem key={rowItemTitle} hasCheckbox={index === 0} isSelected={isSelected} title={rowItemTitle} />;
return (
<RowItem
key={(rowItemTitle || 'row') + index}
hasCheckbox={index === 0}
isSelected={isSelected}
title={rowItemTitle}
/>
);
});
return <tr onClick={() => setSelectedRowId(rowId)}>{columns}</tr>;

View File

@ -2,14 +2,27 @@ import React from 'react';
import createAccountAdapter from '~/driven/adapters/create-account-adapter/createAccountAdapter';
import createProfileAdapter from '~/driven/adapters/create-profile-adapter/createProfileAdapter';
import CreateUserInfra from '~/business-logic/core/users/create-user';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import CreateUserView from '../view/CreateUserView';
import useCreateUserVM from '../viewmodel/CreateUserVM';
import createUserModel from '../model/createUserModel';
export default function CreateUser() {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
// get adapters from driven layer
const createAccountDrivenAdapter = createAccountAdapter();
const createProfileDrivenAdapter = createProfileAdapter();
const createAccountDrivenAdapter = createAccountAdapter(
user as AdminUserModel,
accessTokenUpdateHandler,
notLoginAuth,
);
const createProfileDrivenAdapter = createProfileAdapter(
user as AdminUserModel,
accessTokenUpdateHandler,
notLoginAuth,
);
// pass to the logic and get the usecase
const createUserInfra = new CreateUserInfra(createAccountDrivenAdapter, createProfileDrivenAdapter);
const createUserLogic = createUserInfra.execute();

View File

@ -3,12 +3,17 @@ import getPlaces from '~/business-logic/core/places/get-places';
import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter';
import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import PlacesListView from '../view/PlacesListView';
import usePlacesListVM from '../viewmodel/placesListVM';
import placesListModel from '../model/placesListModel';
const prepareTheLogicForModel = () => {
const gettingPlacesDrivenAdapter = getPlacesAdapter();
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
const gettingPlacesDrivenAdapter = getPlacesAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingPlacesDrivenAdapter;
const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter);
return { getingPlacesLogic, url };

View File

@ -3,19 +3,24 @@ import getUsersAdapter from '~/driven/adapters/get-users-adapter/getUsersAdapter
import getUsers from '~/business-logic/core/users/get-users';
import UsersModel from '~/business-logic/core/users/common/data/model/usersModel';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import useUsersListVM from '../viewmodel/usersListVM';
import UsersListView from '../view/UsersListView';
import usersListModel from '../model/usersListModel';
const prepareTheLogicForModel = () => {
const gettingUsersDrivenAdapter = getUsersAdapter();
const usePrepareTheLogicForModel = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
const gettingUsersDrivenAdapter = getUsersAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingUsersDrivenAdapter;
const getingusersLogic = getUsers(gettingUsersDrivenAdapter);
return { getingusersLogic, url };
};
export default function UsersList() {
const { getingusersLogic, url } = prepareTheLogicForModel();
const { getingusersLogic, url } = usePrepareTheLogicForModel();
const usersModel = async () => await usersListModel(getingusersLogic);
const useGetusersList = prepareStateManagementForVM<UsersModel>(url, usersModel);
const { selectedRowId, setSelectedRowId, usersData } = useUsersListVM({

View File

@ -3,13 +3,17 @@ import authAdminPhoneNumberDriven from '~/driven/adapters/auth-admin-phonenumber
import phonenumberAuthInfra from '~/business-logic/generic/admin-user/authentication/phonnumber-auth';
import authAdminLogin from '~/driven/adapters/auth-admin-login/authAdminLogin';
import otpAuthInfra from '~/business-logic/generic/admin-user/authentication/otp-auth';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import AuthenticationView from '../view/AuthenticationView';
export default function Authentication() {
const authPhonenumberDriven = authAdminPhoneNumberDriven();
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
const authPhonenumberDriven = authAdminPhoneNumberDriven(user, accessTokenUpdateHandler, notLoginAuth);
const authPhoneLogic = phonenumberAuthInfra(authPhonenumberDriven);
const authLoginDriven = authAdminLogin();
const authLoginDriven = authAdminLogin(user, accessTokenUpdateHandler, notLoginAuth);
const otpAuthLogic = otpAuthInfra(authLoginDriven.httpHandler);
return <AuthenticationView authPhone={authPhoneLogic} otpAuth={otpAuthLogic} />;

View File

@ -11,7 +11,7 @@ import Sidebar from '~/driving/application/support/sidebar';
*/
export default function MainPageLayout() {
const { user, setUser } = useUser();
console.log('hi');
useEffect(() => {
const storage = StorageService.localStorage<AdminUserModel>();
const currentUser = storage.getData(appConfig.adminUserStorageKey);

View File

@ -5,7 +5,7 @@ import { useUser } from '~/driven/utils/helpers/contexts/userContext';
export default function UserLoginLayout() {
const { user } = useUser();
if (user) return <Navigate to={routes.usersList} replace />;
console.log('hhhh');
if (user && user.adminUserData.accessToken) return <Navigate to={routes.usersList} replace />;
return <Outlet />;
}