Skip to content

Commit

Permalink
chore: revamp the frontend architecture (#6598)
Browse files Browse the repository at this point in the history
* feat: setup the app context to fetch users,licenses and feature flags

* feat: added global event listeners for after_login event

* feat: remove redux from app state and private route

* feat: syncronize the approutes file

* feat: cleanup the private routes

* feat: handle login and logout

* feat: cleanup the app layout file

* feat: cleanup and syncronize side nav item

* fix: minor small re-render issue

* feat: parallel processing for sync calls for faster bootup of application

* feat: some refactoring for private routes

* fix: entire application too much re-rendering

* fix: remove redux

* feat: some more corrections

* feat: fix all the files except signup

* feat: add app provider to the test-utils

* feat: should fix a lot of tests

* chore: fix more tests

* chore: fix more tests

* feat: fix some tests and corrected the redux mock

* feat: delete snapshot

* fix: test cases

* fix: pipeline actions test cases

* fix: billing test cases

* feat: update the signup API to accept isAnonymous and hasOptedUpdates

* chore: cleanup the console logs

* fix: indefinite loading on manage licenses screen

* fix: better handling and route to something_went_wrong in case of qs down

* fix: signup for subsequent users

* chore: update test-utils

* fix: jerky behaviour on entering the home page

* feat: handle the retention for login context flow

* fix: do not let users workaround workspace blocked screen
  • Loading branch information
vikrantgupta25 authored Dec 20, 2024
1 parent accafbc commit 26fe5e4
Show file tree
Hide file tree
Showing 136 changed files with 1,935 additions and 3,080 deletions.
359 changes: 94 additions & 265 deletions frontend/src/AppRoutes/Private.tsx

Large diffs are not rendered by default.

467 changes: 212 additions & 255 deletions frontend/src/AppRoutes/index.tsx

Large diffs are not rendered by default.

96 changes: 16 additions & 80 deletions frontend/src/AppRoutes/utils.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,28 @@
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import getUserApi from 'api/user/getUser';
import { Logout } from 'api/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import store from 'store';
import AppActions from 'types/actions';
import {
LOGGED_IN,
UPDATE_USER,
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
UPDATE_USER_IS_FETCH,
} from 'types/actions/app';
import { SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/user/getUser';

const afterLogin = async (
const afterLogin = (
userId: string,
authToken: string,
refreshToken: string,
): Promise<SuccessResponse<PayloadProps> | undefined> => {
interceptorRejected?: boolean,
): void => {
setLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN, authToken);
setLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN, refreshToken);

store.dispatch<AppActions>({
type: UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
payload: {
accessJwt: authToken,
refreshJwt: refreshToken,
},
});

const [getUserResponse] = await Promise.all([
getUserApi({
userId,
token: authToken,
}),
]);

if (getUserResponse.statusCode === 200 && getUserResponse.payload) {
store.dispatch<AppActions>({
type: LOGGED_IN,
payload: {
isLoggedIn: true,
},
});

const { payload } = getUserResponse;

store.dispatch<AppActions>({
type: UPDATE_USER,
payload: {
ROLE: payload.role,
email: payload.email,
name: payload.name,
orgName: payload.organization,
profilePictureURL: payload.profilePictureURL,
userId: payload.id,
orgId: payload.orgId,
userFlags: payload.flags,
},
});

const isLoggedInLocalStorage = getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN);

if (isLoggedInLocalStorage === null) {
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
}

store.dispatch({
type: UPDATE_USER_IS_FETCH,
payload: {
isUserFetching: false,
},
});

return getUserResponse;
setLocalStorageApi(LOCALSTORAGE.USER_ID, userId);
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');

if (!interceptorRejected) {
window.dispatchEvent(
new CustomEvent('AFTER_LOGIN', {
detail: {
accessJWT: authToken,
refreshJWT: refreshToken,
id: userId,
},
}),
);
}

store.dispatch({
type: UPDATE_USER_IS_FETCH,
payload: {
isUserFetching: false,
},
});

Logout();

return undefined;
};

export default afterLogin;
47 changes: 19 additions & 28 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import afterLogin from 'AppRoutes/utils';
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import store from 'store';

import apiV1, {
apiAlertManager,
Expand All @@ -26,10 +25,7 @@ const interceptorsResponse = (
const interceptorsRequestResponse = (
value: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const token =
store.getState().app.user?.accessJwt ||
getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) ||
'';
const token = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || '';

if (value && value.headers) {
value.headers.Authorization = token ? `Bearer ${token}` : '';
Expand All @@ -47,41 +43,36 @@ const interceptorRejected = async (
// reject the refresh token error
if (response.status === 401 && response.config.url !== '/login') {
const response = await loginApi({
refreshToken: store.getState().app.user?.refreshJwt,
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
});

if (response.statusCode === 200) {
const user = await afterLogin(
afterLogin(
response.payload.userId,
response.payload.accessJwt,
response.payload.refreshJwt,
true,
);

if (user) {
const reResponse = await axios(
`${value.config.baseURL}${value.config.url?.substring(1)}`,
{
method: value.config.method,
headers: {
...value.config.headers,
Authorization: `Bearer ${response.payload.accessJwt}`,
},
data: {
...JSON.parse(value.config.data || '{}'),
},
const reResponse = await axios(
`${value.config.baseURL}${value.config.url?.substring(1)}`,
{
method: value.config.method,
headers: {
...value.config.headers,
Authorization: `Bearer ${response.payload.accessJwt}`,
},
);

if (reResponse.status === 200) {
return await Promise.resolve(reResponse);
}
Logout();
data: {
...JSON.parse(value.config.data || '{}'),
},
},
);

return await Promise.reject(reResponse);
if (reResponse.status === 200) {
return await Promise.resolve(reResponse);
}
Logout();

return await Promise.reject(value);
return await Promise.reject(reResponse);
}
Logout();
}
Expand Down
20 changes: 7 additions & 13 deletions frontend/src/api/licenses/getAll.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { ApiV2Instance as axios } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/licenses/getAll';

const getAll = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get('/licenses');
const response = await axios.get('/licenses');

return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};

export default getAll;
24 changes: 7 additions & 17 deletions frontend/src/api/user/getUser.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/getUser';

const getUser = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(`/user/${props.userId}`, {
headers: {
Authorization: `bearer ${props.token}`,
},
});
const response = await axios.get(`/user/${props.userId}`);

return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
};
};

export default getUser;
53 changes: 2 additions & 51 deletions frontend/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@ import deleteLocalStorageKey from 'api/browser/localstorage/remove';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import store from 'store';
import {
LOGGED_IN,
UPDATE_ORG,
UPDATE_USER,
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
UPDATE_USER_ORG_ROLE,
} from 'types/actions/app';

export const Logout = (): void => {
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
Expand All @@ -19,50 +11,9 @@ export const Logout = (): void => {
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_EMAIL);
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_NAME);
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
deleteLocalStorageKey(LOCALSTORAGE.USER_ID);

store.dispatch({
type: LOGGED_IN,
payload: {
isLoggedIn: false,
},
});

store.dispatch({
type: UPDATE_USER_ORG_ROLE,
payload: {
org: null,
role: null,
},
});

store.dispatch({
type: UPDATE_USER,
payload: {
ROLE: 'VIEWER',
email: '',
name: '',
orgId: '',
orgName: '',
profilePictureURL: '',
userId: '',
userFlags: {},
},
});

store.dispatch({
type: UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
payload: {
accessJwt: '',
refreshJwt: '',
},
});

store.dispatch({
type: UPDATE_ORG,
payload: {
org: [],
},
});
window.dispatchEvent(new CustomEvent('LOGOUT'));

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Button, Modal, Typography } from 'antd';
import updateCreditCardApi from 'api/billing/checkout';
import logEvent from 'api/common/logEvent';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import { CreditCard, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
Expand All @@ -20,16 +20,16 @@ export default function ChatSupportGateway(): JSX.Element {
false,
);

const { data: licenseData, isFetching } = useLicense();
const { licenses, isFetchingLicenses } = useAppContext();

useEffect(() => {
const activeValidLicense =
licenseData?.payload?.licenses?.find(
(license) => license.isCurrent === true,
) || null;
if (!isFetchingLicenses && licenses) {
const activeValidLicense =
licenses.licenses?.find((license) => license.isCurrent === true) || null;

setActiveLicense(activeValidLicense);
}, [licenseData, isFetching]);
setActiveLicense(activeValidLicense);
}
}, [licenses, isFetchingLicenses]);

const handleBillingOnSuccess = (
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
Expand Down
Loading

0 comments on commit 26fe5e4

Please sign in to comment.