Chore: Refactor app entry file

This commit is contained in:
Gustav Hansen 2023-08-15 11:39:28 +02:00
parent 0b2975dbed
commit a23be30700
12 changed files with 157 additions and 130 deletions

View File

@ -22,7 +22,7 @@ import {
} from './exposedHooks';
import favicon from './favicon.png';
import injectionZones from './injectionZones';
import App from './pages/App';
import { App } from './pages/App';
import languageNativeNames from './translations/languageNativeNames';
class StrapiApp {

View File

@ -27,7 +27,7 @@ import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
const strapiVersion = packageJSON.version;
const AuthenticatedApp = () => {
export const AuthenticatedApp = () => {
const { setGuidedTourVisibility } = useGuidedTour();
const toggleNotification = useNotification();
const userInfo = auth.getUserInfo();

View File

@ -52,7 +52,7 @@ const run = async () => {
// We need to make sure to fetch the project type before importing the StrapiApp
// otherwise the strapi-babel-plugin does not work correctly
const StrapiApp = await import(/* webpackChunkName: "admin-app" */ './StrapiApp');
const StrapiApp = await import(/* webpackChunkName: "StrapiApp" */ './StrapiApp');
const app = StrapiApp.default({
appPlugins: plugins,

View File

@ -4,7 +4,7 @@
*
*/
import React, { lazy, Suspense, useEffect, useMemo, useState } from 'react';
import * as React from 'react';
import { SkipToContent } from '@strapi/design-system';
import {
@ -12,31 +12,52 @@ import {
LoadingIndicatorPage,
prefixFileUrlWithBackendUrl,
TrackingProvider,
useAppInfo,
useFetchClient,
useNotification,
} from '@strapi/helper-plugin';
import merge from 'lodash/merge';
import { useIntl } from 'react-intl';
import { useQueries } from 'react-query';
import { useDispatch } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import PrivateRoute from '../../components/PrivateRoute';
import { ADMIN_PERMISSIONS_CE } from '../../constants';
import { useConfigurations } from '../../hooks';
import useConfigurations from '../../hooks/useConfigurations';
import { useEnterprise } from '../../hooks/useEnterprise';
import { createRoute, makeUniqueRoutes } from '../../utils';
import AuthPage from '../AuthPage';
import NotFoundPage from '../NotFoundPage';
import UseCasePage from '../UseCasePage';
import createRoute from '../../utils/createRoute';
import { ROUTES_CE, SET_ADMIN_PERMISSIONS } from './constants';
const AuthenticatedApp = lazy(() =>
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp')
const AuthPage = React.lazy(() =>
import(/* webpackChunkName: "Admin-AuthPage" */ '../AuthPage').then((module) => ({
default: module.AuthPage,
}))
);
function App() {
const AuthenticatedApp = React.lazy(() =>
import(/* webpackChunkName: "Admin-AuthenticatedApp" */ '../../components/AuthenticatedApp').then(
(module) => ({ default: module.AuthenticatedApp })
)
);
const UseCasePage = React.lazy(() =>
import(/* webpackChunkName: "Admin-UseCasePage" */ '../UseCasePage').then((module) => ({
default: module.UseCasePage,
}))
);
const NotFoundPage = React.lazy(() =>
import(/* webpackChunkName: "Admin_NotFoundPage" */ '../NotFoundPage').then((module) => ({
default: module.NotFoundPage,
}))
);
export function App() {
const { updateProjectSettings } = useConfigurations();
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const { get, post } = useFetchClient();
const adminPermissions = useEnterprise(
ADMIN_PERMISSIONS_CE,
async () => (await import('../../../../ee/admin/constants')).ADMIN_PERMISSIONS_EE,
@ -49,6 +70,7 @@ function App() {
defaultValue: ADMIN_PERMISSIONS_CE,
}
);
const routes = useEnterprise(
ROUTES_CE,
async () => (await import('../../../../ee/admin/pages/App/constants')).ROUTES_EE,
@ -56,138 +78,158 @@ function App() {
defaultValue: [],
}
);
const toggleNotification = useNotification();
const { updateProjectSettings } = useConfigurations();
const { formatMessage } = useIntl();
const [{ isLoading, hasAdmin, uuid, deviceId }, setState] = useState({
isLoading: true,
const [{ hasAdmin, uuid }, setState] = React.useState({
hasAdmin: false,
uuid: undefined,
});
const dispatch = useDispatch();
const appInfo = useAppInfo();
const { get, post } = useFetchClient();
const authRoutes = useMemo(() => {
return makeUniqueRoutes(
routes.map(({ to, Component, exact }) => createRoute(Component, to, exact))
);
}, [routes]);
const [telemetryProperties, setTelemetryProperties] = useState(null);
useEffect(() => {
// Store permissions in redux
React.useEffect(() => {
dispatch({ type: SET_ADMIN_PERMISSIONS, payload: adminPermissions });
}, [adminPermissions, dispatch]);
useEffect(() => {
const currentToken = auth.getToken();
const renewToken = async () => {
try {
const [
{ data: token, error: errorRenewToken },
{ data: initData, isLoading: isLoadingInit },
{ data: telemetryProperties },
] = useQueries([
{
queryKey: 'renew-token',
async queryFn() {
const {
data: {
data: { token },
},
} = await post('/admin/renew-token', { token: currentToken });
auth.updateToken(token);
} catch (err) {
// Refresh app
auth.clearAppStorage();
window.location.reload();
}
};
} = await post('/admin/renew-token', { token: auth.getToken() });
if (currentToken) {
renewToken();
}
}, [post]);
return token;
},
useEffect(() => {
const getData = async () => {
try {
enabled: !!auth.getToken(),
},
{
queryKey: 'init',
async queryFn() {
const {
data: {
data: { hasAdmin, uuid, menuLogo, authLogo },
},
data: { data },
} = await get(`/admin/init`);
updateProjectSettings({
menuLogo: prefixFileUrlWithBackendUrl(menuLogo),
authLogo: prefixFileUrlWithBackendUrl(authLogo),
return data;
},
},
{
queryKey: 'telemetry-properties',
async queryFn() {
const {
data: { data },
} = await get(`/admin/telemetry-properties`, {
// NOTE: needed because the interceptors of the fetchClient redirect to /login when receive a
// 401 and it would end up in an infinite loop when the user doesn't have a session.
validateStatus: (status) => status < 500,
});
if (uuid) {
const {
data: { data: properties },
} = await get(`/admin/telemetry-properties`, {
// NOTE: needed because the interceptors of the fetchClient redirect to /login when receive a 401 and it would end up in an infinite loop when the user doesn't have a session.
validateStatus: (status) => status < 500,
});
return data;
},
setTelemetryProperties(properties);
enabled: !!auth.getToken(),
},
]);
try {
const event = 'didInitializeAdministration';
await post(
'https://analytics.strapi.io/api/v2/track',
{
// This event is anonymous
event,
userId: '',
deviceId,
eventPropeties: {},
userProperties: { environment: appInfo.currentEnvironment },
groupProperties: { ...properties, projectId: uuid },
},
{
headers: {
'X-Strapi-Event': event,
},
}
);
} catch (e) {
// Silent.
}
}
React.useEffect(() => {
// If the renew token could not be fetched, logout the user
if (errorRenewToken) {
auth.clearAppStorage();
window.location.reload();
} else if (token) {
auth.updateToken(token);
}
}, [errorRenewToken, token]);
setState({ isLoading: false, hasAdmin, uuid, deviceId });
} catch (err) {
toggleNotification({
type: 'warning',
message: { id: 'app.containers.App.notification.error.init' },
});
}
};
// Store the fetched project settings (e.g. logos)
// TODO: this should be moved to redux
React.useEffect(() => {
if (!isLoadingInit && initData) {
updateProjectSettings({
menuLogo: prefixFileUrlWithBackendUrl(initData.menuLogo),
authLogo: prefixFileUrlWithBackendUrl(initData.authLogo),
});
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toggleNotification, updateProjectSettings]);
// TODO: this should be stored in redux
setState((prev) => ({
...prev,
hasAdmin: initData.hasAdmin,
uuid: initData.uuid,
}));
}
}, [initData, isLoadingInit, updateProjectSettings]);
const setHasAdmin = (hasAdmin) => setState((prev) => ({ ...prev, hasAdmin }));
// we can't use useTracking here, because `App` is not wrapped in the tracking provider
// context. This shouldn't use `useFetchClient`, because it does not talk to the admin
// API
React.useEffect(() => {
async function trackInitEvent() {
await fetch('https://analytics.strapi.io/api/v2/track', {
body: JSON.stringify({
event: 'didInitializeAdministration',
// This event is anonymous
userId: '',
eventPropeties: {},
userProperties: {},
groupProperties: { ...telemetryProperties, projectId: uuid },
}),
const trackingInfo = useMemo(
headers: {
'Content-Type': 'application/json',
'X-Strapi-Event': 'didInitializeAdministration',
},
method: 'POST',
});
}
if (uuid) {
trackInitEvent();
}
}, [telemetryProperties, uuid]);
const authRoutes = routes
.map(({ to, Component, exact }) => createRoute(Component, to, exact))
.filter(
(route, index, refArray) => refArray.findIndex((obj) => obj.key === route.key) === index
);
const trackingContext = React.useMemo(
() => ({
uuid,
telemetryProperties,
deviceId,
}),
[uuid, telemetryProperties, deviceId]
[uuid, telemetryProperties]
);
if (isLoading) {
if (isLoadingInit) {
return <LoadingIndicatorPage />;
}
return (
<Suspense fallback={<LoadingIndicatorPage />}>
<React.Suspense fallback={<LoadingIndicatorPage />}>
<SkipToContent>{formatMessage({ id: 'skipToContent' })}</SkipToContent>
<TrackingProvider value={trackingInfo}>
<TrackingProvider value={trackingContext}>
<Switch>
{authRoutes}
<Route
path="/auth/:authType"
render={(routerProps) => (
<AuthPage {...routerProps} setHasAdmin={setHasAdmin} hasAdmin={hasAdmin} />
<AuthPage
{...routerProps}
setHasAdmin={(hasAdmin) => {
// TODO: this should be a flag in redux
React.setState((prev) => ({ ...prev, hasAdmin }));
}}
hasAdmin={hasAdmin}
/>
)}
exact
/>
@ -196,8 +238,6 @@ function App() {
<Route path="" component={NotFoundPage} />
</Switch>
</TrackingProvider>
</Suspense>
</React.Suspense>
);
}
export default App;

View File

@ -18,7 +18,7 @@ import { FORMS } from './constants';
import init from './init';
import { initialState, reducer } from './reducer';
const AuthPage = ({ hasAdmin, setHasAdmin }) => {
export const AuthPage = ({ hasAdmin, setHasAdmin }) => {
const {
push,
location: { search },
@ -315,5 +315,3 @@ AuthPage.propTypes = {
hasAdmin: PropTypes.bool,
setHasAdmin: PropTypes.func.isRequired,
};
export default AuthPage;

View File

@ -11,7 +11,7 @@ import { LinkButton, useFocusWhenNavigate } from '@strapi/helper-plugin';
import { ArrowRight, EmptyPictures } from '@strapi/icons';
import { useIntl } from 'react-intl';
const NoContentType = () => {
export const NotFoundPage = () => {
const { formatMessage } = useIntl();
useFocusWhenNavigate();
@ -46,5 +46,3 @@ const NoContentType = () => {
</Main>
);
};
export default NoContentType;

View File

@ -6,7 +6,7 @@ import { createMemoryHistory } from 'history';
import { IntlProvider } from 'react-intl';
import { Router } from 'react-router-dom';
import NotFoundPage from '../index';
import { NotFoundPage } from '../index';
const history = createMemoryHistory();

View File

@ -8,8 +8,8 @@ import { Redirect, Route, Switch, useParams } from 'react-router-dom';
import { useSettingsMenu } from '../../hooks';
import { useEnterprise } from '../../hooks/useEnterprise';
import createRoute from '../../utils/createRoute';
import makeUniqueRoutes from '../../utils/makeUniqueRoutes';
// eslint-disable-next-line
import { createRoute, makeUniqueRoutes } from '../../utils';
import SettingsNav from './components/SettingsNav';
import { ROUTES_CE } from './constants';

View File

@ -69,7 +69,7 @@ const TypographyCenter = styled(Typography)`
text-align: center;
`;
const UseCasePage = () => {
export const UseCasePage = () => {
const toggleNotification = useNotification();
const { push, location } = useHistory();
const { formatMessage } = useIntl();
@ -171,5 +171,3 @@ const UseCasePage = () => {
</UnauthenticatedLayout>
);
};
export default UseCasePage;

View File

@ -7,7 +7,7 @@ import { createMemoryHistory } from 'history';
import { IntlProvider } from 'react-intl';
import { Router } from 'react-router-dom';
import UseCasePage from '../index';
import { UseCasePage } from '../index';
jest.mock('../../../components/LocalesProvider/useLocalesProvider', () => () => ({
changeLocale() {},

View File

@ -4,6 +4,5 @@ export { default as formatAPIErrors } from './formatAPIErrors';
export { default as getAttributesToDisplay } from './getAttributesToDisplay';
export { default as getExistingActions } from './getExistingActions';
export { default as getFullName } from './getFullName';
export { default as makeUniqueRoutes } from './makeUniqueRoutes';
export { default as sortLinks } from './sortLinks';
export { default as hashAdminUserEmail } from './uniqueAdminHash';

View File

@ -1,6 +0,0 @@
const makeUniqueRoutes = (routes) =>
routes.filter((route, index, refArray) => {
return refArray.findIndex((obj) => obj.key === route.key) === index;
});
export default makeUniqueRoutes;