@strapi/admin 4.4.0-beta.3 → 4.4.0-rc.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 (92) hide show
  1. package/admin/src/StrapiApp.js +2 -0
  2. package/admin/src/components/AuthenticatedApp/index.js +2 -2
  3. package/admin/src/components/LanguageProvider/index.js +1 -0
  4. package/admin/src/components/ThemeToggleProvider/index.js +21 -10
  5. package/admin/src/content-manager/components/DynamicTable/index.js +2 -2
  6. package/admin/src/content-manager/components/InputJSON/index.js +2 -0
  7. package/admin/src/contexts/ApiTokenPermissions/index.js +24 -0
  8. package/admin/src/hooks/index.js +1 -0
  9. package/admin/src/hooks/useRegenerate/index.js +34 -0
  10. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +56 -0
  11. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +41 -0
  12. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +72 -0
  13. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +30 -0
  14. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +150 -0
  15. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +37 -0
  16. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +254 -0
  17. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +77 -0
  18. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +85 -0
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +40 -0
  20. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +68 -0
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +215 -197
  22. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +13 -0
  23. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +72 -0
  24. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +16 -0
  25. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +5 -0
  26. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +2 -1
  27. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +36 -0
  28. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +63 -0
  29. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +1 -0
  30. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +19 -0
  31. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +3 -36
  32. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +13 -11
  33. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +3 -2
  34. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  35. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  36. package/admin/src/permissions/defaultPermissions.js +2 -6
  37. package/admin/src/translations/en.json +17 -0
  38. package/admin/src/translations/zh-Hans.json +0 -1
  39. package/build/2077.c935ee42.chunk.js +205 -0
  40. package/build/4318.7d167b58.chunk.js +30 -0
  41. package/build/{4715.3f6cac0a.chunk.js → 4715.58cd558f.chunk.js} +32 -31
  42. package/build/4982.05eda880.chunk.js +324 -0
  43. package/build/7379.d246dd38.chunk.js +1 -0
  44. package/build/{7866.c793a31d.chunk.js → 7866.1201afbd.chunk.js} +2 -2
  45. package/build/{8773.eccaa5f3.chunk.js → 8773.c06c24c0.chunk.js} +2 -2
  46. package/build/{Admin-authenticatedApp.50e41ff2.chunk.js → Admin-authenticatedApp.9dec5230.chunk.js} +6 -6
  47. package/build/{Admin_homePage.118926e0.chunk.js → Admin_homePage.6d5e3236.chunk.js} +1 -1
  48. package/build/{Admin_profilePage.9d50ac44.chunk.js → Admin_profilePage.da32abbc.chunk.js} +1 -1
  49. package/build/{Admin_settingsPage.98a711e5.chunk.js → Admin_settingsPage.98e2a62b.chunk.js} +16 -16
  50. package/build/admin-app.a61d5c2e.chunk.js +112 -0
  51. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +1 -0
  52. package/build/api-tokens-create-page.93dd0689.chunk.js +1 -0
  53. package/build/api-tokens-edit-page.b0adac81.chunk.js +1 -0
  54. package/build/api-tokens-list-page.bb36535f.chunk.js +16 -0
  55. package/build/{content-manager.2a6f876d.chunk.js → content-manager.feb0d540.chunk.js} +2 -2
  56. package/build/{content-type-builder.d4610e20.chunk.js → content-type-builder.a684b2e8.chunk.js} +11 -11
  57. package/build/en-json.a9918c93.chunk.js +1 -0
  58. package/build/index.html +1 -1
  59. package/build/{main.fdc482f3.js → main.e4065f58.js} +1304 -1288
  60. package/build/{runtime~main.29105d25.js → runtime~main.4204f341.js} +2 -2
  61. package/build/sso-settings-page.9ceb0140.chunk.js +1 -0
  62. package/build/{webhook-edit-page.d2ea3351.chunk.js → webhook-edit-page.9e46fc3f.chunk.js} +1 -1
  63. package/build/{zh-Hans-json.77a42bc5.chunk.js → zh-Hans-json.9c99f8d4.chunk.js} +1 -1
  64. package/package.json +10 -9
  65. package/server/bootstrap.js +19 -1
  66. package/server/config/admin-actions.js +20 -0
  67. package/server/content-types/api-token-permission.js +36 -0
  68. package/server/content-types/api-token.js +25 -1
  69. package/server/content-types/index.js +1 -0
  70. package/server/controllers/api-token.js +24 -1
  71. package/server/controllers/content-api.js +15 -0
  72. package/server/controllers/index.js +1 -0
  73. package/server/routes/api-tokens.js +11 -0
  74. package/server/routes/content-api.js +20 -0
  75. package/server/routes/index.js +2 -0
  76. package/server/services/api-token.js +310 -29
  77. package/server/services/constants.js +10 -0
  78. package/server/services/permission/engine.js +36 -226
  79. package/server/services/permission.js +4 -1
  80. package/server/strategies/admin.js +7 -1
  81. package/server/strategies/api-token.js +71 -11
  82. package/server/validation/api-tokens.js +12 -2
  83. package/build/2077.61cebc93.chunk.js +0 -195
  84. package/build/4982.c6f88c5d.chunk.js +0 -314
  85. package/build/admin-app.8bc3e80f.chunk.js +0 -112
  86. package/build/admin-edit-roles-page.554ba3fa.chunk.js +0 -1
  87. package/build/api-tokens-create-page.4c262d6e.chunk.js +0 -1
  88. package/build/api-tokens-edit-page.10a9d368.chunk.js +0 -1
  89. package/build/api-tokens-list-page.442c9f3c.chunk.js +0 -15
  90. package/build/en-json.12bc5a14.chunk.js +0 -1
  91. package/build/sso-settings-page.445184e0.chunk.js +0 -1
  92. package/server/services/permission/engine-hooks.js +0 -82
@@ -22,6 +22,7 @@ import {
22
22
  } from './exposedHooks';
23
23
  import injectionZones from './injectionZones';
24
24
  import favicon from './favicon.ico';
25
+ import localStorageKey from './components/LanguageProvider/utils/localStorageKey';
25
26
 
26
27
  class StrapiApp {
27
28
  constructor({ adminConfig, appPlugins, library, middlewares, reducers }) {
@@ -449,6 +450,7 @@ class StrapiApp {
449
450
  href: this.configurations.head.favicon,
450
451
  },
451
452
  ]}
453
+ htmlAttributes={{ lang: localStorage.getItem(localStorageKey) || 'en' }}
452
454
  />
453
455
  <BrowserRouter basename={basename}>
454
456
  <App store={store} />
@@ -65,11 +65,11 @@ const AuthenticatedApp = () => {
65
65
  if (userRoles) {
66
66
  const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
67
67
 
68
- if (isUserSuperAdmin) {
68
+ if (isUserSuperAdmin && appInfos?.autoReload) {
69
69
  setGuidedTourVisibilityRef.current(true);
70
70
  }
71
71
  }
72
- }, [userRoles]);
72
+ }, [userRoles, appInfos]);
73
73
 
74
74
  // We don't need to wait for the release query to be fetched before rendering the plugins
75
75
  // however, we need the appInfos and the permissions
@@ -21,6 +21,7 @@ const LanguageProvider = ({ children, localeNames, messages }) => {
21
21
  useEffect(() => {
22
22
  // Set user language in local storage.
23
23
  window.localStorage.setItem(localStorageKey, locale);
24
+ document.documentElement.setAttribute('lang', locale);
24
25
  }, [locale]);
25
26
 
26
27
  const changeLocale = (locale) => {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  */
6
6
 
7
- import React, { useState } from 'react';
7
+ import React, { useState, useMemo, useCallback } from 'react';
8
8
  import PropTypes from 'prop-types';
9
9
  import { ThemeToggleContext } from '../../contexts';
10
10
 
@@ -14,22 +14,33 @@ const getDefaultTheme = () => {
14
14
  const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
15
15
  const persistedTheme = localStorage.getItem(THEME_KEY);
16
16
 
17
+ if (!persistedTheme) {
18
+ localStorage.setItem(THEME_KEY, browserTheme);
19
+ }
20
+
17
21
  return persistedTheme || browserTheme;
18
22
  };
19
23
 
20
24
  const ThemeToggleProvider = ({ children, themes }) => {
21
25
  const [currentTheme, setCurrentTheme] = useState(getDefaultTheme());
22
26
 
23
- const handleChangeTheme = (nextTheme) => {
24
- setCurrentTheme(nextTheme);
25
- localStorage.setItem(THEME_KEY, nextTheme);
26
- };
27
-
28
- return (
29
- <ThemeToggleContext.Provider value={{ currentTheme, onChangeTheme: handleChangeTheme, themes }}>
30
- {children}
31
- </ThemeToggleContext.Provider>
27
+ const handleChangeTheme = useCallback(
28
+ (nextTheme) => {
29
+ setCurrentTheme(nextTheme);
30
+ localStorage.setItem(THEME_KEY, nextTheme);
31
+ },
32
+ [setCurrentTheme]
32
33
  );
34
+
35
+ const themeValues = useMemo(() => {
36
+ return {
37
+ currentTheme,
38
+ onChangeTheme: handleChangeTheme,
39
+ themes,
40
+ };
41
+ }, [currentTheme, handleChangeTheme, themes]);
42
+
43
+ return <ThemeToggleContext.Provider value={themeValues}>{children}</ThemeToggleContext.Provider>;
33
44
  };
34
45
 
35
46
  ThemeToggleProvider.propTypes = {
@@ -47,7 +47,7 @@ const DynamicTable = ({
47
47
  ...metadatas,
48
48
  label: formatMessage({
49
49
  id: getTrad(`containers.ListPage.table-headers.${header.name}`),
50
- defaultMessage: header.name,
50
+ defaultMessage: metadatas.label,
51
51
  }),
52
52
  },
53
53
  name: sortFieldValue,
@@ -60,7 +60,7 @@ const DynamicTable = ({
60
60
  ...metadatas,
61
61
  label: formatMessage({
62
62
  id: getTrad(`containers.ListPage.table-headers.${header.name}`),
63
- defaultMessage: header.name,
63
+ defaultMessage: metadatas.label,
64
64
  }),
65
65
  },
66
66
  };
@@ -79,6 +79,8 @@ class InputJSON extends React.Component {
79
79
  try {
80
80
  if (value === null) return this.codeMirror.setValue('');
81
81
 
82
+ if (value === 0) return this.codeMirror.setValue('0');
83
+
82
84
  return this.codeMirror.setValue(value);
83
85
  } catch (err) {
84
86
  return this.setState({ error: true });
@@ -0,0 +1,24 @@
1
+ import React, { createContext, useContext } from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const ApiTokenPermissionsContext = createContext({});
5
+
6
+ const ApiTokenPermissionsContextProvider = ({ children, ...rest }) => {
7
+ return (
8
+ <ApiTokenPermissionsContext.Provider value={rest}>
9
+ {children}
10
+ </ApiTokenPermissionsContext.Provider>
11
+ );
12
+ };
13
+
14
+ const useApiTokenPermissionsContext = () => useContext(ApiTokenPermissionsContext);
15
+
16
+ ApiTokenPermissionsContextProvider.propTypes = {
17
+ children: PropTypes.node.isRequired,
18
+ };
19
+
20
+ export {
21
+ ApiTokenPermissionsContext,
22
+ ApiTokenPermissionsContextProvider,
23
+ useApiTokenPermissionsContext,
24
+ };
@@ -9,3 +9,4 @@ export { default as useSettingsForm } from './useSettingsForm';
9
9
  export { default as usePermissionsDataManager } from './usePermissionsDataManager';
10
10
  export { default as useReleaseNotification } from './useReleaseNotification';
11
11
  export { default as useThemeToggle } from './useThemeToggle';
12
+ export { default as useRegenerate } from './useRegenerate';
@@ -0,0 +1,34 @@
1
+ import { useState } from 'react';
2
+ import { get } from 'lodash';
3
+ import { useNotification } from '@strapi/helper-plugin';
4
+ import { axiosInstance } from '../../core/utils';
5
+
6
+ const useRegenerate = (id, onRegenerate) => {
7
+ const [isLoadingConfirmation, setIsLoadingConfirmation] = useState(false);
8
+ const toggleNotification = useNotification();
9
+
10
+ const regenerateData = async () => {
11
+ try {
12
+ const {
13
+ data: {
14
+ data: { accessKey },
15
+ },
16
+ } = await axiosInstance.post(`/admin/api-tokens/${id}/regenerate`);
17
+ setIsLoadingConfirmation(false);
18
+ onRegenerate(accessKey);
19
+ } catch (error) {
20
+ setIsLoadingConfirmation(false);
21
+ toggleNotification({
22
+ type: 'warning',
23
+ message: get(error, 'response.data.message', 'notification.error'),
24
+ });
25
+ }
26
+ };
27
+
28
+ return {
29
+ regenerateData,
30
+ isLoadingConfirmation,
31
+ };
32
+ };
33
+
34
+ export default useRegenerate;
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { Typography } from '@strapi/design-system/Typography';
4
+ import { Stack } from '@strapi/design-system/Stack';
5
+ import { GridItem } from '@strapi/design-system/Grid';
6
+ import BoundRoute from '../BoundRoute';
7
+ import { useApiTokenPermissionsContext } from '../../../../../../../contexts/ApiTokenPermissions';
8
+
9
+ const ActionBoundRoutes = () => {
10
+ const {
11
+ value: { selectedAction, routes },
12
+ } = useApiTokenPermissionsContext();
13
+ const { formatMessage } = useIntl();
14
+ const actionSection = selectedAction?.split('.')[0];
15
+
16
+ return (
17
+ <GridItem
18
+ col={5}
19
+ background="neutral150"
20
+ paddingTop={6}
21
+ paddingBottom={6}
22
+ paddingLeft={7}
23
+ paddingRight={7}
24
+ style={{ minHeight: '100%' }}
25
+ >
26
+ {selectedAction ? (
27
+ <Stack spacing={2}>
28
+ {routes[actionSection]?.map((route) => {
29
+ return route.config.auth?.scope?.includes(selectedAction) ||
30
+ route.handler === selectedAction ? (
31
+ <BoundRoute key={route.handler} route={route} />
32
+ ) : null;
33
+ })}
34
+ </Stack>
35
+ ) : (
36
+ <Stack spacing={2}>
37
+ <Typography variant="delta" as="h3">
38
+ {formatMessage({
39
+ id: 'Settings.apiTokens.createPage.permissions.header.title',
40
+ defaultMessage: 'Advanced settings',
41
+ })}
42
+ </Typography>
43
+ <Typography as="p" textColor="neutral600">
44
+ {formatMessage({
45
+ id: 'Settings.apiTokens.createPage.permissions.header.hint',
46
+ defaultMessage:
47
+ "Select the application's actions or the plugin's actions and click on the cog icon to display the bound route",
48
+ })}
49
+ </Typography>
50
+ </Stack>
51
+ )}
52
+ </GridItem>
53
+ );
54
+ };
55
+
56
+ export default ActionBoundRoutes;
@@ -0,0 +1,41 @@
1
+ const getMethodColor = (verb) => {
2
+ switch (verb) {
3
+ case 'POST': {
4
+ return {
5
+ text: 'success600',
6
+ border: 'success200',
7
+ background: 'success100',
8
+ };
9
+ }
10
+ case 'GET': {
11
+ return {
12
+ text: 'secondary600',
13
+ border: 'secondary200',
14
+ background: 'secondary100',
15
+ };
16
+ }
17
+ case 'PUT': {
18
+ return {
19
+ text: 'warning600',
20
+ border: 'warning200',
21
+ background: 'warning100',
22
+ };
23
+ }
24
+ case 'DELETE': {
25
+ return {
26
+ text: 'danger600',
27
+ border: 'danger200',
28
+ background: 'danger100',
29
+ };
30
+ }
31
+ default: {
32
+ return {
33
+ text: 'neutral600',
34
+ border: 'neutral200',
35
+ background: 'neutral100',
36
+ };
37
+ }
38
+ }
39
+ };
40
+
41
+ export default getMethodColor;
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Stack } from '@strapi/design-system/Stack';
4
+ import { Box } from '@strapi/design-system/Box';
5
+ import { Typography } from '@strapi/design-system/Typography';
6
+ import map from 'lodash/map';
7
+ import tail from 'lodash/tail';
8
+ import { useIntl } from 'react-intl';
9
+ import PropTypes from 'prop-types';
10
+ import getMethodColor from './getMethodColor';
11
+
12
+ const MethodBox = styled(Box)`
13
+ margin: -1px;
14
+ border-radius: ${({ theme }) => theme.spaces[1]} 0 0 ${({ theme }) => theme.spaces[1]};
15
+ `;
16
+
17
+ function BoundRoute({ route }) {
18
+ const { formatMessage } = useIntl();
19
+
20
+ const { method, handler: title, path } = route;
21
+ const formattedRoute = path ? tail(path.split('/')) : [];
22
+ const [controller = '', action = ''] = title ? title.split('.') : [];
23
+ const colors = getMethodColor(route.method);
24
+
25
+ return (
26
+ <Stack spacing={2}>
27
+ <Typography variant="delta" as="h3">
28
+ {formatMessage({
29
+ id: 'Settings.apiTokens.createPage.BoundRoute.title',
30
+ defaultMessage: 'Bound route to',
31
+ })}
32
+ &nbsp;
33
+ <span>{controller}</span>
34
+ <Typography variant="delta" textColor="primary600">
35
+ .{action}
36
+ </Typography>
37
+ </Typography>
38
+ <Stack horizontal hasRadius background="neutral0" borderColor="neutral200" spacing={0}>
39
+ <MethodBox background={colors.background} borderColor={colors.border} padding={2}>
40
+ <Typography fontWeight="bold" textColor={colors.text}>
41
+ {method}
42
+ </Typography>
43
+ </MethodBox>
44
+ <Box paddingLeft={2} paddingRight={2}>
45
+ {map(formattedRoute, (value) => (
46
+ <Typography key={value} textColor={value.includes(':') ? 'neutral600' : 'neutral900'}>
47
+ /{value}
48
+ </Typography>
49
+ ))}
50
+ </Box>
51
+ </Stack>
52
+ </Stack>
53
+ );
54
+ }
55
+
56
+ BoundRoute.defaultProps = {
57
+ route: {
58
+ handler: 'Nocontroller.error',
59
+ method: 'GET',
60
+ path: '/there-is-no-path',
61
+ },
62
+ };
63
+
64
+ BoundRoute.propTypes = {
65
+ route: PropTypes.shape({
66
+ handler: PropTypes.string,
67
+ method: PropTypes.string,
68
+ path: PropTypes.string,
69
+ }),
70
+ };
71
+
72
+ export default BoundRoute;
@@ -0,0 +1,30 @@
1
+ import styled, { css } from 'styled-components';
2
+ import { Box } from '@strapi/design-system/Box';
3
+
4
+ const activeCheckboxWrapperStyles = css`
5
+ background: ${(props) => props.theme.colors.primary100};
6
+ svg {
7
+ opacity: 1;
8
+ }
9
+ `;
10
+
11
+ const CheckboxWrapper = styled(Box)`
12
+ display: flex;
13
+ justify-content: space-between;
14
+ align-items: center;
15
+
16
+ svg {
17
+ opacity: 0;
18
+ path {
19
+ fill: ${(props) => props.theme.colors.primary600};
20
+ }
21
+ }
22
+
23
+ /* Show active style both on hover and when the action is selected */
24
+ ${(props) => props.isActive && activeCheckboxWrapperStyles}
25
+ &:hover {
26
+ ${activeCheckboxWrapperStyles}
27
+ }
28
+ `;
29
+
30
+ export default CheckboxWrapper;
@@ -0,0 +1,150 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { capitalize } from 'lodash';
3
+ import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-system/Accordion';
4
+ import { Checkbox } from '@strapi/design-system/Checkbox';
5
+ import { Grid, GridItem } from '@strapi/design-system/Grid';
6
+ import { Typography } from '@strapi/design-system/Typography';
7
+ import { Box } from '@strapi/design-system/Box';
8
+ import { Flex } from '@strapi/design-system/Flex';
9
+ import CogIcon from '@strapi/icons/Cog';
10
+ import styled from 'styled-components';
11
+ import PropTypes from 'prop-types';
12
+ import { useApiTokenPermissionsContext } from '../../../../../../../contexts/ApiTokenPermissions';
13
+ import CheckboxWrapper from './CheckBoxWrapper';
14
+
15
+ const Border = styled.div`
16
+ flex: 1;
17
+ align-self: center;
18
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
19
+ `;
20
+
21
+ const CollapsableContentType = ({
22
+ controllers,
23
+ label,
24
+ orderNumber,
25
+ disabled,
26
+ onExpanded,
27
+ indexExpandendCollapsedContent,
28
+ }) => {
29
+ const {
30
+ value: { onChangeSelectAll, onChange, selectedActions, setSelectedAction, selectedAction },
31
+ } = useApiTokenPermissionsContext();
32
+ const [expanded, setExpanded] = useState(false);
33
+
34
+ const handleExpandedAccordion = () => {
35
+ setExpanded((s) => !s);
36
+ onExpanded(orderNumber);
37
+ };
38
+
39
+ useEffect(() => {
40
+ if (
41
+ indexExpandendCollapsedContent !== null &&
42
+ indexExpandendCollapsedContent !== orderNumber &&
43
+ expanded
44
+ ) {
45
+ setExpanded(false);
46
+ }
47
+ }, [indexExpandendCollapsedContent, orderNumber, expanded]);
48
+
49
+ const isActionSelected = (actionId) => actionId === selectedAction;
50
+
51
+ return (
52
+ <Accordion
53
+ expanded={expanded}
54
+ onToggle={handleExpandedAccordion}
55
+ variant={orderNumber % 2 ? 'primary' : 'secondary'}
56
+ >
57
+ <AccordionToggle title={capitalize(label)} />
58
+ <AccordionContent>
59
+ {controllers?.map((controller) => {
60
+ const allActionsSelected = controller.actions.every((action) =>
61
+ selectedActions.includes(action.actionId)
62
+ );
63
+
64
+ const someActionsSelected = controller.actions.some((action) =>
65
+ selectedActions.includes(action.actionId)
66
+ );
67
+
68
+ return (
69
+ <Box key={`${label}.${controller?.controller}`}>
70
+ <Flex justifyContent="space-between" alignItems="center" padding={4}>
71
+ <Box paddingRight={4}>
72
+ <Typography variant="sigma" textColor="neutral600">
73
+ {controller?.controller}
74
+ </Typography>
75
+ </Box>
76
+ <Border />
77
+ <Box paddingLeft={4}>
78
+ <Checkbox
79
+ value={allActionsSelected}
80
+ indeterminate={!allActionsSelected && someActionsSelected}
81
+ onValueChange={() => {
82
+ onChangeSelectAll({ target: { value: [...controller.actions] } });
83
+ }}
84
+ disabled={disabled}
85
+ >
86
+ Select all
87
+ </Checkbox>
88
+ </Box>
89
+ </Flex>
90
+ <Grid gap={4} padding={4}>
91
+ {controller?.actions &&
92
+ controller?.actions.map((action) => {
93
+ return (
94
+ <GridItem col={6} key={action.actionId}>
95
+ <CheckboxWrapper
96
+ isActive={isActionSelected(action.actionId)}
97
+ padding={2}
98
+ hasRadius
99
+ >
100
+ <Checkbox
101
+ value={selectedActions.includes(action.actionId)}
102
+ name={action.actionId}
103
+ onValueChange={() => {
104
+ onChange({ target: { value: action.actionId } });
105
+ }}
106
+ disabled={disabled}
107
+ >
108
+ {action.action}
109
+ </Checkbox>
110
+ <button
111
+ type="button"
112
+ data-testid="action-cog"
113
+ onClick={() =>
114
+ setSelectedAction({ target: { value: action.actionId } })
115
+ }
116
+ style={{ display: 'inline-flex', alignItems: 'center' }}
117
+ >
118
+ <CogIcon />
119
+ </button>
120
+ </CheckboxWrapper>
121
+ </GridItem>
122
+ );
123
+ })}
124
+ </Grid>
125
+ </Box>
126
+ );
127
+ })}
128
+ </AccordionContent>
129
+ </Accordion>
130
+ );
131
+ };
132
+
133
+ CollapsableContentType.defaultProps = {
134
+ controllers: [],
135
+ orderNumber: 0,
136
+ disabled: false,
137
+ onExpanded: () => null,
138
+ indexExpandendCollapsedContent: null,
139
+ };
140
+
141
+ CollapsableContentType.propTypes = {
142
+ controllers: PropTypes.array,
143
+ orderNumber: PropTypes.number,
144
+ label: PropTypes.string.isRequired,
145
+ disabled: PropTypes.bool,
146
+ onExpanded: PropTypes.func,
147
+ indexExpandendCollapsedContent: PropTypes.number,
148
+ };
149
+
150
+ export default CollapsableContentType;
@@ -0,0 +1,37 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Box } from '@strapi/design-system/Box';
4
+ import CollapsableContentType from '../CollapsableContentType';
5
+
6
+ const ContentTypesSection = ({ section, ...props }) => {
7
+ const [indexExpandedCollpsedContent, setIndexExpandedCollpsedContent] = useState(null);
8
+ const handleExpandedCollpsedContentIndex = (index) => setIndexExpandedCollpsedContent(index);
9
+
10
+ return (
11
+ <Box padding={4} background="neutral0">
12
+ {section &&
13
+ section.map((api, index) => (
14
+ <CollapsableContentType
15
+ key={api.apiId}
16
+ label={api.label}
17
+ controllers={api.controllers}
18
+ orderNumber={index}
19
+ indexExpandendCollapsedContent={indexExpandedCollpsedContent}
20
+ onExpanded={handleExpandedCollpsedContentIndex}
21
+ name={api.apiId}
22
+ {...props}
23
+ />
24
+ ))}
25
+ </Box>
26
+ );
27
+ };
28
+
29
+ ContentTypesSection.defaultProps = {
30
+ section: null,
31
+ };
32
+
33
+ ContentTypesSection.propTypes = {
34
+ section: PropTypes.arrayOf(PropTypes.object),
35
+ };
36
+
37
+ export default ContentTypesSection;