diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js
index bbe0ef2bad..36867db510 100644
--- a/packages/core/admin/admin/src/StrapiApp.js
+++ b/packages/core/admin/admin/src/StrapiApp.js
@@ -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 {
diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js b/packages/core/admin/admin/src/components/AuthenticatedApp/index.js
index 8a83736507..ae55ef90fb 100644
--- a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js
+++ b/packages/core/admin/admin/src/components/AuthenticatedApp/index.js
@@ -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();
diff --git a/packages/core/admin/admin/src/index.js b/packages/core/admin/admin/src/index.js
index 65efa9596f..4508b2171c 100644
--- a/packages/core/admin/admin/src/index.js
+++ b/packages/core/admin/admin/src/index.js
@@ -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,
diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js
index 216c138011..67962ba2e0 100644
--- a/packages/core/admin/admin/src/pages/App/index.js
+++ b/packages/core/admin/admin/src/pages/App/index.js
@@ -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 ;
}
return (
- }>
+ }>
{formatMessage({ id: 'skipToContent' })}
-
+
{authRoutes}
(
-
+ {
+ // TODO: this should be a flag in redux
+ React.setState((prev) => ({ ...prev, hasAdmin }));
+ }}
+ hasAdmin={hasAdmin}
+ />
)}
exact
/>
@@ -196,8 +238,6 @@ function App() {
-
+
);
}
-
-export default App;
diff --git a/packages/core/admin/admin/src/pages/AuthPage/index.js b/packages/core/admin/admin/src/pages/AuthPage/index.js
index 0f3c6568f9..72f8d42244 100644
--- a/packages/core/admin/admin/src/pages/AuthPage/index.js
+++ b/packages/core/admin/admin/src/pages/AuthPage/index.js
@@ -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;
diff --git a/packages/core/admin/admin/src/pages/NotFoundPage/index.js b/packages/core/admin/admin/src/pages/NotFoundPage/index.js
index a48d81b1b2..7d82f33447 100644
--- a/packages/core/admin/admin/src/pages/NotFoundPage/index.js
+++ b/packages/core/admin/admin/src/pages/NotFoundPage/index.js
@@ -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 = () => {
);
};
-
-export default NoContentType;
diff --git a/packages/core/admin/admin/src/pages/NotFoundPage/tests/index.test.js b/packages/core/admin/admin/src/pages/NotFoundPage/tests/index.test.js
index 028c91b616..cb47ebeed0 100644
--- a/packages/core/admin/admin/src/pages/NotFoundPage/tests/index.test.js
+++ b/packages/core/admin/admin/src/pages/NotFoundPage/tests/index.test.js
@@ -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();
diff --git a/packages/core/admin/admin/src/pages/SettingsPage/index.js b/packages/core/admin/admin/src/pages/SettingsPage/index.js
index a647420d44..7bf137fc53 100644
--- a/packages/core/admin/admin/src/pages/SettingsPage/index.js
+++ b/packages/core/admin/admin/src/pages/SettingsPage/index.js
@@ -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';
diff --git a/packages/core/admin/admin/src/pages/UseCasePage/index.js b/packages/core/admin/admin/src/pages/UseCasePage/index.js
index 40e08fea9d..7b7de0f401 100644
--- a/packages/core/admin/admin/src/pages/UseCasePage/index.js
+++ b/packages/core/admin/admin/src/pages/UseCasePage/index.js
@@ -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 = () => {
);
};
-
-export default UseCasePage;
diff --git a/packages/core/admin/admin/src/pages/UseCasePage/tests/index.test.js b/packages/core/admin/admin/src/pages/UseCasePage/tests/index.test.js
index e54abf0545..f8ce1ae3dd 100644
--- a/packages/core/admin/admin/src/pages/UseCasePage/tests/index.test.js
+++ b/packages/core/admin/admin/src/pages/UseCasePage/tests/index.test.js
@@ -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() {},
diff --git a/packages/core/admin/admin/src/utils/index.js b/packages/core/admin/admin/src/utils/index.js
index 4a2531048d..3dcbfd588a 100644
--- a/packages/core/admin/admin/src/utils/index.js
+++ b/packages/core/admin/admin/src/utils/index.js
@@ -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';
diff --git a/packages/core/admin/admin/src/utils/makeUniqueRoutes.js b/packages/core/admin/admin/src/utils/makeUniqueRoutes.js
deleted file mode 100644
index c837b362f0..0000000000
--- a/packages/core/admin/admin/src/utils/makeUniqueRoutes.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const makeUniqueRoutes = (routes) =>
- routes.filter((route, index, refArray) => {
- return refArray.findIndex((obj) => obj.key === route.key) === index;
- });
-
-export default makeUniqueRoutes;