@strapi/admin 4.2.0-beta.4 → 4.2.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.
Files changed (127) hide show
  1. package/admin/src/app.js +4 -7
  2. package/admin/src/hooks/useFetchMarketplaceProviders/index.js +23 -0
  3. package/admin/src/hooks/useFetchMarketplaceProviders/utils/api.js +11 -0
  4. package/admin/src/pages/Admin/index.js +5 -15
  5. package/admin/src/pages/App/index.js +3 -17
  6. package/admin/src/pages/MarketplacePage/components/{EmptyPluginSearch/EmptyPluginGrid.js → EmptyNpmPackageSearch/EmptyNpmPackageGrid.js} +1 -1
  7. package/admin/src/pages/MarketplacePage/components/{EmptyPluginSearch → EmptyNpmPackageSearch}/index.js +6 -4
  8. package/admin/src/pages/MarketplacePage/components/{PluginCard → NpmPackageCard}/InstallPluginButton.js +0 -0
  9. package/admin/src/pages/MarketplacePage/components/{PluginCard → NpmPackageCard}/index.js +22 -10
  10. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +42 -0
  11. package/admin/src/pages/MarketplacePage/components/PageHeader/index.js +12 -5
  12. package/admin/src/pages/MarketplacePage/index.js +99 -37
  13. package/admin/src/reducers.js +2 -4
  14. package/admin/src/translations/en.json +7 -3
  15. package/admin/src/translations/pl.json +264 -12
  16. package/admin/src/translations/vi.json +17 -17
  17. package/build/1856.6a94980b.chunk.js +172 -0
  18. package/build/2077.5456ccd1.chunk.js +194 -0
  19. package/build/2758.9475712b.chunk.js +162 -0
  20. package/build/2912.dd031292.chunk.js +253 -0
  21. package/build/4715.4588fdf5.chunk.js +385 -0
  22. package/build/4982.c57c5675.chunk.js +308 -0
  23. package/build/7197.ed8d6752.chunk.js +113 -0
  24. package/build/{6229.a5cca9f2.chunk.js → 7589.577cf729.chunk.js} +2 -2
  25. package/build/{472.0350a5bd.chunk.js → 7757.f6eb5e92.chunk.js} +58 -58
  26. package/build/7841.9e9cf739.chunk.js +253 -0
  27. package/build/8681.aec05472.chunk.js +163 -0
  28. package/build/9066.2847fdff.chunk.js +101 -0
  29. package/build/{4073.e144a91a.chunk.js → 9115.abdf4e3b.chunk.js} +1 -1
  30. package/build/9158.e48d88af.chunk.js +503 -0
  31. package/build/{9298.aff28744.chunk.js → 9298.cb3b6bc1.chunk.js} +93 -93
  32. package/build/9420.ba035f29.chunk.js +508 -0
  33. package/build/Admin-authenticatedApp.e7ed0550.chunk.js +80 -0
  34. package/build/{Admin_homePage.0ac648e8.chunk.js → Admin_homePage.8c00145e.chunk.js} +1 -1
  35. package/build/Admin_marketplace.a88d5bda.chunk.js +11 -0
  36. package/build/Admin_pluginsPage.e895d79f.chunk.js +1 -0
  37. package/build/{Admin_profilePage.27191ed2.chunk.js → Admin_profilePage.33cfed9b.chunk.js} +2 -2
  38. package/build/Admin_settingsPage.fe33e0a2.chunk.js +180 -0
  39. package/build/{admin-edit-roles-page.fb374555.chunk.js → admin-edit-roles-page.e77a2acc.chunk.js} +1 -1
  40. package/build/admin-edit-users.4c49fe98.chunk.js +11 -0
  41. package/build/admin-users.b89adf82.chunk.js +12 -0
  42. package/build/{api-tokens-create-page.698f132d.chunk.js → api-tokens-create-page.618b3e40.chunk.js} +1 -1
  43. package/build/{api-tokens-edit-page.afece2fe.chunk.js → api-tokens-edit-page.8d19dfe1.chunk.js} +1 -1
  44. package/build/{api-tokens-list-page.46d96dee.chunk.js → api-tokens-list-page.274e1c80.chunk.js} +1 -1
  45. package/build/{codemirror-css.b467b1de.chunk.js → codemirror-css.98490df3.chunk.js} +2 -2
  46. package/build/{codemirror-javacript.41bdefda.chunk.js → codemirror-javacript.cafbda9c.chunk.js} +1 -1
  47. package/build/codemirror-theme.b3c64617.chunk.js +34 -0
  48. package/build/{content-manager.7cd28f84.chunk.js → content-manager.04b93497.chunk.js} +9 -9
  49. package/build/content-type-builder.b3139cb1.chunk.js +141 -0
  50. package/build/{cropper-css.ecc0d670.chunk.js → cropper-css.0055cd53.chunk.js} +2 -2
  51. package/build/email-settings-page.4ae595f6.chunk.js +103 -0
  52. package/build/en-json.0a5ba154.chunk.js +1 -0
  53. package/build/{fontawesome-css-all.04f33619.chunk.js → fontawesome-css-all.b88d464e.chunk.js} +3 -3
  54. package/build/{fontawesome-css.477ba714.chunk.js → fontawesome-css.59dc4459.chunk.js} +2 -2
  55. package/build/highlight.js.9d8ef460.chunk.js +86 -0
  56. package/build/i18n-settings-page.bdac3c7b.chunk.js +101 -0
  57. package/build/index.html +1 -1
  58. package/build/main.3257934a.js +8404 -0
  59. package/build/pl-json.f65302c2.chunk.js +1 -0
  60. package/build/runtime~main.de49adfd.js +2 -0
  61. package/build/sso-settings-page.0b4d2106.chunk.js +1 -0
  62. package/build/upload-settings.cadfd452.chunk.js +101 -0
  63. package/build/upload.fbc65439.chunk.js +105 -0
  64. package/build/users-advanced-settings-page.646b6f29.chunk.js +101 -0
  65. package/build/users-email-settings-page.8b561ea3.chunk.js +1 -0
  66. package/build/users-providers-settings-page.82141ace.chunk.js +1 -0
  67. package/build/{users-roles-settings-page.988ebc3b.chunk.js → users-roles-settings-page.28bf6bdc.chunk.js} +2 -2
  68. package/build/vi-json.bf3424be.chunk.js +1 -0
  69. package/build/{webhook-edit-page.a7ae6e3b.chunk.js → webhook-edit-page.ca670f8d.chunk.js} +2 -2
  70. package/build/webhook-list-page.7057f1e8.chunk.js +133 -0
  71. package/index.js +239 -53
  72. package/package.json +7 -11
  73. package/scripts/build.js +3 -17
  74. package/server/controllers/admin.js +0 -15
  75. package/server/policies/index.js +0 -1
  76. package/server/routes/admin.js +0 -9
  77. package/server/routes/serve-admin-panel.js +1 -1
  78. package/webpack.config.js +5 -28
  79. package/admin/src/pages/App/constants.js +0 -1
  80. package/admin/src/pages/App/reducer.js +0 -22
  81. package/admin/src/tsconfig.json +0 -10
  82. package/build/1541.6c1c96f9.chunk.js +0 -307
  83. package/build/1856.521a99fd.chunk.js +0 -172
  84. package/build/2077.51485bfb.chunk.js +0 -194
  85. package/build/2912.79c2b3c8.chunk.js +0 -253
  86. package/build/3214.9196aeff.chunk.js +0 -235
  87. package/build/3865.21cec9de.chunk.js +0 -310
  88. package/build/4715.77e04177.chunk.js +0 -385
  89. package/build/4982.f53b78a4.chunk.js +0 -308
  90. package/build/7351.b95e65ae.chunk.js +0 -428
  91. package/build/7418.6db737ce.chunk.js +0 -112
  92. package/build/7841.f0e7d629.chunk.js +0 -253
  93. package/build/8826.58e236d4.chunk.js +0 -1057
  94. package/build/9066.118ecccd.chunk.js +0 -101
  95. package/build/9420.cb0b75e8.chunk.js +0 -508
  96. package/build/9988.f84412d9.chunk.js +0 -162
  97. package/build/Admin-authenticatedApp.162a5805.chunk.js +0 -80
  98. package/build/Admin_marketplace.0bb91ec8.chunk.js +0 -11
  99. package/build/Admin_pluginsPage.788fb2f6.chunk.js +0 -1
  100. package/build/Admin_settingsPage.23e873f0.chunk.js +0 -178
  101. package/build/admin-edit-users.a360deaf.chunk.js +0 -10
  102. package/build/admin-users.47d06d24.chunk.js +0 -11
  103. package/build/codemirror-theme.cf9f9eb6.chunk.js +0 -34
  104. package/build/content-type-builder.7456cabe.chunk.js +0 -141
  105. package/build/email-settings-page.f67d13b2.chunk.js +0 -103
  106. package/build/en-json.40ee00aa.chunk.js +0 -1
  107. package/build/highlight.js.3381ffc3.chunk.js +0 -86
  108. package/build/i18n-settings-page.6b67cb75.chunk.js +0 -101
  109. package/build/main.b632a0d6.js +0 -11625
  110. package/build/pl-json.94f05d2c.chunk.js +0 -1
  111. package/build/runtime~main.38d418e9.js +0 -2
  112. package/build/sso-settings-page.dfb0b917.chunk.js +0 -1
  113. package/build/upload-settings.3db55de0.chunk.js +0 -101
  114. package/build/upload.070c189b.chunk.js +0 -105
  115. package/build/users-advanced-settings-page.a23cda17.chunk.js +0 -101
  116. package/build/users-email-settings-page.0a096388.chunk.js +0 -1
  117. package/build/users-providers-settings-page.bfe7755a.chunk.js +0 -1
  118. package/build/vi-json.3d14e91e.chunk.js +0 -1
  119. package/build/webhook-list-page.83297d98.chunk.js +0 -133
  120. package/server/policies/isTelemetryEnabled.js +0 -16
  121. package/utils/create-cache-dir.js +0 -161
  122. package/utils/get-custom-app-config-file.js +0 -23
  123. package/utils/get-custom-webpack-config.js +0 -38
  124. package/utils/get-plugins-path.js +0 -26
  125. package/utils/index.js +0 -13
  126. package/utils/should-build-admin.js +0 -52
  127. package/utils/watch-admin-files.js +0 -59
package/admin/src/app.js CHANGED
@@ -1,9 +1,6 @@
1
- const config = {
2
- locales: ['fr'],
3
- };
4
- const bootstrap = () => {};
5
-
6
1
  export default {
7
- config,
8
- bootstrap,
2
+ config: {
3
+ locales: ['fr'],
4
+ },
5
+ bootstrap() {},
9
6
  };
@@ -0,0 +1,23 @@
1
+ import { useQuery } from 'react-query';
2
+ import { useNotification } from '@strapi/helper-plugin';
3
+ import { fetchMarketplacePlugins } from './utils/api';
4
+
5
+ const useFetchMarketplaceProviders = (notifyLoad) => {
6
+ const toggleNotification = useNotification();
7
+
8
+ return useQuery('list-marketplace-providers', () => fetchMarketplacePlugins(), {
9
+ onSuccess: () => {
10
+ if (notifyLoad) {
11
+ notifyLoad();
12
+ }
13
+ },
14
+ onError: () => {
15
+ toggleNotification({
16
+ type: 'warning',
17
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
18
+ });
19
+ },
20
+ });
21
+ };
22
+
23
+ export default useFetchMarketplaceProviders;
@@ -0,0 +1,11 @@
1
+ import axios from 'axios';
2
+
3
+ const MARKETPLACE_API_URL = 'https://market-api.strapi.io';
4
+
5
+ const fetchMarketplacePlugins = async () => {
6
+ const { data } = await axios.get(`${MARKETPLACE_API_URL}/providers`);
7
+
8
+ return data;
9
+ };
10
+
11
+ export { fetchMarketplacePlugins };
@@ -6,17 +6,16 @@
6
6
 
7
7
  import React, { Suspense, useEffect, useMemo, lazy } from 'react';
8
8
  import { Switch, Route } from 'react-router-dom';
9
+ // Components from @strapi/helper-plugin
9
10
  import { useTracking, LoadingIndicatorPage, useStrapiApp } from '@strapi/helper-plugin';
10
- import { useDispatch, useSelector } from 'react-redux';
11
11
  import { DndProvider } from 'react-dnd';
12
12
  import { HTML5Backend } from 'react-dnd-html5-backend';
13
- import GuidedTourModal from '../../components/GuidedTour/Modal';
14
13
  import LeftMenu from '../../components/LeftMenu';
15
14
  import AppLayout from '../../layouts/AppLayout';
16
15
  import { useMenu } from '../../hooks';
17
- import { createRoute } from '../../utils';
18
- import { SET_APP_RUNTIME_STATUS } from '../App/constants';
19
16
  import Onboarding from './Onboarding';
17
+ import { createRoute } from '../../utils';
18
+ import GuidedTourModal from '../../components/GuidedTour/Modal';
20
19
 
21
20
  const CM = lazy(() =>
22
21
  import(/* webpackChunkName: "content-manager" */ '../../content-manager/pages/App')
@@ -41,20 +40,11 @@ const SettingsPage = lazy(() =>
41
40
  // Simple hook easier for testing
42
41
  const useTrackUsage = () => {
43
42
  const { trackUsage } = useTracking();
44
- const dispatch = useDispatch();
45
- const appStatus = useSelector(state => state.admin_app.status);
46
43
 
47
44
  useEffect(() => {
48
- // Make sure the event is only send once after accessing the admin panel
49
- // and not at runtime for example when regenerating the permissions with the ctb
50
- // or with i18n
51
- if (appStatus === 'init') {
52
- trackUsage('didAccessAuthenticatedAdministration');
53
-
54
- dispatch({ type: SET_APP_RUNTIME_STATUS });
55
- }
45
+ trackUsage('didAccessAuthenticatedAdministration');
56
46
  // eslint-disable-next-line react-hooks/exhaustive-deps
57
- }, [appStatus]);
47
+ }, []);
58
48
  };
59
49
 
60
50
  const Admin = () => {
@@ -14,7 +14,6 @@ import {
14
14
  TrackingContext,
15
15
  prefixFileUrlWithBackendUrl,
16
16
  } from '@strapi/helper-plugin';
17
- import axios from 'axios';
18
17
  import { SkipToContent } from '@strapi/design-system/Main';
19
18
  import { useIntl } from 'react-intl';
20
19
  import PrivateRoute from '../../components/PrivateRoute';
@@ -42,8 +41,6 @@ function App() {
42
41
  );
43
42
  }, []);
44
43
 
45
- const [telemetryProperties, setTelemetryProperties] = useState(null);
46
-
47
44
  useEffect(() => {
48
45
  const currentToken = auth.getToken();
49
46
 
@@ -72,20 +69,12 @@ function App() {
72
69
  const getData = async () => {
73
70
  try {
74
71
  const {
75
- data: {
76
- data: { hasAdmin, uuid, menuLogo },
77
- },
78
- } = await axios.get(`${strapi.backendURL}/admin/init`);
72
+ data: { hasAdmin, uuid, menuLogo },
73
+ } = await request('/admin/init', { method: 'GET' });
79
74
 
80
75
  updateProjectSettings({ menuLogo: prefixFileUrlWithBackendUrl(menuLogo) });
81
76
 
82
77
  if (uuid) {
83
- const {
84
- data: { data: properties },
85
- } = await axios.get(`${strapi.backendURL}/admin/telemetry-properties`);
86
-
87
- setTelemetryProperties(properties);
88
-
89
78
  try {
90
79
  const deviceId = await getUID();
91
80
 
@@ -95,9 +84,6 @@ function App() {
95
84
  event: 'didInitializeAdministration',
96
85
  uuid,
97
86
  deviceId,
98
- properties: {
99
- ...properties,
100
- },
101
87
  }),
102
88
  headers: {
103
89
  'Content-Type': 'application/json',
@@ -129,7 +115,7 @@ function App() {
129
115
  return (
130
116
  <Suspense fallback={<LoadingIndicatorPage />}>
131
117
  <SkipToContent>{formatMessage({ id: 'skipToContent' })}</SkipToContent>
132
- <TrackingContext.Provider value={{ uuid, telemetryProperties }}>
118
+ <TrackingContext.Provider value={uuid}>
133
119
  <Switch>
134
120
  {authRoutes}
135
121
  <Route
@@ -9,7 +9,7 @@ const EmptyPluginCard = styled(Box)`
9
9
  opacity: 0.33;
10
10
  `;
11
11
 
12
- export const EmptyPluginGrid = () => {
12
+ export const EmptyNpmPackageGrid = () => {
13
13
  return (
14
14
  <GridLayout>
15
15
  {Array(12)
@@ -5,12 +5,12 @@ import { Box } from '@strapi/design-system/Box';
5
5
  import { Flex } from '@strapi/design-system/Flex';
6
6
  import { Icon } from '@strapi/design-system/Icon';
7
7
  import EmptyStateDocument from '@strapi/icons/EmptyDocuments';
8
- import { EmptyPluginGrid } from './EmptyPluginGrid';
8
+ import { EmptyNpmPackageGrid } from './EmptyNpmPackageGrid';
9
9
 
10
- export const EmptyPluginSearch = ({ content }) => {
10
+ const EmptyNpmPackageSearch = ({ content }) => {
11
11
  return (
12
12
  <Box position="relative">
13
- <EmptyPluginGrid />
13
+ <EmptyNpmPackageGrid />
14
14
  <Box position="absolute" top={11} width="100%">
15
15
  <Flex alignItems="center" justifyContent="center" direction="column">
16
16
  <Icon as={EmptyStateDocument} color="" width="160px" height="88px" />
@@ -25,6 +25,8 @@ export const EmptyPluginSearch = ({ content }) => {
25
25
  );
26
26
  };
27
27
 
28
- EmptyPluginSearch.propTypes = {
28
+ EmptyNpmPackageSearch.propTypes = {
29
29
  content: PropTypes.string.isRequired,
30
30
  };
31
+
32
+ export default EmptyNpmPackageSearch;
@@ -25,12 +25,18 @@ const EllipsisText = styled(Typography)`
25
25
  overflow: hidden;
26
26
  `;
27
27
 
28
- const PluginCard = ({ plugin, installedPluginNames, useYarn, isInDevelopmentMode }) => {
29
- const { attributes } = plugin;
28
+ const NpmPackageCard = ({
29
+ npmPackage,
30
+ installedPackageNames,
31
+ useYarn,
32
+ isInDevelopmentMode,
33
+ npmPackageType,
34
+ }) => {
35
+ const { attributes } = npmPackage;
30
36
  const { formatMessage } = useIntl();
31
37
  const { trackUsage } = useTracking();
32
38
 
33
- const isInstalled = installedPluginNames.includes(attributes.npmPackageName);
39
+ const isInstalled = installedPackageNames.includes(attributes.npmPackageName);
34
40
 
35
41
  const commandToCopy = useYarn
36
42
  ? `yarn add ${attributes.npmPackageName}`
@@ -41,6 +47,11 @@ const PluginCard = ({ plugin, installedPluginNames, useYarn, isInDevelopmentMode
41
47
  defaultMessage: 'Made by Strapi',
42
48
  });
43
49
 
50
+ const npmPackageHref =
51
+ npmPackageType === 'provider'
52
+ ? attributes.npmPackageUrl
53
+ : `https://market.strapi.io/plugins/${attributes.slug}`;
54
+
44
55
  return (
45
56
  <Flex
46
57
  direction="column"
@@ -107,7 +118,7 @@ const PluginCard = ({ plugin, installedPluginNames, useYarn, isInDevelopmentMode
107
118
  <Stack horizontal spacing={2} style={{ alignSelf: 'flex-end' }} paddingTop={6}>
108
119
  <LinkButton
109
120
  size="S"
110
- href={`https://market.strapi.io/plugins/${attributes.slug}`}
121
+ href={npmPackageHref}
111
122
  isExternal
112
123
  endIcon={<ExternalLink />}
113
124
  aria-label={formatMessage(
@@ -135,12 +146,12 @@ const PluginCard = ({ plugin, installedPluginNames, useYarn, isInDevelopmentMode
135
146
  );
136
147
  };
137
148
 
138
- PluginCard.defaultProps = {
149
+ NpmPackageCard.defaultProps = {
139
150
  isInDevelopmentMode: false,
140
151
  };
141
152
 
142
- PluginCard.propTypes = {
143
- plugin: PropTypes.shape({
153
+ NpmPackageCard.propTypes = {
154
+ npmPackage: PropTypes.shape({
144
155
  id: PropTypes.string.isRequired,
145
156
  attributes: PropTypes.shape({
146
157
  name: PropTypes.string.isRequired,
@@ -153,12 +164,13 @@ PluginCard.propTypes = {
153
164
  developerName: PropTypes.string.isRequired,
154
165
  validated: PropTypes.bool.isRequired,
155
166
  madeByStrapi: PropTypes.bool.isRequired,
156
- strapiCompatibility: PropTypes.oneOf(['v3', 'v4']).isRequired,
167
+ strapiCompatibility: PropTypes.oneOf(['v3', 'v4']),
157
168
  }).isRequired,
158
169
  }).isRequired,
159
- installedPluginNames: PropTypes.arrayOf(PropTypes.string).isRequired,
170
+ installedPackageNames: PropTypes.arrayOf(PropTypes.string).isRequired,
160
171
  useYarn: PropTypes.bool.isRequired,
161
172
  isInDevelopmentMode: PropTypes.bool,
173
+ npmPackageType: PropTypes.string.isRequired,
162
174
  };
163
175
 
164
- export default PluginCard;
176
+ export default NpmPackageCard;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Grid, GridItem } from '@strapi/design-system/Grid';
4
+ import NpmPackageCard from '../NpmPackageCard';
5
+
6
+ const NpmPackagesGrid = ({
7
+ npmPackages,
8
+ installedPackageNames,
9
+ useYarn,
10
+ isInDevelopmentMode,
11
+ npmPackageType,
12
+ }) => {
13
+ return (
14
+ <Grid gap={4}>
15
+ {npmPackages.map((npmPackage) => (
16
+ <GridItem col={4} s={6} xs={12} style={{ height: '100%' }} key={npmPackage.id}>
17
+ <NpmPackageCard
18
+ npmPackage={npmPackage}
19
+ installedPackageNames={installedPackageNames}
20
+ useYarn={useYarn}
21
+ isInDevelopmentMode={isInDevelopmentMode}
22
+ npmPackageType={npmPackageType}
23
+ />
24
+ </GridItem>
25
+ ))}
26
+ </Grid>
27
+ );
28
+ };
29
+
30
+ NpmPackagesGrid.defaultProps = {
31
+ installedPackageNames: [],
32
+ };
33
+
34
+ NpmPackagesGrid.propTypes = {
35
+ npmPackages: PropTypes.array.isRequired,
36
+ installedPackageNames: PropTypes.arrayOf(PropTypes.string),
37
+ useYarn: PropTypes.bool.isRequired,
38
+ isInDevelopmentMode: PropTypes.bool.isRequired,
39
+ npmPackageType: PropTypes.string.isRequired,
40
+ };
41
+
42
+ export default NpmPackagesGrid;
@@ -6,10 +6,12 @@ import { LinkButton } from '@strapi/design-system/v2/LinkButton';
6
6
  import Upload from '@strapi/icons/Upload';
7
7
  import { useTracking } from '@strapi/helper-plugin';
8
8
 
9
- const PageHeader = ({ isOnline }) => {
9
+ const PageHeader = ({ isOnline, npmPackageType }) => {
10
10
  const { formatMessage } = useIntl();
11
11
  const { trackUsage } = useTracking();
12
12
 
13
+ const tracking = npmPackageType === 'provider' ? 'didSubmitProvider' : 'didSubmitPlugin';
14
+
13
15
  return (
14
16
  <HeaderLayout
15
17
  title={formatMessage({
@@ -25,13 +27,13 @@ const PageHeader = ({ isOnline }) => {
25
27
  <LinkButton
26
28
  startIcon={<Upload />}
27
29
  variant="tertiary"
28
- href="https://market.strapi.io/submit-plugin"
29
- onClick={() => trackUsage('didSubmitPlugin')}
30
+ href={`https://market.strapi.io/submit-${npmPackageType}`}
31
+ onClick={() => trackUsage(tracking)}
30
32
  isExternal
31
33
  >
32
34
  {formatMessage({
33
- id: 'admin.pages.MarketPlacePage.submit.plugin.link',
34
- defaultMessage: 'Submit your plugin',
35
+ id: `admin.pages.MarketPlacePage.submit.${npmPackageType}.link`,
36
+ defaultMessage: `Submit ${npmPackageType}`,
35
37
  })}
36
38
  </LinkButton>
37
39
  )
@@ -42,6 +44,11 @@ const PageHeader = ({ isOnline }) => {
42
44
 
43
45
  export default PageHeader;
44
46
 
47
+ PageHeader.defaultProps = {
48
+ npmPackageType: 'plugin',
49
+ };
50
+
45
51
  PageHeader.propTypes = {
46
52
  isOnline: PropTypes.bool.isRequired,
53
+ npmPackageType: PropTypes.string,
47
54
  };
@@ -12,7 +12,6 @@ import {
12
12
  useNotification,
13
13
  useAppInfos,
14
14
  } from '@strapi/helper-plugin';
15
- import { Grid, GridItem } from '@strapi/design-system/Grid';
16
15
  import { Layout, ContentLayout } from '@strapi/design-system/Layout';
17
16
  import { Main } from '@strapi/design-system/Main';
18
17
  import { Searchbar } from '@strapi/design-system/Searchbar';
@@ -20,20 +19,22 @@ import { Box } from '@strapi/design-system/Box';
20
19
  import { useNotifyAT } from '@strapi/design-system/LiveRegions';
21
20
  import { Typography } from '@strapi/design-system/Typography';
22
21
  import { Flex } from '@strapi/design-system/Flex';
22
+ import { Tabs, Tab, TabGroup, TabPanels, TabPanel } from '@strapi/design-system/Tabs';
23
23
 
24
- import PluginCard from './components/PluginCard';
25
- import { EmptyPluginSearch } from './components/EmptyPluginSearch';
24
+ import EmptyNpmPackageSearch from './components/EmptyNpmPackageSearch';
26
25
  import PageHeader from './components/PageHeader';
27
26
  import { fetchAppInformation } from './utils/api';
28
27
  import useFetchInstalledPlugins from '../../hooks/useFetchInstalledPlugins';
28
+ import useFetchMarketplaceProviders from '../../hooks/useFetchMarketplaceProviders';
29
29
  import useFetchMarketplacePlugins from '../../hooks/useFetchMarketplacePlugins';
30
30
  import adminPermissions from '../../permissions';
31
31
  import offlineCloud from '../../assets/images/icon_offline-cloud.svg';
32
32
  import useNavigatorOnLine from '../../hooks/useNavigatorOnLine';
33
33
  import MissingPluginBanner from './components/MissingPluginBanner';
34
+ import NpmPackagesGrid from './components/NpmPackagesGrid';
34
35
 
35
- const matchSearch = (plugins, search) => {
36
- return matchSorter(plugins, search, {
36
+ const matchSearch = (npmPackages, search) => {
37
+ return matchSorter(npmPackages, search, {
37
38
  keys: [
38
39
  {
39
40
  threshold: matchSorter.rankings.WORD_STARTS_WITH,
@@ -51,6 +52,7 @@ const MarketPlacePage = () => {
51
52
  const trackUsageRef = useRef(trackUsage);
52
53
  const toggleNotification = useNotification();
53
54
  const [searchQuery, setSearchQuery] = useState('');
55
+ const [npmPackageType, setNpmPackageType] = useState('plugin');
54
56
  const { autoReload: isInDevelopmentMode } = useAppInfos();
55
57
  const isOnline = useNavigatorOnLine();
56
58
 
@@ -78,6 +80,11 @@ const MarketPlacePage = () => {
78
80
  data: marketplacePluginsResponse,
79
81
  } = useFetchMarketplacePlugins(notifyMarketplaceLoad);
80
82
 
83
+ const {
84
+ status: marketplaceProvidersStatus,
85
+ data: marketplaceProvidersResponse,
86
+ } = useFetchMarketplaceProviders(notifyMarketplaceLoad);
87
+
81
88
  const {
82
89
  status: installedPluginsStatus,
83
90
  data: installedPluginsResponse,
@@ -96,13 +103,19 @@ const MarketPlacePage = () => {
96
103
  }
97
104
  );
98
105
 
99
- const isLoading = [marketplacePluginsStatus, installedPluginsStatus, appInfoStatus].includes(
100
- 'loading'
101
- );
106
+ const isLoading = [
107
+ marketplacePluginsStatus,
108
+ marketplaceProvidersStatus,
109
+ installedPluginsStatus,
110
+ appInfoStatus,
111
+ ].includes('loading');
102
112
 
103
- const hasFailed = [marketplacePluginsStatus, installedPluginsStatus, appInfoStatus].includes(
104
- 'error'
105
- );
113
+ const hasFailed = [
114
+ marketplacePluginsStatus,
115
+ marketplaceProvidersStatus,
116
+ installedPluginsStatus,
117
+ appInfoStatus,
118
+ ].includes('error');
106
119
 
107
120
  useEffect(() => {
108
121
  trackUsageRef.current('didGoToMarketplace');
@@ -179,7 +192,23 @@ const MarketPlacePage = () => {
179
192
  );
180
193
  }
181
194
 
182
- const searchResults = matchSearch(marketplacePluginsResponse.data, searchQuery);
195
+ // Search for plugins and providers that match the search query
196
+ const pluginSearchResults = matchSearch(marketplacePluginsResponse.data, searchQuery);
197
+ const providerSearchResults = matchSearch(marketplaceProvidersResponse.data, searchQuery);
198
+ const emptySearchMessage = formatMessage(
199
+ {
200
+ id: 'admin.pages.MarketPlacePage.search.empty',
201
+ defaultMessage: 'No result for "{target}"',
202
+ },
203
+ { target: searchQuery }
204
+ );
205
+
206
+ const handleTabChange = selected => {
207
+ const packageType = selected === 0 ? 'plugin' : 'provider';
208
+ setNpmPackageType(packageType);
209
+ };
210
+
211
+ // Check if plugins are installed already
183
212
  const installedPluginNames = installedPluginsResponse.plugins.map(plugin => plugin.packageName);
184
213
 
185
214
  return (
@@ -191,7 +220,7 @@ const MarketPlacePage = () => {
191
220
  defaultMessage: 'Marketplace - Plugins',
192
221
  })}
193
222
  />
194
- <PageHeader isOnline={isOnline} />
223
+ <PageHeader isOnline={isOnline} npmPackageType={npmPackageType} />
195
224
  <ContentLayout>
196
225
  <Box width="25%" paddingBottom={4}>
197
226
  <Searchbar
@@ -201,43 +230,76 @@ const MarketPlacePage = () => {
201
230
  onChange={e => setSearchQuery(e.target.value)}
202
231
  clearLabel={formatMessage({
203
232
  id: 'admin.pages.MarketPlacePage.search.clear',
204
- defaultMessage: 'Clear the plugin search',
233
+ defaultMessage: 'Clear the search',
205
234
  })}
206
235
  placeholder={formatMessage({
207
236
  id: 'admin.pages.MarketPlacePage.search.placeholder',
208
- defaultMessage: 'Search for a plugin',
237
+ defaultMessage: 'Search',
209
238
  })}
210
239
  >
211
240
  {formatMessage({
212
241
  id: 'admin.pages.MarketPlacePage.search.placeholder',
213
- defaultMessage: 'Search for a plugin',
242
+ defaultMessage: 'Search',
214
243
  })}
215
244
  </Searchbar>
216
245
  </Box>
217
- {searchQuery.length > 0 && !searchResults.length ? (
218
- <EmptyPluginSearch
219
- content={formatMessage(
220
- {
221
- id: 'admin.pages.MarketPlacePage.search.empty',
222
- defaultMessage: 'No result for "{target}"',
223
- },
224
- { target: searchQuery }
225
- )}
226
- />
227
- ) : (
228
- <Grid gap={4}>
229
- {searchResults.map(plugin => (
230
- <GridItem col={4} s={6} xs={12} style={{ height: '100%' }} key={plugin.id}>
231
- <PluginCard
232
- plugin={plugin}
233
- installedPluginNames={installedPluginNames}
246
+ <TabGroup
247
+ label={formatMessage({
248
+ id: 'admin.pages.MarketPlacePage.tab-group.label',
249
+ defaultMessage: 'Plugins and Providers for Strapi',
250
+ })}
251
+ id="tabs"
252
+ variant="simple"
253
+ onTabChange={handleTabChange}
254
+ >
255
+ <Box paddingBottom={4}>
256
+ <Tabs>
257
+ <Tab>
258
+ {formatMessage({
259
+ id: 'admin.pages.MarketPlacePage.plugins',
260
+ defaultMessage: 'Plugins',
261
+ })}{' '}
262
+ ({pluginSearchResults.length})
263
+ </Tab>
264
+ <Tab>
265
+ {formatMessage({
266
+ id: 'admin.pages.MarketPlacePage.providers',
267
+ defaultMessage: 'Providers',
268
+ })}{' '}
269
+ ({providerSearchResults.length})
270
+ </Tab>
271
+ </Tabs>
272
+ </Box>
273
+ <TabPanels>
274
+ {/* Plugins panel */}
275
+ <TabPanel>
276
+ {searchQuery.length > 0 && !pluginSearchResults.length ? (
277
+ <EmptyNpmPackageSearch content={emptySearchMessage} />
278
+ ) : (
279
+ <NpmPackagesGrid
280
+ npmPackages={pluginSearchResults}
281
+ installedPackageNames={installedPluginNames}
282
+ useYarn={appInfoResponse.data.useYarn}
283
+ isInDevelopmentMode={isInDevelopmentMode}
284
+ npmPackageType="plugin"
285
+ />
286
+ )}
287
+ </TabPanel>
288
+ {/* Providers panel */}
289
+ <TabPanel>
290
+ {searchQuery.length > 0 && !providerSearchResults.length ? (
291
+ <EmptyNpmPackageSearch content={emptySearchMessage} />
292
+ ) : (
293
+ <NpmPackagesGrid
294
+ npmPackages={providerSearchResults}
234
295
  useYarn={appInfoResponse.data.useYarn}
235
296
  isInDevelopmentMode={isInDevelopmentMode}
297
+ npmPackageType="provider"
236
298
  />
237
- </GridItem>
238
- ))}
239
- </Grid>
240
- )}
299
+ )}
300
+ </TabPanel>
301
+ </TabPanels>
302
+ </TabGroup>
241
303
  <Box paddingTop={7}>
242
304
  <MissingPluginBanner />
243
305
  </Box>
@@ -1,13 +1,12 @@
1
- import appReducer from './pages/App/reducer';
2
1
  import rbacProviderReducer from './components/RBACProvider/reducer';
3
- import cmAppReducer from './content-manager/pages/App/reducer';
2
+ import appReducer from './content-manager/pages/App/reducer';
4
3
  import editViewLayoutManagerReducer from './content-manager/pages/EditViewLayoutManager/reducer';
5
4
  import listViewReducer from './content-manager/pages/ListView/reducer';
6
5
  import rbacManagerReducer from './content-manager/hooks/useSyncRbac/reducer';
7
6
  import editViewCrudReducer from './content-manager/sharedReducers/crudReducer/reducer';
8
7
 
9
8
  const contentManagerReducers = {
10
- 'content-manager_app': cmAppReducer,
9
+ 'content-manager_app': appReducer,
11
10
  'content-manager_listView': listViewReducer,
12
11
  'content-manager_rbacManager': rbacManagerReducer,
13
12
  'content-manager_editViewLayoutManager': editViewLayoutManagerReducer,
@@ -15,7 +14,6 @@ const contentManagerReducers = {
15
14
  };
16
15
 
17
16
  const reducers = {
18
- admin_app: appReducer,
19
17
  rbacProvider: rbacProviderReducer,
20
18
  ...contentManagerReducers,
21
19
  };
@@ -232,6 +232,7 @@
232
232
  "admin.pages.MarketPlacePage.helmet": "Marketplace - Plugins",
233
233
  "admin.pages.MarketPlacePage.offline.title": "You are offline",
234
234
  "admin.pages.MarketPlacePage.offline.subtitle": "You need to be connected to the Internet to access Strapi Market.",
235
+ "admin.pages.MarketPlacePage.plugins": "Plugins",
235
236
  "admin.pages.MarketPlacePage.plugin.copy": "Copy install command",
236
237
  "admin.pages.MarketPlacePage.plugin.copy.success": "Install command ready to be pasted in your terminal",
237
238
  "admin.pages.MarketPlacePage.plugin.info": "Learn more",
@@ -240,11 +241,14 @@
240
241
  "admin.pages.MarketPlacePage.plugin.installed": "Installed",
241
242
  "admin.pages.MarketPlacePage.plugin.tooltip.madeByStrapi": "Made by Strapi",
242
243
  "admin.pages.MarketPlacePage.plugin.tooltip.verified": "Plugin verified by Strapi",
243
- "admin.pages.MarketPlacePage.search.clear": "Clear the plugin search",
244
+ "admin.pages.MarketPlacePage.providers": "Providers",
245
+ "admin.pages.MarketPlacePage.search.clear": "Clear the search",
244
246
  "admin.pages.MarketPlacePage.search.empty": "No result for \"{target}\"",
245
- "admin.pages.MarketPlacePage.search.placeholder": "Search for a plugin",
246
- "admin.pages.MarketPlacePage.submit.plugin.link": "Submit your plugin",
247
+ "admin.pages.MarketPlacePage.search.placeholder": "Search",
248
+ "admin.pages.MarketPlacePage.submit.plugin.link": "Submit plugin",
249
+ "admin.pages.MarketPlacePage.submit.provider.link": "Submit provider",
247
250
  "admin.pages.MarketPlacePage.subtitle": "Get more out of Strapi",
251
+ "admin.pages.MarketPlacePage.tab-group.label": "Plugins and Providers for Strapi",
248
252
  "admin.pages.MarketPlacePage.missingPlugin.title": "Missing a plugin?",
249
253
  "admin.pages.MarketPlacePage.missingPlugin.description": "Tell us what plugin you are looking for and we'll let our community plugin developers know in case they are in search for inspiration!",
250
254
  "anErrorOccurred": "Woops! Something went wrong. Please, try again.",