mirror of
https://github.com/strapi/strapi.git
synced 2025-11-05 04:13:36 +00:00
Merge pull request #17844 from strapi/revert-17685-fix/code-splitting
Revert "Fix: Use sync over async components in admin panel APIs"
This commit is contained in:
commit
ba1b99b408
@ -1,4 +1,4 @@
|
|||||||
import * as React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { darkTheme, lightTheme } from '@strapi/design-system';
|
import { darkTheme, lightTheme } from '@strapi/design-system';
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
@ -114,30 +114,14 @@ class StrapiApp {
|
|||||||
);
|
);
|
||||||
invariant(
|
invariant(
|
||||||
link.Component && typeof link.Component === 'function',
|
link.Component && typeof link.Component === 'function',
|
||||||
`link.Component must be a function returning a Promise. Please use: \`Component: () => import(path)\` instead.`
|
`link.Component should be a valid React Component`
|
||||||
);
|
);
|
||||||
invariant(
|
invariant(
|
||||||
link.icon && typeof link.icon === 'function',
|
link.icon && typeof link.icon === 'function',
|
||||||
`link.Icon should be a valid React Component`
|
`link.Icon should be a valid React Component`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
this.menu.push(link);
|
||||||
link.Component &&
|
|
||||||
typeof link.Component === 'function' &&
|
|
||||||
link.Component[Symbol.toStringTag] === 'AsyncFunction'
|
|
||||||
) {
|
|
||||||
console.warn(`
|
|
||||||
[deprecated] addMenuLink() was called with an async Component from the plugin "${link.intlLabel.Internationalization}". This will be removed
|
|
||||||
in the future. Please use: \`Component: () => import(path)\` instead.
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.menu.push({
|
|
||||||
...link,
|
|
||||||
|
|
||||||
// React.lazy can be removed once we migrate to react-router@6, because the <Route /> component can handle it natively
|
|
||||||
Component: React.lazy(link.Component),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
addMiddlewares = (middlewares) => {
|
addMiddlewares = (middlewares) => {
|
||||||
@ -165,26 +149,10 @@ class StrapiApp {
|
|||||||
invariant(link.to, `link.to should be defined for ${stringifiedLink}`);
|
invariant(link.to, `link.to should be defined for ${stringifiedLink}`);
|
||||||
invariant(
|
invariant(
|
||||||
link.Component && typeof link.Component === 'function',
|
link.Component && typeof link.Component === 'function',
|
||||||
`link.Component must be a function returning a Promise. Please use: \`Component: () => import(path)\` instead.`
|
`link.Component should be a valid React Component`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
this.settings[sectionId].links.push(link);
|
||||||
link.Component &&
|
|
||||||
typeof link.Component === 'function' &&
|
|
||||||
link.Component[Symbol.toStringTag] === 'AsyncFunction'
|
|
||||||
) {
|
|
||||||
console.warn(`
|
|
||||||
[deprecated] addSettingsLink() was called with an async Component from the plugin: "${link.intlLabel.Internationalization}". This will be removed
|
|
||||||
in the future. Please use: \`Component: () => import(path)\` instead.
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings[sectionId].links.push({
|
|
||||||
...link,
|
|
||||||
|
|
||||||
// React.lazy can be removed once we migrate to react-router@6, because the <Route /> component can handle it natively
|
|
||||||
Component: React.lazy(link.Component),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
addSettingsLinks = (sectionId, links) => {
|
addSettingsLinks = (sectionId, links) => {
|
||||||
|
|||||||
@ -111,8 +111,7 @@ Providers.propTypes = {
|
|||||||
defaultMessage: PropTypes.string.isRequired,
|
defaultMessage: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
permissions: PropTypes.array,
|
permissions: PropTypes.array,
|
||||||
// React.lazy loadable
|
Component: PropTypes.func,
|
||||||
Component: PropTypes.object,
|
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
menuLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
|
menuLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { Route, Switch } from 'react-router-dom';
|
|||||||
import LeftMenu from '../../components/LeftMenu';
|
import LeftMenu from '../../components/LeftMenu';
|
||||||
import useConfigurations from '../../hooks/useConfigurations';
|
import useConfigurations from '../../hooks/useConfigurations';
|
||||||
import useMenu from '../../hooks/useMenu';
|
import useMenu from '../../hooks/useMenu';
|
||||||
|
import { createRoute } from '../../utils/createRoute';
|
||||||
import { SET_APP_RUNTIME_STATUS } from '../App/constants';
|
import { SET_APP_RUNTIME_STATUS } from '../App/constants';
|
||||||
|
|
||||||
const CM = React.lazy(() =>
|
const CM = React.lazy(() =>
|
||||||
@ -75,6 +76,25 @@ export const Admin = () => {
|
|||||||
}
|
}
|
||||||
}, [appStatus, dispatch, trackUsage]);
|
}, [appStatus, dispatch, trackUsage]);
|
||||||
|
|
||||||
|
const routes = menu
|
||||||
|
.filter((link) => link.Component)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Component` is an async function, which is passed as property of the
|
||||||
|
* addMenuLink() API during the plugin registration step.
|
||||||
|
*
|
||||||
|
* Because of that we can't just render <Route component={Component} />,
|
||||||
|
* but have to await the function.
|
||||||
|
*
|
||||||
|
* This isn't a good React pattern and should be reconsidered.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.map(({ to, Component, exact }) => createRoute(Component, to, exact));
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicatorPage />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Flex alignItems="stretch">
|
<Flex alignItems="stretch">
|
||||||
@ -84,31 +104,18 @@ export const Admin = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Box flex="1">
|
<Box flex="1">
|
||||||
{isLoading ? (
|
<React.Suspense fallback={<LoadingIndicatorPage />}>
|
||||||
<LoadingIndicatorPage />
|
|
||||||
) : (
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" component={HomePage} exact />
|
<Route path="/" component={HomePage} exact />
|
||||||
<Route path="/me" component={ProfilePage} exact />
|
<Route path="/me" component={ProfilePage} exact />
|
||||||
<Route path="/content-manager" component={CM} />
|
<Route path="/content-manager" component={CM} />
|
||||||
{menu.map(({ to, Component, exact }) => (
|
{routes}
|
||||||
<Route
|
|
||||||
render={() => (
|
|
||||||
<React.Suspense fallback={<LoadingIndicatorPage />}>
|
|
||||||
<Component />
|
|
||||||
</React.Suspense>
|
|
||||||
)}
|
|
||||||
key={to}
|
|
||||||
path={to}
|
|
||||||
exact={exact || false}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<Route path="/settings/:settingId" component={SettingsPage} />
|
<Route path="/settings/:settingId" component={SettingsPage} />
|
||||||
<Route path="/settings" component={SettingsPage} exact />
|
<Route path="/settings" component={SettingsPage} exact />
|
||||||
<Route path="/marketplace" component={MarketplacePage} />
|
<Route path="/marketplace" component={MarketplacePage} />
|
||||||
<Route path="/list-plugins" component={InstalledPluginsPage} exact />
|
<Route path="/list-plugins" component={InstalledPluginsPage} exact />
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
</React.Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* TODO: we should move the logic to determine whether the guided tour is displayed
|
{/* TODO: we should move the logic to determine whether the guided tour is displayed
|
||||||
|
|||||||
@ -2,67 +2,67 @@ import * as React from 'react';
|
|||||||
|
|
||||||
export const SETTINGS_ROUTES_CE = [
|
export const SETTINGS_ROUTES_CE = [
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-roles-list" */ './pages/Roles/ProtectedListPage')
|
import(/* webpackChunkName: "admin-roles-list" */ './pages/Roles/ProtectedListPage')
|
||||||
),
|
),
|
||||||
path: '/settings/roles',
|
path: '/settings/roles',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/CreatePage')
|
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/CreatePage')
|
||||||
),
|
),
|
||||||
path: '/settings/roles/duplicate/:id',
|
path: '/settings/roles/duplicate/:id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/CreatePage')
|
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/CreatePage')
|
||||||
),
|
),
|
||||||
path: '/settings/roles/new',
|
path: '/settings/roles/new',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/ProtectedEditPage')
|
import(/* webpackChunkName: "admin-edit-roles-page" */ './pages/Roles/ProtectedEditPage')
|
||||||
),
|
),
|
||||||
path: '/settings/roles/:id',
|
path: '/settings/roles/:id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-users" */ './pages/Users/ProtectedListPage')
|
import(/* webpackChunkName: "admin-users" */ './pages/Users/ProtectedListPage')
|
||||||
),
|
),
|
||||||
path: '/settings/users',
|
path: '/settings/users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "admin-edit-users" */ './pages/Users/ProtectedEditPage')
|
import(/* webpackChunkName: "admin-edit-users" */ './pages/Users/ProtectedEditPage')
|
||||||
),
|
),
|
||||||
path: '/settings/users/:id',
|
path: '/settings/users/:id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "webhook-edit-page" */ './pages/Webhooks/ProtectedCreateView')
|
import(/* webpackChunkName: "webhook-edit-page" */ './pages/Webhooks/ProtectedCreateView')
|
||||||
),
|
),
|
||||||
path: '/settings/webhooks/create',
|
path: '/settings/webhooks/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "webhook-edit-page" */ './pages/Webhooks/ProtectedEditView')
|
import(/* webpackChunkName: "webhook-edit-page" */ './pages/Webhooks/ProtectedEditView')
|
||||||
),
|
),
|
||||||
path: '/settings/webhooks/:id',
|
path: '/settings/webhooks/:id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "webhook-list-page" */ './pages/Webhooks/ProtectedListView')
|
import(/* webpackChunkName: "webhook-list-page" */ './pages/Webhooks/ProtectedListView')
|
||||||
),
|
),
|
||||||
path: '/settings/webhooks',
|
path: '/settings/webhooks',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "api-tokens-list-page" */ './pages/ApiTokens/ProtectedListView')
|
import(/* webpackChunkName: "api-tokens-list-page" */ './pages/ApiTokens/ProtectedListView')
|
||||||
),
|
),
|
||||||
path: '/settings/api-tokens',
|
path: '/settings/api-tokens',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "api-tokens-create-page" */ './pages/ApiTokens/ProtectedCreateView'
|
/* webpackChunkName: "api-tokens-create-page" */ './pages/ApiTokens/ProtectedCreateView'
|
||||||
)
|
)
|
||||||
@ -70,13 +70,13 @@ export const SETTINGS_ROUTES_CE = [
|
|||||||
path: '/settings/api-tokens/create',
|
path: '/settings/api-tokens/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "api-tokens-edit-page" */ './pages/ApiTokens/ProtectedEditView')
|
import(/* webpackChunkName: "api-tokens-edit-page" */ './pages/ApiTokens/ProtectedEditView')
|
||||||
),
|
),
|
||||||
path: '/settings/api-tokens/:id',
|
path: '/settings/api-tokens/:id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "transfer-tokens-create-page" */ './pages/TransferTokens/ProtectedCreateView'
|
/* webpackChunkName: "transfer-tokens-create-page" */ './pages/TransferTokens/ProtectedCreateView'
|
||||||
)
|
)
|
||||||
@ -84,7 +84,7 @@ export const SETTINGS_ROUTES_CE = [
|
|||||||
path: '/settings/transfer-tokens/create',
|
path: '/settings/transfer-tokens/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "transfer-tokens-list-page" */ './pages/TransferTokens/ProtectedListView'
|
/* webpackChunkName: "transfer-tokens-list-page" */ './pages/TransferTokens/ProtectedListView'
|
||||||
)
|
)
|
||||||
@ -92,7 +92,7 @@ export const SETTINGS_ROUTES_CE = [
|
|||||||
path: '/settings/transfer-tokens',
|
path: '/settings/transfer-tokens',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "transfer-tokens-edit-page" */ './pages/TransferTokens/ProtectedEditView'
|
/* webpackChunkName: "transfer-tokens-edit-page" */ './pages/TransferTokens/ProtectedEditView'
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ 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';
|
||||||
|
|
||||||
import SettingsNav from './components/SettingsNav';
|
import SettingsNav from './components/SettingsNav';
|
||||||
import { SETTINGS_ROUTES_CE } from './constants';
|
import { SETTINGS_ROUTES_CE } from './constants';
|
||||||
@ -30,6 +31,26 @@ export function SettingsPage() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Component` is an async function, which is passed as property of the
|
||||||
|
* addSettingsLink() API during the plugin bootstrap step.
|
||||||
|
*
|
||||||
|
* Because of that we can't just render <Route component={Component} />,
|
||||||
|
* but have to await the function.
|
||||||
|
*
|
||||||
|
* This isn't a good React pattern and should be reconsidered.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pluginSettingsRoutes = Object.values(settings).flatMap((section) =>
|
||||||
|
section.links.map((link) => createRoute(link.Component, link.to, link.exact || false))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Since the useSettingsMenu hook can make API calls in order to check the links permissions
|
||||||
|
// We need to add a loading state to prevent redirecting the user while permissions are being checked
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicatorPage />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!settingId) {
|
if (!settingId) {
|
||||||
return <Redirect to="/settings/application-infos" />;
|
return <Redirect to="/settings/application-infos" />;
|
||||||
}
|
}
|
||||||
@ -43,49 +64,15 @@ export function SettingsPage() {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isLoading ? (
|
|
||||||
<LoadingIndicatorPage />
|
|
||||||
) : (
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route path="/settings/application-infos" component={ApplicationInfosPage} exact />
|
||||||
path="/settings/application-infos"
|
|
||||||
render={() => (
|
|
||||||
<React.Suspense fallback={<LoadingIndicatorPage />}>
|
|
||||||
<ApplicationInfosPage />
|
|
||||||
</React.Suspense>
|
|
||||||
)}
|
|
||||||
exact
|
|
||||||
/>
|
|
||||||
|
|
||||||
{routes.map(({ path, Component }) => (
|
{routes.map(({ path, component }) => (
|
||||||
<Route
|
<Route key={path} path={path} component={component} exact />
|
||||||
key={path}
|
|
||||||
path={path}
|
|
||||||
render={() => (
|
|
||||||
<React.Suspense fallback={<LoadingIndicatorPage />}>
|
|
||||||
<Component />
|
|
||||||
</React.Suspense>
|
|
||||||
)}
|
|
||||||
exact
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{Object.values(settings).flatMap((section) =>
|
{pluginSettingsRoutes}
|
||||||
section.links.map(({ Component, to, exact }) => (
|
|
||||||
<Route
|
|
||||||
render={() => (
|
|
||||||
<React.Suspense fallback={<LoadingIndicatorPage />}>
|
|
||||||
<Component />
|
|
||||||
</React.Suspense>
|
|
||||||
)}
|
|
||||||
key={to}
|
|
||||||
path={to}
|
|
||||||
exact={exact || false}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,11 @@ import {
|
|||||||
HeaderLayout,
|
HeaderLayout,
|
||||||
Layout,
|
Layout,
|
||||||
Link,
|
Link,
|
||||||
|
Loader,
|
||||||
Main,
|
Main,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import {
|
import {
|
||||||
LoadingIndicatorPage,
|
|
||||||
prefixFileUrlWithBackendUrl,
|
prefixFileUrlWithBackendUrl,
|
||||||
SettingsPageTitle,
|
SettingsPageTitle,
|
||||||
useAPIErrorHandler,
|
useAPIErrorHandler,
|
||||||
@ -161,7 +161,12 @@ const ApplicationInfosPage = () => {
|
|||||||
<SettingsPageTitle name="Application" />
|
<SettingsPageTitle name="Application" />
|
||||||
<Main>
|
<Main>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoadingIndicatorPage data-testid="loader" />
|
<Loader>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.application.isLoading',
|
||||||
|
defaultMessage: 'Loading',
|
||||||
|
})}
|
||||||
|
</Loader>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<HeaderLayout
|
<HeaderLayout
|
||||||
|
|||||||
@ -113,9 +113,9 @@ describe('Application page', () => {
|
|||||||
afterAll(() => server.close());
|
afterAll(() => server.close());
|
||||||
|
|
||||||
it('should not display link upgrade version if not necessary', async () => {
|
it('should not display link upgrade version if not necessary', async () => {
|
||||||
const { queryByText, getByTestId } = setup();
|
const { queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
expect(queryByText('Upgrade your admin panel')).not.toBeInTheDocument();
|
expect(queryByText('Upgrade your admin panel')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -127,18 +127,18 @@ describe('Application page', () => {
|
|||||||
strapiVersion: '4.0.0',
|
strapiVersion: '4.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByText, getByTestId } = setup();
|
const { getByText, queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
expect(getByText('v4.0.0')).toBeInTheDocument();
|
expect(getByText('v4.0.0')).toBeInTheDocument();
|
||||||
expect(getByText('Upgrade your admin panel')).toBeInTheDocument();
|
expect(getByText('Upgrade your admin panel')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render logo input if read permissions', async () => {
|
it('should render logo input if read permissions', async () => {
|
||||||
const { queryByText, getByTestId } = setup();
|
const { queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
expect(queryByText('Menu logo')).toBeInTheDocument();
|
expect(queryByText('Menu logo')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -153,9 +153,9 @@ describe('Application page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render save button if update permissions', async () => {
|
it('should render save button if update permissions', async () => {
|
||||||
const { queryByText, getByTestId } = setup();
|
const { queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
expect(queryByText('Save')).toBeInTheDocument();
|
expect(queryByText('Save')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -163,9 +163,9 @@ describe('Application page', () => {
|
|||||||
it('should not render save button if no update permissions', async () => {
|
it('should not render save button if no update permissions', async () => {
|
||||||
useRBAC.mockReturnValue({ allowedActions: { canRead: true, canUpdate: false } });
|
useRBAC.mockReturnValue({ allowedActions: { canRead: true, canUpdate: false } });
|
||||||
|
|
||||||
const { queryByText, getByTestId } = setup();
|
const { queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
expect(queryByText('Save')).not.toBeInTheDocument();
|
expect(queryByText('Save')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -173,9 +173,9 @@ describe('Application page', () => {
|
|||||||
it('should update project settings on save', async () => {
|
it('should update project settings on save', async () => {
|
||||||
useRBAC.mockReturnValue({ allowedActions: { canRead: true, canUpdate: true } });
|
useRBAC.mockReturnValue({ allowedActions: { canRead: true, canUpdate: true } });
|
||||||
|
|
||||||
const { getByRole, getByTestId } = setup();
|
const { getByRole, queryByText } = setup();
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => getByTestId('loader'));
|
await waitForElementToBeRemoved(() => queryByText('Loading'));
|
||||||
|
|
||||||
fireEvent.click(getByRole('button', { name: 'Save' }));
|
fireEvent.click(getByRole('button', { name: 'Save' }));
|
||||||
|
|
||||||
|
|||||||
@ -256,9 +256,7 @@ describe('ADMIN | pages | SettingsPage', () => {
|
|||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
to: '/settings/internationalization',
|
to: '/settings/internationalization',
|
||||||
Component() {
|
Component: () => ({ default: () => <div>i18n settings</div> }),
|
||||||
return <div>i18n settings</div>;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -272,9 +270,7 @@ describe('ADMIN | pages | SettingsPage', () => {
|
|||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
to: '/settings/email-settings',
|
to: '/settings/email-settings',
|
||||||
Component() {
|
Component: () => ({ default: () => <div>email settings</div> }),
|
||||||
return <div>i18n settings</div>;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -296,9 +292,7 @@ describe('ADMIN | pages | SettingsPage', () => {
|
|||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
to: '/settings/internationalization',
|
to: '/settings/internationalization',
|
||||||
Component() {
|
Component: () => ({ default: () => <div>i18n settings</div> }),
|
||||||
return <div>i18n settings</div>;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -312,9 +306,7 @@ describe('ADMIN | pages | SettingsPage', () => {
|
|||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
to: '/settings/email-settings',
|
to: '/settings/email-settings',
|
||||||
Component() {
|
Component: () => ({ default: () => <div>email settings</div> }),
|
||||||
return <div>email settings</div>;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -135,7 +135,7 @@ describe('ADMIN | StrapiApp', () => {
|
|||||||
app.createSettingSection(section, links);
|
app.createSettingSection(section, links);
|
||||||
|
|
||||||
expect(app.settings.foo).toBeDefined();
|
expect(app.settings.foo).toBeDefined();
|
||||||
expect(app.settings.foo.links).toEqual(expect.any(Object));
|
expect(app.settings.foo.links).toEqual(links);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a link correctly to the global section', () => {
|
it('should add a link correctly to the global section', () => {
|
||||||
@ -150,7 +150,7 @@ describe('ADMIN | StrapiApp', () => {
|
|||||||
app.addSettingsLink('global', link);
|
app.addSettingsLink('global', link);
|
||||||
|
|
||||||
expect(app.settings.global.links).toHaveLength(1);
|
expect(app.settings.global.links).toHaveLength(1);
|
||||||
expect(app.settings.global.links[0]).toEqual(expect.any(Object));
|
expect(app.settings.global.links[0]).toEqual(link);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add an array of links correctly to the global section', () => {
|
it('should add an array of links correctly to the global section', () => {
|
||||||
@ -167,7 +167,7 @@ describe('ADMIN | StrapiApp', () => {
|
|||||||
app.addSettingsLinks('global', links);
|
app.addSettingsLinks('global', links);
|
||||||
|
|
||||||
expect(app.settings.global.links).toHaveLength(1);
|
expect(app.settings.global.links).toHaveLength(1);
|
||||||
expect(app.settings.global.links).toEqual(expect.any(Object));
|
expect(app.settings.global.links).toEqual(links);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -391,7 +391,7 @@ describe('ADMIN | StrapiApp', () => {
|
|||||||
app.addMenuLink(link);
|
app.addMenuLink(link);
|
||||||
|
|
||||||
expect(app.menu[0]).toBeDefined();
|
expect(app.menu[0]).toBeDefined();
|
||||||
expect(app.menu[0]).toEqual(expect.any(Object));
|
expect(app.menu[0]).toEqual(link);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addCorePluginMenuLink should add a link to the menu', () => {
|
it('addCorePluginMenuLink should add a link to the menu', () => {
|
||||||
|
|||||||
45
packages/core/admin/admin/src/utils/createRoute.js
Normal file
45
packages/core/admin/admin/src/utils/createRoute.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
const LazyCompo = ({ loadComponent }) => {
|
||||||
|
const [Compo, setCompo] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCompo = async () => {
|
||||||
|
try {
|
||||||
|
const loadedCompo = await loadComponent();
|
||||||
|
|
||||||
|
setCompo(() => loadedCompo.default);
|
||||||
|
} catch (err) {
|
||||||
|
// TODO return the error component
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadCompo();
|
||||||
|
}, [loadComponent]);
|
||||||
|
|
||||||
|
if (Compo) {
|
||||||
|
return <Compo />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LoadingIndicatorPage />;
|
||||||
|
};
|
||||||
|
|
||||||
|
LazyCompo.propTypes = {
|
||||||
|
loadComponent: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRoute = (Component, to, exact) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
render={() => <LazyCompo loadComponent={Component} />}
|
||||||
|
key={to}
|
||||||
|
path={to}
|
||||||
|
exact={exact || false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { createRoute } from '../createRoute';
|
||||||
|
|
||||||
|
describe('ADMIN | CONTAINER | SettingsPage | utils | createRoute', () => {
|
||||||
|
it('should return a <Route /> with the correctProps', () => {
|
||||||
|
const compo = () => 'test';
|
||||||
|
|
||||||
|
const {
|
||||||
|
props: { path, exact },
|
||||||
|
key,
|
||||||
|
} = createRoute(compo, '/test', true);
|
||||||
|
|
||||||
|
expect(key).toEqual('/test');
|
||||||
|
expect(exact).toBeTruthy();
|
||||||
|
expect(path).toEqual('/test');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,7 +4,7 @@ export const SETTINGS_ROUTES_EE = [
|
|||||||
...(window.strapi.features.isEnabled(window.strapi.features.AUDIT_LOGS)
|
...(window.strapi.features.isEnabled(window.strapi.features.AUDIT_LOGS)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "audit-logs-settings-page" */ './pages/AuditLogs/ProtectedListPage'
|
/* webpackChunkName: "audit-logs-settings-page" */ './pages/AuditLogs/ProtectedListPage'
|
||||||
)
|
)
|
||||||
@ -17,7 +17,7 @@ export const SETTINGS_ROUTES_EE = [
|
|||||||
...(window.strapi.features.isEnabled(window.strapi.features.REVIEW_WORKFLOWS)
|
...(window.strapi.features.isEnabled(window.strapi.features.REVIEW_WORKFLOWS)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "review-workflows-settings-list-view" */ './pages/ReviewWorkflows/pages/ListView'
|
/* webpackChunkName: "review-workflows-settings-list-view" */ './pages/ReviewWorkflows/pages/ListView'
|
||||||
)
|
)
|
||||||
@ -26,7 +26,7 @@ export const SETTINGS_ROUTES_EE = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "review-workflows-settings-create-view" */ './pages/ReviewWorkflows/pages/CreateView'
|
/* webpackChunkName: "review-workflows-settings-create-view" */ './pages/ReviewWorkflows/pages/CreateView'
|
||||||
)
|
)
|
||||||
@ -35,7 +35,7 @@ export const SETTINGS_ROUTES_EE = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "review-workflows-settings-edit-view" */ './pages/ReviewWorkflows/pages/EditView'
|
/* webpackChunkName: "review-workflows-settings-edit-view" */ './pages/ReviewWorkflows/pages/EditView'
|
||||||
)
|
)
|
||||||
@ -48,7 +48,7 @@ export const SETTINGS_ROUTES_EE = [
|
|||||||
...(window.strapi.features.isEnabled(window.strapi.features.SSO)
|
...(window.strapi.features.isEnabled(window.strapi.features.SSO)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
Component: React.lazy(() =>
|
component: React.lazy(() =>
|
||||||
import(/* webpackChunkName: "sso-settings-page" */ './pages/SingleSignOn')
|
import(/* webpackChunkName: "sso-settings-page" */ './pages/SingleSignOn')
|
||||||
),
|
),
|
||||||
path: '/settings/single-sign-on',
|
path: '/settings/single-sign-on',
|
||||||
|
|||||||
@ -22,7 +22,13 @@ export default {
|
|||||||
defaultMessage: 'Content Types Builder',
|
defaultMessage: 'Content Types Builder',
|
||||||
},
|
},
|
||||||
permissions: PERMISSIONS.main,
|
permissions: PERMISSIONS.main,
|
||||||
Component: () => import(/* webpackChunkName: "content-type-builder" */ './pages/App'),
|
async Component() {
|
||||||
|
const component = await import(
|
||||||
|
/* webpackChunkName: "content-type-builder" */ './pages/App'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.registerPlugin({
|
app.registerPlugin({
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
|
// NOTE TO PLUGINS DEVELOPERS:
|
||||||
|
// If you modify this file by adding new options to the plugin entry point
|
||||||
|
// Here's the file: strapi/docs/3.x/plugin-development/frontend-field-api.md
|
||||||
|
// Here's the file: strapi/docs/3.x/guides/registering-a-field-in-admin.md
|
||||||
|
// Also the strapi-generate-plugins/files/admin/src/index.js needs to be updated
|
||||||
|
// IF THE DOC IS NOT UPDATED THE PULL REQUEST WILL NOT BE MERGED
|
||||||
|
|
||||||
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
import { PERMISSIONS } from './constants';
|
import { PERMISSIONS } from './constants';
|
||||||
@ -18,12 +25,17 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
to: `/settings/email`,
|
to: `/settings/email`,
|
||||||
Component: () => import(/* webpackChunkName: "email-settings-page" */ './pages/Settings'),
|
async Component() {
|
||||||
|
const component = await import(
|
||||||
|
/* webpackChunkName: "email-settings-page" */ './pages/Settings'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.settings,
|
permissions: PERMISSIONS.settings,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
app.registerPlugin({
|
app.registerPlugin({
|
||||||
id: 'email',
|
id: 'email',
|
||||||
name: 'email',
|
name: 'email',
|
||||||
|
|||||||
@ -83,8 +83,7 @@ StrapiAppProvider.propTypes = {
|
|||||||
defaultMessage: PropTypes.string.isRequired,
|
defaultMessage: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
permissions: PropTypes.array,
|
permissions: PropTypes.array,
|
||||||
// React.lazy loadable
|
Component: PropTypes.func,
|
||||||
Component: PropTypes.object,
|
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
plugins: PropTypes.object.isRequired,
|
plugins: PropTypes.object.isRequired,
|
||||||
|
|||||||
@ -108,4 +108,3 @@ export { setHexOpacity } from './utils/setHexOpacity';
|
|||||||
export * from './utils/stopPropagation';
|
export * from './utils/stopPropagation';
|
||||||
export { translatedErrors } from './utils/translatedErrors';
|
export { translatedErrors } from './utils/translatedErrors';
|
||||||
export { wrapAxiosInstance } from './utils/wrapAxiosInstance';
|
export { wrapAxiosInstance } from './utils/wrapAxiosInstance';
|
||||||
export * from './utils/once';
|
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
// NOTE TO PLUGINS DEVELOPERS:
|
||||||
|
// If you modify this file by adding new options to the plugin entry point
|
||||||
|
// Here's the file: strapi/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md
|
||||||
|
// Here's the file: strapi/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md
|
||||||
|
// Also the strapi-generate-plugins/files/admin/src/index.js needs to be updated
|
||||||
|
// IF THE DOC IS NOT UPDATED THE PULL REQUEST WILL NOT BE MERGED
|
||||||
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
import pluginPkg from '../../package.json';
|
import pluginPkg from '../../package.json';
|
||||||
@ -21,7 +27,11 @@ export default {
|
|||||||
defaultMessage: 'Media Library',
|
defaultMessage: 'Media Library',
|
||||||
},
|
},
|
||||||
permissions: PERMISSIONS.main,
|
permissions: PERMISSIONS.main,
|
||||||
Component: () => import(/* webpackChunkName: "upload" */ './pages/App'),
|
async Component() {
|
||||||
|
const component = await import(/* webpackChunkName: "upload" */ './pages/App');
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.addFields({ type: 'media', Component: MediaLibraryInput });
|
app.addFields({ type: 'media', Component: MediaLibraryInput });
|
||||||
@ -40,7 +50,13 @@ export default {
|
|||||||
defaultMessage: 'Media Library',
|
defaultMessage: 'Media Library',
|
||||||
},
|
},
|
||||||
to: '/settings/media-library',
|
to: '/settings/media-library',
|
||||||
Component: () => import(/* webpackChunkName: "upload-settings" */ './pages/SettingsPage'),
|
async Component() {
|
||||||
|
const component = await import(
|
||||||
|
/* webpackChunkName: "upload-settings" */ './pages/SettingsPage'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.settings,
|
permissions: PERMISSIONS.settings,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,7 +24,13 @@ export default {
|
|||||||
defaultMessage: 'Documentation',
|
defaultMessage: 'Documentation',
|
||||||
},
|
},
|
||||||
permissions: PERMISSIONS.main,
|
permissions: PERMISSIONS.main,
|
||||||
Component: () => import(/* webpackChunkName: "documentation-page" */ './pages/PluginPage'),
|
async Component() {
|
||||||
|
const component = await import(
|
||||||
|
/* webpackChunkName: "documentation-page" */ './pages/PluginPage'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.registerPlugin({
|
app.registerPlugin({
|
||||||
@ -40,8 +46,13 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'documentation',
|
id: 'documentation',
|
||||||
to: `/settings/${pluginId}`,
|
to: `/settings/${pluginId}`,
|
||||||
Component: () =>
|
async Component() {
|
||||||
import(/* webpackChunkName: "documentation-settings" */ './pages/SettingsPage'),
|
const component = await import(
|
||||||
|
/* webpackChunkName: "documentation-settings" */ './pages/SettingsPage'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.main,
|
permissions: PERMISSIONS.main,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -61,7 +61,13 @@ export default {
|
|||||||
id: 'internationalization',
|
id: 'internationalization',
|
||||||
to: '/settings/internationalization',
|
to: '/settings/internationalization',
|
||||||
|
|
||||||
Component: () => import(/* webpackChunkName: "i18n-settings-page" */ './pages/SettingsPage'),
|
async Component() {
|
||||||
|
const component = await import(
|
||||||
|
/* webpackChunkName: "i18n-settings-page" */ './pages/SettingsPage'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.accessMain,
|
permissions: PERMISSIONS.accessMain,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -32,8 +32,13 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'roles',
|
id: 'roles',
|
||||||
to: `/settings/users-permissions/roles`,
|
to: `/settings/users-permissions/roles`,
|
||||||
Component: () =>
|
async Component() {
|
||||||
import(/* webpackChunkName: "users-roles-settings-page" */ './pages/Roles'),
|
const component = await import(
|
||||||
|
/* webpackChunkName: "users-roles-settings-page" */ './pages/Roles'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.accessRoles,
|
permissions: PERMISSIONS.accessRoles,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -43,8 +48,13 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'providers',
|
id: 'providers',
|
||||||
to: `/settings/users-permissions/providers`,
|
to: `/settings/users-permissions/providers`,
|
||||||
Component: () =>
|
async Component() {
|
||||||
import(/* webpackChunkName: "users-providers-settings-page" */ './pages/Providers'),
|
const component = await import(
|
||||||
|
/* webpackChunkName: "users-providers-settings-page" */ './pages/Providers'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.readProviders,
|
permissions: PERMISSIONS.readProviders,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,8 +64,13 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'email-templates',
|
id: 'email-templates',
|
||||||
to: `/settings/users-permissions/email-templates`,
|
to: `/settings/users-permissions/email-templates`,
|
||||||
Component: () =>
|
async Component() {
|
||||||
import(/* webpackChunkName: "users-email-settings-page" */ './pages/EmailTemplates'),
|
const component = await import(
|
||||||
|
/* webpackChunkName: "users-email-settings-page" */ './pages/EmailTemplates'
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.readEmailTemplates,
|
permissions: PERMISSIONS.readEmailTemplates,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -65,10 +80,13 @@ export default {
|
|||||||
},
|
},
|
||||||
id: 'advanced-settings',
|
id: 'advanced-settings',
|
||||||
to: `/settings/users-permissions/advanced-settings`,
|
to: `/settings/users-permissions/advanced-settings`,
|
||||||
Component: () =>
|
async Component() {
|
||||||
import(
|
const component = await import(
|
||||||
/* webpackChunkName: "users-advanced-settings-page" */ './pages/AdvancedSettings'
|
/* webpackChunkName: "users-advanced-settings-page" */ './pages/AdvancedSettings'
|
||||||
),
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
permissions: PERMISSIONS.readAdvancedSettings,
|
permissions: PERMISSIONS.readAdvancedSettings,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user