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'; } from './exposedHooks';
import favicon from './favicon.png'; import favicon from './favicon.png';
import injectionZones from './injectionZones'; import injectionZones from './injectionZones';
import App from './pages/App'; import { App } from './pages/App';
import languageNativeNames from './translations/languageNativeNames'; import languageNativeNames from './translations/languageNativeNames';
class StrapiApp { class StrapiApp {

View File

@ -27,7 +27,7 @@ import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
const strapiVersion = packageJSON.version; const strapiVersion = packageJSON.version;
const AuthenticatedApp = () => { export const AuthenticatedApp = () => {
const { setGuidedTourVisibility } = useGuidedTour(); const { setGuidedTourVisibility } = useGuidedTour();
const toggleNotification = useNotification(); const toggleNotification = useNotification();
const userInfo = auth.getUserInfo(); 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 // We need to make sure to fetch the project type before importing the StrapiApp
// otherwise the strapi-babel-plugin does not work correctly // 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({ const app = StrapiApp.default({
appPlugins: plugins, 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 { SkipToContent } from '@strapi/design-system';
import { import {
@ -12,31 +12,52 @@ import {
LoadingIndicatorPage, LoadingIndicatorPage,
prefixFileUrlWithBackendUrl, prefixFileUrlWithBackendUrl,
TrackingProvider, TrackingProvider,
useAppInfo,
useFetchClient, useFetchClient,
useNotification,
} from '@strapi/helper-plugin'; } from '@strapi/helper-plugin';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { useQueries } from 'react-query';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import PrivateRoute from '../../components/PrivateRoute'; import PrivateRoute from '../../components/PrivateRoute';
import { ADMIN_PERMISSIONS_CE } from '../../constants'; import { ADMIN_PERMISSIONS_CE } from '../../constants';
import { useConfigurations } from '../../hooks'; import useConfigurations from '../../hooks/useConfigurations';
import { useEnterprise } from '../../hooks/useEnterprise'; import { useEnterprise } from '../../hooks/useEnterprise';
import { createRoute, makeUniqueRoutes } from '../../utils'; import createRoute from '../../utils/createRoute';
import AuthPage from '../AuthPage';
import NotFoundPage from '../NotFoundPage';
import UseCasePage from '../UseCasePage';
import { ROUTES_CE, SET_ADMIN_PERMISSIONS } from './constants'; import { ROUTES_CE, SET_ADMIN_PERMISSIONS } from './constants';
const AuthenticatedApp = lazy(() => const AuthPage = React.lazy(() =>
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp') 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( const adminPermissions = useEnterprise(
ADMIN_PERMISSIONS_CE, ADMIN_PERMISSIONS_CE,
async () => (await import('../../../../ee/admin/constants')).ADMIN_PERMISSIONS_EE, async () => (await import('../../../../ee/admin/constants')).ADMIN_PERMISSIONS_EE,
@ -49,6 +70,7 @@ function App() {
defaultValue: ADMIN_PERMISSIONS_CE, defaultValue: ADMIN_PERMISSIONS_CE,
} }
); );
const routes = useEnterprise( const routes = useEnterprise(
ROUTES_CE, ROUTES_CE,
async () => (await import('../../../../ee/admin/pages/App/constants')).ROUTES_EE, async () => (await import('../../../../ee/admin/pages/App/constants')).ROUTES_EE,
@ -56,138 +78,158 @@ function App() {
defaultValue: [], defaultValue: [],
} }
); );
const toggleNotification = useNotification();
const { updateProjectSettings } = useConfigurations(); const [{ hasAdmin, uuid }, setState] = React.useState({
const { formatMessage } = useIntl();
const [{ isLoading, hasAdmin, uuid, deviceId }, setState] = useState({
isLoading: true,
hasAdmin: false, hasAdmin: false,
uuid: undefined,
}); });
const dispatch = useDispatch();
const appInfo = useAppInfo();
const { get, post } = useFetchClient();
const authRoutes = useMemo(() => { // Store permissions in redux
return makeUniqueRoutes( React.useEffect(() => {
routes.map(({ to, Component, exact }) => createRoute(Component, to, exact))
);
}, [routes]);
const [telemetryProperties, setTelemetryProperties] = useState(null);
useEffect(() => {
dispatch({ type: SET_ADMIN_PERMISSIONS, payload: adminPermissions }); dispatch({ type: SET_ADMIN_PERMISSIONS, payload: adminPermissions });
}, [adminPermissions, dispatch]); }, [adminPermissions, dispatch]);
useEffect(() => { const [
const currentToken = auth.getToken(); { data: token, error: errorRenewToken },
{ data: initData, isLoading: isLoadingInit },
const renewToken = async () => { { data: telemetryProperties },
try { ] = useQueries([
{
queryKey: 'renew-token',
async queryFn() {
const { const {
data: { data: {
data: { token }, data: { token },
}, },
} = await post('/admin/renew-token', { token: currentToken }); } = await post('/admin/renew-token', { token: auth.getToken() });
auth.updateToken(token);
} catch (err) {
// Refresh app
auth.clearAppStorage();
window.location.reload();
}
};
if (currentToken) { return token;
renewToken();
}
}, [post]);
useEffect(() => {
const getData = async () => {
try {
const {
data: {
data: { hasAdmin, uuid, menuLogo, authLogo },
}, },
enabled: !!auth.getToken(),
},
{
queryKey: 'init',
async queryFn() {
const {
data: { data },
} = await get(`/admin/init`); } = await get(`/admin/init`);
updateProjectSettings({ return data;
menuLogo: prefixFileUrlWithBackendUrl(menuLogo), },
authLogo: prefixFileUrlWithBackendUrl(authLogo), },
});
if (uuid) { {
queryKey: 'telemetry-properties',
async queryFn() {
const { const {
data: { data: properties }, data: { data },
} = await get(`/admin/telemetry-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. // 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, validateStatus: (status) => status < 500,
}); });
setTelemetryProperties(properties); return data;
},
try { enabled: !!auth.getToken(),
const event = 'didInitializeAdministration'; },
await post( ]);
'https://analytics.strapi.io/api/v2/track',
{ 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]);
// 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),
});
// TODO: this should be stored in redux
setState((prev) => ({
...prev,
hasAdmin: initData.hasAdmin,
uuid: initData.uuid,
}));
}
}, [initData, isLoadingInit, updateProjectSettings]);
// 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 // This event is anonymous
event,
userId: '', userId: '',
deviceId,
eventPropeties: {}, eventPropeties: {},
userProperties: { environment: appInfo.currentEnvironment }, userProperties: {},
groupProperties: { ...properties, projectId: uuid }, groupProperties: { ...telemetryProperties, projectId: uuid },
}, }),
{
headers: {
'X-Strapi-Event': event,
},
}
);
} catch (e) {
// Silent.
}
}
setState({ isLoading: false, hasAdmin, uuid, deviceId }); headers: {
} catch (err) { 'Content-Type': 'application/json',
toggleNotification({ 'X-Strapi-Event': 'didInitializeAdministration',
type: 'warning', },
message: { id: 'app.containers.App.notification.error.init' },
method: 'POST',
}); });
} }
};
getData(); if (uuid) {
// eslint-disable-next-line react-hooks/exhaustive-deps trackInitEvent();
}, [toggleNotification, updateProjectSettings]); }
}, [telemetryProperties, uuid]);
const setHasAdmin = (hasAdmin) => setState((prev) => ({ ...prev, hasAdmin })); const authRoutes = routes
.map(({ to, Component, exact }) => createRoute(Component, to, exact))
.filter(
(route, index, refArray) => refArray.findIndex((obj) => obj.key === route.key) === index
);
const trackingInfo = useMemo( const trackingContext = React.useMemo(
() => ({ () => ({
uuid, uuid,
telemetryProperties, telemetryProperties,
deviceId,
}), }),
[uuid, telemetryProperties, deviceId] [uuid, telemetryProperties]
); );
if (isLoading) { if (isLoadingInit) {
return <LoadingIndicatorPage />; return <LoadingIndicatorPage />;
} }
return ( return (
<Suspense fallback={<LoadingIndicatorPage />}> <React.Suspense fallback={<LoadingIndicatorPage />}>
<SkipToContent>{formatMessage({ id: 'skipToContent' })}</SkipToContent> <SkipToContent>{formatMessage({ id: 'skipToContent' })}</SkipToContent>
<TrackingProvider value={trackingInfo}> <TrackingProvider value={trackingContext}>
<Switch> <Switch>
{authRoutes} {authRoutes}
<Route <Route
path="/auth/:authType" path="/auth/:authType"
render={(routerProps) => ( 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 exact
/> />
@ -196,8 +238,6 @@ function App() {
<Route path="" component={NotFoundPage} /> <Route path="" component={NotFoundPage} />
</Switch> </Switch>
</TrackingProvider> </TrackingProvider>
</Suspense> </React.Suspense>
); );
} }
export default App;

View File

@ -18,7 +18,7 @@ import { FORMS } from './constants';
import init from './init'; import init from './init';
import { initialState, reducer } from './reducer'; import { initialState, reducer } from './reducer';
const AuthPage = ({ hasAdmin, setHasAdmin }) => { export const AuthPage = ({ hasAdmin, setHasAdmin }) => {
const { const {
push, push,
location: { search }, location: { search },
@ -315,5 +315,3 @@ AuthPage.propTypes = {
hasAdmin: PropTypes.bool, hasAdmin: PropTypes.bool,
setHasAdmin: PropTypes.func.isRequired, 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 { ArrowRight, EmptyPictures } from '@strapi/icons';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
const NoContentType = () => { export const NotFoundPage = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
useFocusWhenNavigate(); useFocusWhenNavigate();
@ -46,5 +46,3 @@ const NoContentType = () => {
</Main> </Main>
); );
}; };
export default NoContentType;

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,5 @@ export { default as formatAPIErrors } from './formatAPIErrors';
export { default as getAttributesToDisplay } from './getAttributesToDisplay'; export { default as getAttributesToDisplay } from './getAttributesToDisplay';
export { default as getExistingActions } from './getExistingActions'; export { default as getExistingActions } from './getExistingActions';
export { default as getFullName } from './getFullName'; export { default as getFullName } from './getFullName';
export { default as makeUniqueRoutes } from './makeUniqueRoutes';
export { default as sortLinks } from './sortLinks'; export { default as sortLinks } from './sortLinks';
export { default as hashAdminUserEmail } from './uniqueAdminHash'; 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;