@strapi/admin 4.12.6 → 4.13.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/StrapiApp.js +1 -1
- package/admin/src/components/AuthenticatedApp/index.js +118 -0
- package/admin/src/components/AuthenticatedApp/utils/api.js +85 -0
- package/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.js +11 -0
- package/admin/src/components/GuidedTour/Modal/index.js +3 -1
- package/admin/src/components/NpsSurvey/hooks/useNpsSurveySettings.js +17 -0
- package/admin/src/components/NpsSurvey/index.js +365 -0
- package/admin/src/components/PluginsInitializer/index.js +68 -0
- package/admin/src/components/PluginsInitializer/init.js +11 -0
- package/admin/src/components/PluginsInitializer/reducer.js +22 -0
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
- package/admin/src/content-manager/components/DynamicZone/components/DynamicZoneLabel.js +1 -1
- package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +8 -1
- package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
- package/admin/src/content-manager/components/Filter/Filter.js +54 -0
- package/admin/src/content-manager/components/Filter/index.js +1 -0
- package/admin/src/content-manager/components/InputUID/index.js +222 -216
- package/admin/src/content-manager/components/RelationInput/RelationInput.js +4 -0
- package/admin/src/content-manager/components/RepeatableComponent/index.js +32 -2
- package/admin/src/content-manager/components/Wysiwyg/Editor.js +432 -68
- package/admin/src/content-manager/components/Wysiwyg/WysiwygStyles.js +0 -7
- package/admin/src/content-manager/components/Wysiwyg/index.js +147 -152
- package/admin/src/content-manager/hooks/useAllowedAttributes.js +47 -0
- package/admin/src/content-manager/pages/App/index.js +16 -5
- package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
- package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
- package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
- package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/index.js +2 -2
- package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
- package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +1 -1
- package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +80 -0
- package/admin/src/content-manager/pages/ListView/index.js +236 -67
- package/admin/src/content-manager/utils/getDisplayName.js +33 -0
- package/admin/src/content-manager/utils/index.js +1 -0
- package/admin/src/hooks/useSettingsForm/index.js +3 -14
- package/admin/src/hooks/useSettingsMenu/index.js +2 -2
- package/admin/src/hooks/useSettingsMenu/utils/formatLinks.js +3 -1
- package/admin/src/hooks/useSettingsMenu/utils/sortLinks.js +3 -1
- package/admin/src/index.js +1 -1
- package/admin/src/layouts/AppLayout/index.js +33 -0
- package/admin/src/pages/Admin/Onboarding/index.js +3 -1
- package/admin/src/pages/Admin/index.js +73 -77
- package/admin/src/pages/App/constants.js +1 -1
- package/admin/src/pages/App/index.js +122 -160
- package/admin/src/pages/AuthPage/components/Register/index.js +5 -0
- package/admin/src/pages/AuthPage/index.js +4 -2
- package/admin/src/pages/HomePage/index.js +3 -1
- package/admin/src/pages/InstalledPluginsPage/index.js +3 -1
- package/admin/src/pages/{InternalErrorPage.js → InternalErrorPage/index.js} +4 -3
- package/admin/src/pages/{NotFoundPage.js → NotFoundPage/index.js} +3 -1
- package/admin/src/pages/ProfilePage/index.js +4 -2
- package/admin/src/pages/SettingsPage/constants.js +132 -67
- package/admin/src/pages/SettingsPage/index.js +18 -23
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +1 -1
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +1 -1
- package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +1 -2
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/DynamicTable/TableRows/index.js +1 -1
- package/admin/src/pages/UseCasePage/index.js +175 -0
- package/admin/src/plugins.js +7 -8
- package/admin/src/translations/en.json +10 -1
- package/admin/src/utils/checkFormValidity.js +15 -0
- package/admin/src/utils/createRoute.js +7 -5
- package/admin/src/utils/formatAPIErrors.js +3 -1
- package/admin/src/utils/getAttributesToDisplay.js +19 -0
- package/admin/src/utils/getExistingActions.js +32 -0
- package/admin/src/utils/getFullName.js +1 -1
- package/admin/src/utils/index.js +9 -0
- package/admin/src/utils/makeUniqueRoutes.js +6 -0
- package/admin/src/utils/sortLinks.js +3 -1
- package/admin/src/utils/uniqueAdminHash.js +9 -2
- package/build/{1049.9d69d231.chunk.js → 1049.ec69f5e0.chunk.js} +1 -1
- package/build/1227.9f37e1dc.chunk.js +1 -0
- package/build/1386.ea73b677.chunk.js +7 -0
- package/build/{2225.33287e1b.chunk.js → 2225.649fb7bc.chunk.js} +2 -2
- package/build/{2237.03792b63.chunk.js → 2237.b832ae6e.chunk.js} +4 -4
- package/build/{2379.401f56f3.chunk.js → 2379.1f98a31a.chunk.js} +1 -1
- package/build/{2395.e6a79fbb.chunk.js → 2395.0e5e8ded.chunk.js} +1 -1
- package/build/{2801.31393ffe.chunk.js → 2801.8e1aa82a.chunk.js} +1 -1
- package/build/3483.19381b40.chunk.js +1 -0
- package/build/3739.63e352f1.chunk.js +103 -0
- package/build/4174.f1f39e40.chunk.js +1 -0
- package/build/448.829e1344.chunk.js +1 -0
- package/build/4546.a5946d22.chunk.js +1 -0
- package/build/4724.aea5c8c1.chunk.js +6 -0
- package/build/{502.8dd074ff.chunk.js → 502.7bba43b1.chunk.js} +1 -1
- package/build/6158.c3c13c20.chunk.js +1 -0
- package/build/6691.4985ef22.chunk.js +105 -0
- package/build/{7464.592a9295.chunk.js → 7464.eb057bec.chunk.js} +1 -1
- package/build/78.dcc6df5c.chunk.js +1 -0
- package/build/{8276.e519a707.chunk.js → 8276.be3ed581.chunk.js} +2 -2
- package/build/{2747.d1442a90.chunk.js → 9806.5d5a0e8d.chunk.js} +64 -72
- package/build/9944.7af075a5.chunk.js +26 -0
- package/build/Admin-authenticatedApp.43b6ec9a.chunk.js +79 -0
- package/build/Admin_InternalErrorPage.38155af3.chunk.js +1 -0
- package/build/Admin_homePage.6f128523.chunk.js +81 -0
- package/build/Admin_marketplace.061a6e5a.chunk.js +55 -0
- package/build/Admin_pluginsPage.16f837b8.chunk.js +6 -0
- package/build/Admin_profilePage.678bce24.chunk.js +13 -0
- package/build/Admin_settingsPage.af7309e4.chunk.js +111 -0
- package/build/{Upload_ConfigureTheView.345ac1e0.chunk.js → Upload_ConfigureTheView.3fc1c100.chunk.js} +1 -1
- package/build/admin-app.d63bd229.chunk.js +36 -0
- package/build/{admin-edit-roles-page.24bdf746.chunk.js → admin-edit-roles-page.38a6c863.chunk.js} +3 -3
- package/build/admin-edit-users.545fc882.chunk.js +10 -0
- package/build/{admin-roles-list.23ddff26.chunk.js → admin-roles-list.1e2e814d.chunk.js} +1 -1
- package/build/admin-users.b8ea5677.chunk.js +11 -0
- package/build/{api-tokens-create-page.46c2ea84.chunk.js → api-tokens-create-page.e0c15627.chunk.js} +1 -1
- package/build/{api-tokens-edit-page.58139df9.chunk.js → api-tokens-edit-page.9f2dce47.chunk.js} +1 -1
- package/build/{api-tokens-list-page.0af7d431.chunk.js → api-tokens-list-page.d747051c.chunk.js} +2 -2
- package/build/{audit-logs-settings-page.0f73ccf8.chunk.js → audit-logs-settings-page.96f9d608.chunk.js} +1 -1
- package/build/content-manager.ccff1078.chunk.js +1097 -0
- package/build/{content-type-builder-list-view.bf9be456.chunk.js → content-type-builder-list-view.b71cf240.chunk.js} +1 -1
- package/build/{content-type-builder.66066281.chunk.js → content-type-builder.e5669749.chunk.js} +18 -18
- package/build/{email-settings-page.2f7e35c0.chunk.js → email-settings-page.2809f0bf.chunk.js} +1 -1
- package/build/en-json.e12fd5fc.chunk.js +1 -0
- package/build/{i18n-settings-page.47f78016.chunk.js → i18n-settings-page.5f716172.chunk.js} +1 -1
- package/build/index.html +1 -1
- package/build/main.c6c9e04c.js +2859 -0
- package/build/{review-workflows-settings-create-view.d24a32b9.chunk.js → review-workflows-settings-create-view.4a156a19.chunk.js} +1 -1
- package/build/{review-workflows-settings-edit-view.6044b022.chunk.js → review-workflows-settings-edit-view.ce984d1f.chunk.js} +1 -1
- package/build/{review-workflows-settings-list-view.3f0ef4bc.chunk.js → review-workflows-settings-list-view.419b8deb.chunk.js} +2 -2
- package/build/runtime~main.dcf1cb45.js +2 -0
- package/build/{sso-settings-page.4dba0670.chunk.js → sso-settings-page.45153df5.chunk.js} +1 -1
- package/build/{transfer-tokens-create-page.1597e6ab.chunk.js → transfer-tokens-create-page.ebba16d8.chunk.js} +1 -1
- package/build/{transfer-tokens-edit-page.8741529f.chunk.js → transfer-tokens-edit-page.d7bb2b3e.chunk.js} +1 -1
- package/build/{transfer-tokens-list-page.d6986b03.chunk.js → transfer-tokens-list-page.cfe1736c.chunk.js} +2 -2
- package/build/{upload-settings.7f93d4c0.chunk.js → upload-settings.cc5ad813.chunk.js} +1 -1
- package/build/{upload.37488080.chunk.js → upload.756efc28.chunk.js} +1 -1
- package/build/{users-advanced-settings-page.17052d72.chunk.js → users-advanced-settings-page.818d84eb.chunk.js} +1 -1
- package/build/{users-email-settings-page.3de8ea50.chunk.js → users-email-settings-page.c1967c09.chunk.js} +1 -1
- package/build/{users-providers-settings-page.0eaa916d.chunk.js → users-providers-settings-page.11893e08.chunk.js} +1 -1
- package/build/{users-roles-settings-page.957ad48b.chunk.js → users-roles-settings-page.2b051e6a.chunk.js} +1 -1
- package/build/webhook-edit-page.de45c635.chunk.js +33 -0
- package/build/{webhook-list-page.65e1b5bb.chunk.js → webhook-list-page.ca91df8b.chunk.js} +1 -1
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
- package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +147 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +243 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
- package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +21 -0
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
- package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
- package/ee/admin/pages/App/constants.js +5 -6
- package/ee/admin/pages/SettingsPage/constants.js +42 -27
- package/ee/server/constants/workflows.js +1 -0
- package/ee/server/controllers/index.js +1 -0
- package/ee/server/controllers/workflows/assignees/index.js +44 -0
- package/ee/server/routes/review-workflows.js +17 -0
- package/ee/server/services/index.js +1 -0
- package/ee/server/services/review-workflows/assignees.js +54 -0
- package/ee/server/services/review-workflows/metrics/index.js +5 -0
- package/ee/server/services/review-workflows/review-workflows.js +20 -11
- package/ee/server/validation/review-workflows.js +8 -0
- package/index.js +2 -6
- package/package.json +9 -9
- package/scripts/build.js +15 -15
- package/scripts/create-dev-plugins-file.js +5 -38
- package/server/controllers/role.js +2 -0
- package/server/controllers/user.js +2 -0
- package/server/services/permission/permissions-manager/index.js +3 -1
- package/server/services/permission/permissions-manager/sanitize.js +19 -7
- package/server/services/permission/permissions-manager/validate.js +218 -0
- package/utils/create-cache-dir.js +62 -16
- package/utils/create-plugins-exclude-path.js +3 -23
- package/utils/get-plugins.js +110 -0
- package/utils/index.js +1 -1
- package/webpack.config.js +10 -13
- package/admin/src/components/AuthenticatedApp.js +0 -229
- package/admin/src/content-manager/components/AttributeFilter/Filters.js +0 -58
- package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
- package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
- package/admin/src/content-manager/components/Wysiwyg/EditorStylesContainer.js +0 -344
- package/admin/src/pages/UseCasePage.js +0 -174
- package/build/2166.c837469a.chunk.js +0 -1
- package/build/3483.8517171f.chunk.js +0 -1
- package/build/3984.dda474f7.chunk.js +0 -1
- package/build/4546.7a3c0d03.chunk.js +0 -1
- package/build/5483.5bfbb00d.chunk.js +0 -6
- package/build/6158.c974fd83.chunk.js +0 -1
- package/build/748.fd2e5afd.chunk.js +0 -105
- package/build/773.6381d62d.chunk.js +0 -18
- package/build/7826.399afe81.chunk.js +0 -103
- package/build/8261.2525d35c.chunk.js +0 -7
- package/build/8299.62b67c72.chunk.js +0 -1
- package/build/Admin-AuthPage.90d64342.chunk.js +0 -35
- package/build/Admin-AuthenticatedApp.379ac945.chunk.js +0 -24
- package/build/Admin-UseCasePage.1f757db5.chunk.js +0 -13
- package/build/Admin_GuidedTourModal.8ccf1fbc.chunk.js +0 -12
- package/build/Admin_InternalErrorPage.9de92c6d.chunk.js +0 -9
- package/build/Admin_NotFoundPage.21620424.chunk.js +0 -9
- package/build/Admin_Onboarding.dbfa32f6.chunk.js +0 -43
- package/build/Admin_homePage.2000cbe9.chunk.js +0 -86
- package/build/Admin_marketplace.ec80e29b.chunk.js +0 -63
- package/build/Admin_pluginsPage.0c6851f8.chunk.js +0 -14
- package/build/Admin_profilePage.78cd8495.chunk.js +0 -21
- package/build/Admin_settingsPage.1760c3ce.chunk.js +0 -119
- package/build/StrapiApp.221fac30.chunk.js +0 -5
- package/build/admin-edit-users.5d10d444.chunk.js +0 -10
- package/build/admin-users.2b3e4305.chunk.js +0 -11
- package/build/content-manager.fb0833bd.chunk.js +0 -1099
- package/build/en-json.08c05fcf.chunk.js +0 -1
- package/build/main.ee3c1938.js +0 -2859
- package/build/runtime~main.397ee447.js +0 -2
- package/build/webhook-edit-page.665210af.chunk.js +0 -33
- package/scripts/create-plugins-file.js +0 -92
- package/utils/get-plugins-path.js +0 -41
- /package/server/services/permission/permissions-manager/{query-builers.js → query-builders.js} +0 -0
package/admin/src/StrapiApp.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from './exposedHooks';
|
|
23
23
|
import favicon from './favicon.png';
|
|
24
24
|
import injectionZones from './injectionZones';
|
|
25
|
-
import
|
|
25
|
+
import App from './pages/App';
|
|
26
26
|
import languageNativeNames from './translations/languageNativeNames';
|
|
27
27
|
|
|
28
28
|
class StrapiApp {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AppInfoProvider,
|
|
5
|
+
auth,
|
|
6
|
+
LoadingIndicatorPage,
|
|
7
|
+
useGuidedTour,
|
|
8
|
+
useNotification,
|
|
9
|
+
} from '@strapi/helper-plugin';
|
|
10
|
+
import get from 'lodash/get';
|
|
11
|
+
import { useQueries } from 'react-query';
|
|
12
|
+
// TODO: DS add loader
|
|
13
|
+
|
|
14
|
+
import packageJSON from '../../../../package.json';
|
|
15
|
+
import { useConfigurations } from '../../hooks';
|
|
16
|
+
import { getFullName, hashAdminUserEmail } from '../../utils';
|
|
17
|
+
import PluginsInitializer from '../PluginsInitializer';
|
|
18
|
+
import RBACProvider from '../RBACProvider';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
fetchAppInfo,
|
|
22
|
+
fetchCurrentUserPermissions,
|
|
23
|
+
fetchStrapiLatestRelease,
|
|
24
|
+
fetchUserRoles,
|
|
25
|
+
} from './utils/api';
|
|
26
|
+
import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
|
|
27
|
+
|
|
28
|
+
const strapiVersion = packageJSON.version;
|
|
29
|
+
|
|
30
|
+
const AuthenticatedApp = () => {
|
|
31
|
+
const { setGuidedTourVisibility } = useGuidedTour();
|
|
32
|
+
const toggleNotification = useNotification();
|
|
33
|
+
const userInfo = auth.getUserInfo();
|
|
34
|
+
const userName = get(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname);
|
|
35
|
+
const [userDisplayName, setUserDisplayName] = useState(userName);
|
|
36
|
+
const [userId, setUserId] = useState(null);
|
|
37
|
+
const { showReleaseNotification } = useConfigurations();
|
|
38
|
+
const [
|
|
39
|
+
{ data: appInfos, status },
|
|
40
|
+
{ data: tagName, isLoading },
|
|
41
|
+
{ data: permissions, status: fetchPermissionsStatus, refetch, isFetching },
|
|
42
|
+
{ data: userRoles },
|
|
43
|
+
] = useQueries([
|
|
44
|
+
{ queryKey: 'app-infos', queryFn: fetchAppInfo },
|
|
45
|
+
{
|
|
46
|
+
queryKey: 'strapi-release',
|
|
47
|
+
queryFn: () => fetchStrapiLatestRelease(toggleNotification),
|
|
48
|
+
enabled: showReleaseNotification,
|
|
49
|
+
initialData: strapiVersion,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
queryKey: 'admin-users-permission',
|
|
53
|
+
queryFn: fetchCurrentUserPermissions,
|
|
54
|
+
initialData: [],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
queryKey: 'user-roles',
|
|
58
|
+
queryFn: fetchUserRoles,
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tagName);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* TODO: does this actually need to be an effect?
|
|
66
|
+
*/
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (userRoles) {
|
|
69
|
+
const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
|
|
70
|
+
|
|
71
|
+
if (isUserSuperAdmin && appInfos?.autoReload) {
|
|
72
|
+
setGuidedTourVisibility(true);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}, [userRoles, appInfos, setGuidedTourVisibility]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const getUserId = async () => {
|
|
79
|
+
const userId = await hashAdminUserEmail(userInfo);
|
|
80
|
+
setUserId(userId);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
getUserId();
|
|
84
|
+
}, [userInfo]);
|
|
85
|
+
|
|
86
|
+
// We don't need to wait for the release query to be fetched before rendering the plugins
|
|
87
|
+
// however, we need the appInfos and the permissions
|
|
88
|
+
const shouldShowNotDependentQueriesLoader =
|
|
89
|
+
isFetching || status === 'loading' || fetchPermissionsStatus === 'loading';
|
|
90
|
+
|
|
91
|
+
const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader;
|
|
92
|
+
|
|
93
|
+
if (shouldShowLoader) {
|
|
94
|
+
return <LoadingIndicatorPage />;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// TODO: add error state
|
|
98
|
+
if (status === 'error') {
|
|
99
|
+
return <div>error...</div>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<AppInfoProvider
|
|
104
|
+
{...appInfos}
|
|
105
|
+
userId={userId}
|
|
106
|
+
latestStrapiReleaseTag={tagName}
|
|
107
|
+
setUserDisplayName={setUserDisplayName}
|
|
108
|
+
shouldUpdateStrapi={shouldUpdateStrapi}
|
|
109
|
+
userDisplayName={userDisplayName}
|
|
110
|
+
>
|
|
111
|
+
<RBACProvider permissions={permissions} refetchPermissions={refetch}>
|
|
112
|
+
<PluginsInitializer />
|
|
113
|
+
</RBACProvider>
|
|
114
|
+
</AppInfoProvider>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default AuthenticatedApp;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getFetchClient } from '@strapi/helper-plugin';
|
|
2
|
+
|
|
3
|
+
import packageJSON from '../../../../../package.json';
|
|
4
|
+
|
|
5
|
+
import checkLatestStrapiVersion from './checkLatestStrapiVersion';
|
|
6
|
+
|
|
7
|
+
const strapiVersion = packageJSON.version;
|
|
8
|
+
const showUpdateNotif = !JSON.parse(localStorage.getItem('STRAPI_UPDATE_NOTIF'));
|
|
9
|
+
const { get } = getFetchClient();
|
|
10
|
+
|
|
11
|
+
const fetchStrapiLatestRelease = async (toggleNotification) => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest');
|
|
14
|
+
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error('Failed to fetch latest Strapi version.');
|
|
17
|
+
}
|
|
18
|
+
const { tag_name } = await res.json();
|
|
19
|
+
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tag_name);
|
|
20
|
+
|
|
21
|
+
if (shouldUpdateStrapi && showUpdateNotif) {
|
|
22
|
+
toggleNotification({
|
|
23
|
+
type: 'info',
|
|
24
|
+
message: { id: 'notification.version.update.message' },
|
|
25
|
+
link: {
|
|
26
|
+
url: `https://github.com/strapi/strapi/releases/tag/${tag_name}`,
|
|
27
|
+
label: {
|
|
28
|
+
id: 'global.see-more',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
blockTransition: true,
|
|
32
|
+
onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return tag_name;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Don't throw an error
|
|
39
|
+
return strapiVersion;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const fetchAppInfo = async () => {
|
|
44
|
+
try {
|
|
45
|
+
const { data, headers } = await get('/admin/information');
|
|
46
|
+
|
|
47
|
+
if (!headers['content-type'].includes('application/json')) {
|
|
48
|
+
throw new Error('Not found');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return data.data;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(error);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const fetchCurrentUserPermissions = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const { data, headers } = await get('/admin/users/me/permissions');
|
|
60
|
+
|
|
61
|
+
if (!headers['content-type'].includes('application/json')) {
|
|
62
|
+
throw new Error('Not found');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return data.data;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
throw new Error(err);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const fetchUserRoles = async () => {
|
|
72
|
+
try {
|
|
73
|
+
const {
|
|
74
|
+
data: {
|
|
75
|
+
data: { roles },
|
|
76
|
+
},
|
|
77
|
+
} = await get('/admin/users/me');
|
|
78
|
+
|
|
79
|
+
return roles;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw new Error(err);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { fetchAppInfo, fetchCurrentUserPermissions, fetchStrapiLatestRelease, fetchUserRoles };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
|
|
3
|
+
const checkLatestStrapiVersion = (currentPackageVersion, latestPublishedVersion) => {
|
|
4
|
+
if (!semver.valid(currentPackageVersion) || !semver.valid(latestPublishedVersion)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return semver.lt(currentPackageVersion, latestPublishedVersion);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default checkLatestStrapiVersion;
|
|
@@ -9,7 +9,7 @@ import Modal from './components/Modal';
|
|
|
9
9
|
import StepperModal from './components/Stepper';
|
|
10
10
|
import reducer, { initialState } from './reducer';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const GuidedTourModal = () => {
|
|
13
13
|
const {
|
|
14
14
|
currentStep,
|
|
15
15
|
guidedTourState,
|
|
@@ -90,3 +90,5 @@ export const GuidedTourModal = () => {
|
|
|
90
90
|
|
|
91
91
|
return null;
|
|
92
92
|
};
|
|
93
|
+
|
|
94
|
+
export default GuidedTourModal;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { usePersistentState } from '@strapi/helper-plugin';
|
|
2
|
+
|
|
3
|
+
// Exported to make it available during admin user registration.
|
|
4
|
+
// Because we only enable the NPS for users who subscribe to the newsletter when signing up
|
|
5
|
+
export function useNpsSurveySettings() {
|
|
6
|
+
const [npsSurveySettings, setNpsSurveySettings] = usePersistentState(
|
|
7
|
+
'STRAPI_NPS_SURVEY_SETTINGS',
|
|
8
|
+
{
|
|
9
|
+
enabled: true,
|
|
10
|
+
lastResponseDate: null,
|
|
11
|
+
firstDismissalDate: null,
|
|
12
|
+
lastDismissalDate: null,
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
return { npsSurveySettings, setNpsSurveySettings };
|
|
17
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
Flex,
|
|
6
|
+
IconButton,
|
|
7
|
+
Button,
|
|
8
|
+
Typography,
|
|
9
|
+
Textarea,
|
|
10
|
+
Portal,
|
|
11
|
+
Field,
|
|
12
|
+
FieldLabel,
|
|
13
|
+
FieldInput,
|
|
14
|
+
VisuallyHidden,
|
|
15
|
+
} from '@strapi/design-system';
|
|
16
|
+
import { auth, useNotification, useAppInfo } from '@strapi/helper-plugin';
|
|
17
|
+
import { Cross } from '@strapi/icons';
|
|
18
|
+
import { Formik, Form } from 'formik';
|
|
19
|
+
import { useIntl } from 'react-intl';
|
|
20
|
+
import { useMutation } from 'react-query';
|
|
21
|
+
import styled, { useTheme } from 'styled-components';
|
|
22
|
+
import * as yup from 'yup';
|
|
23
|
+
|
|
24
|
+
import { useNpsSurveySettings } from './hooks/useNpsSurveySettings';
|
|
25
|
+
|
|
26
|
+
const FieldWrapper = styled(Field)`
|
|
27
|
+
height: ${32 / 16}rem;
|
|
28
|
+
width: ${32 / 16}rem;
|
|
29
|
+
|
|
30
|
+
> label,
|
|
31
|
+
~ input {
|
|
32
|
+
display: block;
|
|
33
|
+
position: absolute;
|
|
34
|
+
top: 0;
|
|
35
|
+
left: 0;
|
|
36
|
+
right: 0;
|
|
37
|
+
bottom: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
> label {
|
|
41
|
+
color: inherit;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
padding: ${({ theme }) => theme.spaces[2]};
|
|
44
|
+
text-align: center;
|
|
45
|
+
vertical-align: middle;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&:hover,
|
|
49
|
+
&:focus-within {
|
|
50
|
+
background-color: ${({ theme }) => theme.colors.neutral0};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&:active,
|
|
54
|
+
&.selected {
|
|
55
|
+
color: ${({ theme }) => theme.colors.primary700};
|
|
56
|
+
background-color: ${({ theme }) => theme.colors.neutral0};
|
|
57
|
+
border-color: ${({ theme }) => theme.colors.primary700};
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const delays = {
|
|
62
|
+
postResponse: 90 * 24 * 60 * 60 * 1000, // 90 days in ms
|
|
63
|
+
postFirstDismissal: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
|
|
64
|
+
postSubsequentDismissal: 90 * 24 * 60 * 60 * 1000, // 90 days in ms
|
|
65
|
+
display: 5 * 60 * 1000, // 5 minutes in ms
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const ratingArray = [...Array(11).keys()];
|
|
69
|
+
|
|
70
|
+
const checkIfShouldShowSurvey = (settings) => {
|
|
71
|
+
const { enabled, lastResponseDate, firstDismissalDate, lastDismissalDate } = settings;
|
|
72
|
+
|
|
73
|
+
// This function goes through all the cases where we'd want to not show the survey:
|
|
74
|
+
// 1. If the survey is disabled, abort mission, don't bother checking the other settings.
|
|
75
|
+
// 2. If the user has already responded to the survey, check if enough time has passed since the last response.
|
|
76
|
+
// 3. If the user has dismissed the survey twice or more before, check if enough time has passed since the last dismissal.
|
|
77
|
+
// 4. If the user has only dismissed the survey once before, check if enough time has passed since the first dismissal.
|
|
78
|
+
// If none of these cases check out, then we show the survey.
|
|
79
|
+
// Note that submitting a response resets the dismissal counts.
|
|
80
|
+
// Checks 3 and 4 should not be reversed, since the first dismissal will also exist if the user has dismissed the survey twice or more before.
|
|
81
|
+
|
|
82
|
+
// User hasn't enabled NPS feature
|
|
83
|
+
if (!enabled) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The user has already responded to the survey
|
|
88
|
+
if (lastResponseDate) {
|
|
89
|
+
const timeSinceLastResponse = Date.now() - new Date(lastResponseDate).getTime();
|
|
90
|
+
|
|
91
|
+
if (timeSinceLastResponse >= delays.postResponse) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The user has dismissed the survey twice or more before
|
|
99
|
+
if (lastDismissalDate) {
|
|
100
|
+
const timeSinceLastDismissal = Date.now() - new Date(lastDismissalDate).getTime();
|
|
101
|
+
|
|
102
|
+
if (timeSinceLastDismissal >= delays.postSubsequentDismissal) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The user has only dismissed the survey once before
|
|
110
|
+
if (firstDismissalDate) {
|
|
111
|
+
const timeSinceFirstDismissal = Date.now() - new Date(firstDismissalDate).getTime();
|
|
112
|
+
|
|
113
|
+
if (timeSinceFirstDismissal >= delays.postFirstDismissal) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The user has not interacted with the survey before
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const NpsSurvey = () => {
|
|
125
|
+
const theme = useTheme();
|
|
126
|
+
const { formatMessage } = useIntl();
|
|
127
|
+
const { npsSurveySettings, setNpsSurveySettings } = useNpsSurveySettings();
|
|
128
|
+
const [isFeedbackResponse, setIsFeedbackResponse] = React.useState(false);
|
|
129
|
+
const toggleNotification = useNotification();
|
|
130
|
+
const { currentEnvironment, strapiVersion } = useAppInfo();
|
|
131
|
+
|
|
132
|
+
const { mutate, isLoading } = useMutation(
|
|
133
|
+
async (form) => {
|
|
134
|
+
const res = await fetch('https://analytics.strapi.io/submit-nps', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify(form),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
throw new Error('Failed to submit NPS survey');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return res;
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
onSuccess() {
|
|
150
|
+
setNpsSurveySettings((settings) => ({
|
|
151
|
+
...settings,
|
|
152
|
+
lastResponseDate: new Date(),
|
|
153
|
+
firstDismissalDate: null,
|
|
154
|
+
lastDismissalDate: null,
|
|
155
|
+
}));
|
|
156
|
+
setIsFeedbackResponse(true);
|
|
157
|
+
// Thank you message displayed in the banner should disappear after few seconds.
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
setSurveyIsShown(false);
|
|
160
|
+
}, 3000);
|
|
161
|
+
},
|
|
162
|
+
onError() {
|
|
163
|
+
toggleNotification({
|
|
164
|
+
type: 'warning',
|
|
165
|
+
message: formatMessage({ id: 'notification.error' }),
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Only check on first render if the survey should be shown
|
|
172
|
+
const [surveyIsShown, setSurveyIsShown] = React.useState(
|
|
173
|
+
checkIfShouldShowSurvey(npsSurveySettings)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Set a cooldown to show the survey when session begins
|
|
177
|
+
const [displaySurvey, setDisplaySurvey] = React.useState(false);
|
|
178
|
+
|
|
179
|
+
React.useEffect(() => {
|
|
180
|
+
const displayTime = setTimeout(() => {
|
|
181
|
+
setDisplaySurvey(true);
|
|
182
|
+
}, delays.display);
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
clearTimeout(displayTime);
|
|
186
|
+
};
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
if (!displaySurvey) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!surveyIsShown) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const handleSubmitResponse = ({ npsSurveyRating: rating, npsSurveyFeedback: comment }) => {
|
|
198
|
+
const { email } = auth.getUserInfo();
|
|
199
|
+
mutate({
|
|
200
|
+
email,
|
|
201
|
+
rating,
|
|
202
|
+
comment,
|
|
203
|
+
environment: currentEnvironment,
|
|
204
|
+
version: strapiVersion,
|
|
205
|
+
license: window.strapi.projectType,
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const handleDismiss = () => {
|
|
210
|
+
setNpsSurveySettings((settings) => {
|
|
211
|
+
const nextSettings = {
|
|
212
|
+
...settings,
|
|
213
|
+
lastResponseDate: null,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (settings.firstDismissalDate) {
|
|
217
|
+
// If the user dismisses the survey for the second time
|
|
218
|
+
nextSettings.lastDismissalDate = new Date();
|
|
219
|
+
} else {
|
|
220
|
+
// If the user dismisses the survey for the first time
|
|
221
|
+
nextSettings.firstDismissalDate = new Date();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return nextSettings;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
setSurveyIsShown(false);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Portal>
|
|
232
|
+
<Formik
|
|
233
|
+
initialValues={{ npsSurveyFeedback: '', npsSurveyRating: null }}
|
|
234
|
+
onSubmit={handleSubmitResponse}
|
|
235
|
+
validationSchema={yup.object({
|
|
236
|
+
npsSurveyFeedback: yup.string(),
|
|
237
|
+
npsSurveyRating: yup.number().required(),
|
|
238
|
+
})}
|
|
239
|
+
>
|
|
240
|
+
{({ values, handleChange, setFieldValue }) => (
|
|
241
|
+
<Form name="npsSurveyForm">
|
|
242
|
+
<Flex
|
|
243
|
+
hasRadius
|
|
244
|
+
direction="column"
|
|
245
|
+
padding={4}
|
|
246
|
+
borderColor="primary200"
|
|
247
|
+
background="neutral0"
|
|
248
|
+
shadow="popupShadow"
|
|
249
|
+
position="fixed"
|
|
250
|
+
bottom={0}
|
|
251
|
+
left="50%"
|
|
252
|
+
transform="translateX(-50%)"
|
|
253
|
+
zIndex={theme.zIndices[2]}
|
|
254
|
+
width="50%"
|
|
255
|
+
>
|
|
256
|
+
{isFeedbackResponse ? (
|
|
257
|
+
<Typography fontWeight="semiBold">
|
|
258
|
+
{formatMessage({
|
|
259
|
+
id: 'app.components.NpsSurvey.feedback-response',
|
|
260
|
+
defaultMessage: 'Thank you very much for your feedback!',
|
|
261
|
+
})}
|
|
262
|
+
</Typography>
|
|
263
|
+
) : (
|
|
264
|
+
<Box as="fieldset" width="100%">
|
|
265
|
+
<Flex justifyContent="space-between" width="100%">
|
|
266
|
+
<Box marginLeft="auto" marginRight="auto">
|
|
267
|
+
<Typography fontWeight="semiBold" as="legend">
|
|
268
|
+
{formatMessage({
|
|
269
|
+
id: 'app.components.NpsSurvey.banner-title',
|
|
270
|
+
defaultMessage:
|
|
271
|
+
'How likely are you to recommend Strapi to a friend or colleague?',
|
|
272
|
+
})}
|
|
273
|
+
</Typography>
|
|
274
|
+
</Box>
|
|
275
|
+
<IconButton
|
|
276
|
+
onClick={handleDismiss}
|
|
277
|
+
aria-label={formatMessage({
|
|
278
|
+
id: 'app.components.NpsSurvey.dismiss-survey-label',
|
|
279
|
+
defaultMessage: 'Dismiss survey',
|
|
280
|
+
})}
|
|
281
|
+
icon={<Cross />}
|
|
282
|
+
/>
|
|
283
|
+
</Flex>
|
|
284
|
+
<Flex gap={2} marginTop={2} marginBottom={2} justifyContent="center">
|
|
285
|
+
<Typography variant="pi" textColor="neutral600">
|
|
286
|
+
{formatMessage({
|
|
287
|
+
id: 'app.components.NpsSurvey.no-recommendation',
|
|
288
|
+
defaultMessage: 'Not at all likely',
|
|
289
|
+
})}
|
|
290
|
+
</Typography>
|
|
291
|
+
{ratingArray.map((number) => {
|
|
292
|
+
return (
|
|
293
|
+
<FieldWrapper
|
|
294
|
+
key={number}
|
|
295
|
+
className={values.npsSurveyRating === number ? 'selected' : null} // "selected" class added when child radio button is checked
|
|
296
|
+
hasRadius
|
|
297
|
+
background="primary100"
|
|
298
|
+
borderColor="primary200"
|
|
299
|
+
color="primary600"
|
|
300
|
+
position="relative"
|
|
301
|
+
cursor="pointer"
|
|
302
|
+
>
|
|
303
|
+
<FieldLabel htmlFor={`nps-survey-rating-${number}-input`}>
|
|
304
|
+
<VisuallyHidden>
|
|
305
|
+
<FieldInput
|
|
306
|
+
type="radio"
|
|
307
|
+
id={`nps-survey-rating-${number}-input`}
|
|
308
|
+
name="npsSurveyRating"
|
|
309
|
+
checked={values.npsSurveyRating === number}
|
|
310
|
+
onChange={(e) =>
|
|
311
|
+
setFieldValue('npsSurveyRating', parseInt(e.target.value, 10))
|
|
312
|
+
}
|
|
313
|
+
value={number}
|
|
314
|
+
/>
|
|
315
|
+
</VisuallyHidden>
|
|
316
|
+
{number}
|
|
317
|
+
</FieldLabel>
|
|
318
|
+
</FieldWrapper>
|
|
319
|
+
);
|
|
320
|
+
})}
|
|
321
|
+
<Typography variant="pi" textColor="neutral600">
|
|
322
|
+
{formatMessage({
|
|
323
|
+
id: 'app.components.NpsSurvey.happy-to-recommend',
|
|
324
|
+
defaultMessage: 'Extremely likely',
|
|
325
|
+
})}
|
|
326
|
+
</Typography>
|
|
327
|
+
</Flex>
|
|
328
|
+
{values.npsSurveyRating !== null && (
|
|
329
|
+
<Flex direction="column">
|
|
330
|
+
<Box marginTop={2}>
|
|
331
|
+
<FieldLabel htmlFor="npsSurveyFeedback" fontWeight="semiBold" fontSize={2}>
|
|
332
|
+
{formatMessage({
|
|
333
|
+
id: 'app.components.NpsSurvey.feedback-question',
|
|
334
|
+
defaultMessage: 'Do you have any suggestion for improvements?',
|
|
335
|
+
})}
|
|
336
|
+
</FieldLabel>
|
|
337
|
+
</Box>
|
|
338
|
+
<Box width="62%" marginTop={3} marginBottom={4}>
|
|
339
|
+
<Textarea
|
|
340
|
+
id="npsSurveyFeedback" // formik element attribute "id" should be same as the values key to work
|
|
341
|
+
width="100%"
|
|
342
|
+
onChange={handleChange}
|
|
343
|
+
>
|
|
344
|
+
{values.npsSurveyFeedback}
|
|
345
|
+
</Textarea>
|
|
346
|
+
</Box>
|
|
347
|
+
<Button marginBottom={2} type="submit" loading={isLoading}>
|
|
348
|
+
{formatMessage({
|
|
349
|
+
id: 'app.components.NpsSurvey.submit-feedback',
|
|
350
|
+
defaultMessage: 'Submit Feedback',
|
|
351
|
+
})}
|
|
352
|
+
</Button>
|
|
353
|
+
</Flex>
|
|
354
|
+
)}
|
|
355
|
+
</Box>
|
|
356
|
+
)}
|
|
357
|
+
</Flex>
|
|
358
|
+
</Form>
|
|
359
|
+
)}
|
|
360
|
+
</Formik>
|
|
361
|
+
</Portal>
|
|
362
|
+
);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export default NpsSurvey;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useReducer, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LoadingIndicatorPage, useStrapiApp } from '@strapi/helper-plugin';
|
|
4
|
+
|
|
5
|
+
import Admin from '../../pages/Admin';
|
|
6
|
+
|
|
7
|
+
import init from './init';
|
|
8
|
+
import reducer, { initialState } from './reducer';
|
|
9
|
+
|
|
10
|
+
const PluginsInitializer = () => {
|
|
11
|
+
const { plugins: appPlugins } = useStrapiApp();
|
|
12
|
+
const [{ plugins }, dispatch] = useReducer(reducer, initialState, () => init(appPlugins));
|
|
13
|
+
const setPlugin = useRef((pluginId) => {
|
|
14
|
+
dispatch({ type: 'SET_PLUGIN_READY', pluginId });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const hasApluginNotReady = Object.keys(plugins).some(
|
|
18
|
+
(plugin) => plugins[plugin].isReady === false
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* I have spent some time trying to understand what is happening here, and wanted to
|
|
24
|
+
* leave that knowledge for my future me:
|
|
25
|
+
*
|
|
26
|
+
* `initializer` is an undocumented property of the `registerPlugin` API. At the time
|
|
27
|
+
* of writing it seems only to be used by the i18n plugin.
|
|
28
|
+
*
|
|
29
|
+
* How does it work?
|
|
30
|
+
*
|
|
31
|
+
* Every plugin that has an `initializer` component defined, receives the
|
|
32
|
+
* `setPlugin` function as a component prop. In the case of i18n the plugin fetches locales
|
|
33
|
+
* first and calls `setPlugin` with `pluginId` once they are loaded, which then triggers the
|
|
34
|
+
* reducer of the admin app defined above.
|
|
35
|
+
*
|
|
36
|
+
* Once all plugins are set to `isReady: true` the app renders.
|
|
37
|
+
*
|
|
38
|
+
* This API is used to block rendering of the admin app. We should remove that in v5 completely
|
|
39
|
+
* and make sure plugins can inject data into the global store before they are initialized, to avoid
|
|
40
|
+
* having a new prop-callback based communication channel between plugins and the core admin app.
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
if (hasApluginNotReady) {
|
|
45
|
+
const initializers = Object.keys(plugins).reduce((acc, current) => {
|
|
46
|
+
const InitializerComponent = plugins[current].initializer;
|
|
47
|
+
|
|
48
|
+
if (InitializerComponent) {
|
|
49
|
+
const key = plugins[current].pluginId;
|
|
50
|
+
|
|
51
|
+
acc.push(<InitializerComponent key={key} setPlugin={setPlugin.current} />);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return acc;
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{initializers}
|
|
60
|
+
<LoadingIndicatorPage />
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return <Admin />;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default PluginsInitializer;
|