strapi-plugin-navigation 2.2.4 β†’ 2.2.6

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 (47) hide show
  1. package/README.md +36 -7
  2. package/admin/src/components/AdditionalFieldInput/index.js +2 -2
  3. package/admin/src/components/AdditionalFieldInput/types.d.ts +1 -0
  4. package/admin/src/components/Item/ItemCardHeader/icons.d.ts +1 -1
  5. package/admin/src/components/Item/ItemCardHeader/icons.js +2 -1
  6. package/admin/src/components/Item/ItemCardHeader/index.d.ts +1 -0
  7. package/admin/src/components/Item/ItemCardHeader/index.js +6 -6
  8. package/admin/src/components/Item/index.js +7 -6
  9. package/admin/src/components/NavigationItemList/index.d.ts +2 -1
  10. package/admin/src/components/NavigationItemList/index.js +2 -2
  11. package/admin/src/hooks/useNavigationManager.d.ts +0 -1
  12. package/admin/src/index.js +1 -1
  13. package/admin/src/pages/DataManagerProvider/index.js +14 -1
  14. package/admin/src/pages/DataManagerProvider/reducer.d.ts +1 -1
  15. package/admin/src/pages/NoAccessPage/index.d.ts +3 -0
  16. package/admin/src/pages/NoAccessPage/index.js +31 -0
  17. package/admin/src/pages/SettingsPage/index.d.ts +0 -1
  18. package/admin/src/pages/SettingsPage/index.js +9 -0
  19. package/admin/src/pages/View/components/NavigationHeader/index.d.ts +2 -1
  20. package/admin/src/pages/View/components/NavigationHeader/index.js +9 -8
  21. package/admin/src/pages/View/components/NavigationItemForm/index.js +14 -13
  22. package/admin/src/pages/View/components/NavigationItemForm/types.d.ts +2 -1
  23. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.d.ts +4 -2
  24. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.js +5 -1
  25. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.d.ts +2 -1
  26. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +6 -2
  27. package/admin/src/pages/View/components/NavigationItemPopup/index.d.ts +2 -1
  28. package/admin/src/pages/View/components/NavigationItemPopup/index.js +4 -3
  29. package/admin/src/pages/View/components/NavigationManager/AllNavigations/icons.d.ts +0 -1
  30. package/admin/src/pages/View/components/NavigationManager/AllNavigations/index.d.ts +0 -1
  31. package/admin/src/pages/View/components/NavigationManager/DeletionConfirm/index.d.ts +0 -1
  32. package/admin/src/pages/View/components/NavigationManager/ErrorDetails/index.d.ts +0 -1
  33. package/admin/src/pages/View/components/NavigationManager/Form/index.d.ts +0 -1
  34. package/admin/src/pages/View/components/NavigationManager/NavigationUpdate/index.d.ts +0 -1
  35. package/admin/src/pages/View/components/NavigationManager/NewNavigation/index.d.ts +0 -1
  36. package/admin/src/pages/View/components/NavigationManager/index.d.ts +0 -1
  37. package/admin/src/pages/View/index.js +26 -19
  38. package/admin/src/permissions.d.ts +4 -0
  39. package/admin/src/permissions.js +1 -0
  40. package/admin/src/translations/en.json +8 -0
  41. package/package.json +2 -2
  42. package/permissions.d.ts +1 -0
  43. package/permissions.js +1 -0
  44. package/server/bootstrap/index.js +6 -0
  45. package/server/routes/admin.js +99 -2
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/types/utils.d.ts +5 -0
package/README.md CHANGED
@@ -45,12 +45,14 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
45
45
  - [Plugin file](#in-v202-and-older--default-configuration-state-for-v203-and-newer)
46
46
  5. [πŸ”§ GraphQL Configuration](#-gql-configuration)
47
47
  6. [🌍 i18n Internationalization](#-i18n-internationalization)
48
- 7. [πŸ•ΈοΈ Public API specification](#%EF%B8%8F-public-api-specification)
48
+ 7. [πŸ‘€ RBAC](#-rbac)
49
+ 8. [πŸ” Authorization strategy](#-authorization-strategy)
50
+ 9. [πŸ•ΈοΈ Public API specification](#%EF%B8%8F-public-api-specification)
49
51
  - [REST API](#rest-api)
50
52
  - [GraphQL API](#graphql-api)
51
- 8. [πŸ’¬ FAQ](#-faq)
52
- 10. [🀝 Contributing](#-contributing)
53
- 11. [πŸ‘¨β€πŸ’» Community support](#-community-support)
53
+ 10. [πŸ’¬ FAQ](#-faq)
54
+ 11. [🀝 Contributing](#-contributing)
55
+ 12. [πŸ‘¨β€πŸ’» Community support](#-community-support)
54
56
 
55
57
  ## ✨ Features
56
58
 
@@ -118,7 +120,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
118
120
 
119
121
  **Supported Strapi versions**:
120
122
 
121
- - Strapi v4.6.0 (recently tested)
123
+ - Strapi v4.9.0 (recently tested)
122
124
  - Strapi v4.x
123
125
 
124
126
  > This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation/tree/strapi-v3).
@@ -163,6 +165,9 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
163
165
  contentTypesNameFields: {
164
166
  'api::page.page': ['title']
165
167
  },
168
+ pathDefaultFields: {
169
+ 'api::page.page': ['slug']
170
+ },
166
171
  allowedLevels: 2,
167
172
  gql: {...},
168
173
  }
@@ -175,6 +180,7 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
175
180
  - `allowedLevels` - Maximum level for which you're able to mark item as "Menu attached"
176
181
  - `contentTypes` - UIDs of related content types
177
182
  - `contentTypesNameFields` - Definition of content type title fields like `'api::<collection name>.<content type name>': ['field_name_1', 'field_name_2']`, if not set titles are pulled from fields like `['title', 'subject', 'name']`. **TIP** - Proper content type uid you can find in the URL of Content Manager where you're managing relevant entities like: `admin/content-manager/collectionType/< THE UID HERE >?page=1&pageSize=10&sort=Title:ASC&plugins[i18n][locale]=en`
183
+ - `pathDefaultFields` - The attribute to copy the default path from per content type. Syntax: `'api::<collection name>.<content type name>': ['url_slug', 'path']`
178
184
  - `gql` - If you're using GraphQL that's the right place to put all necessary settings. More **[ here ](#gql-configuration)**
179
185
  - `i18nEnabled` - should you want to manage multi-locale content via navigation set this value `Enabled`. More **[ here ](#i18n-internationalization)**
180
186
  - `cascadeMenuAttached` - If you don't want "Menu attached" to cascade on child items set this value `Disabled`.
@@ -249,12 +255,35 @@ Of course if you know that `fr` version is present at id `2` you can just query
249
255
  If feature is enabled GQL render navigation query is expanded to handle `locale` param(it will work the same as regular requests). Checkout schema provided by GraphQL plugin.
250
256
 
251
257
  ## πŸ‘€ RBAC
252
- Plugin provides granular permissions based on Strapi RBAC functionality.
258
+ Plugin provides granular permissions based on **Strapi RBAC** functionality within the editorial interface &amp; **Admin API**. Those settings are editable via the _Setings_ -> _Administration Panel_ -> _Roles_.
253
259
 
254
- ### Mandatory permissions
255
260
  For any role different than **Super Admin**, to access the **Navigation panel** you must set following permissions:
261
+
262
+ ### Mandatory permissions
256
263
  - _Plugins_ -> _Navigation_ -> _Read_ - gives you the access to **Navigation Panel**
257
264
 
265
+ ### Other permissions
266
+ - _Plugins_ -> _Navigation_ -> _Update_ - with this permission user is able to change Navigation structure
267
+ - _Plugins_ -> _Navigation_ -> _Settings_ - special permission for users that should be able to change plugin settings
268
+
269
+ ## πŸ” Authorization strategy
270
+ Is applied for **Public API** both for REST and GraphQL. You can manage is by two different ways. Those settings are editable via the _Setings_ -> _Users &amp; Permissions Plugin_ -> _Roles_.
271
+
272
+ ## User based
273
+ - _Public_ - as per description it's default role for any not authenticated user. By enabling **Public API** of the plugin here you're making it **fully public**, without **any permissions check**.
274
+ - _Authenticated_ - as per description this is default role for Strapi Users. If you enable **Public API** here, for any call made you must use the User authentication token as `Bearer <token>`.
275
+
276
+ ## Token based
277
+ - _Full Access_ - gives full access to every Strapi Content API including our plugin endpoints as well.
278
+ - _Custom_ - granural access management to every Strapi Content API endpoints as well as plugin **Public API** - _(recomended approach)_
279
+
280
+ > _Note: Token usage &amp Read-Only tokens_
281
+ > If you're aiming to use token based approach, for every call you must provide proper token in headers as `Bearer <token>`.
282
+ >
283
+ > Important: As the Read-Only tokens are dedicated to support just `find` and `findAll` endpoints from Strapi Content API, they are not covering access to plugin **Public API** `render` and `renderChild` endpoints. We recommend to use the `Custom` token type for fully granural and secured approach instead of `Full Access` ones.
284
+ >
285
+ > Reference: [Strapi - API Tokens](https://docs.strapi.io/dev-docs/configurations/api-tokens#usage)
286
+
258
287
  ## Base Navigation Item model
259
288
 
260
289
  ### Flat
@@ -36,14 +36,14 @@ const DEFAULT_STRING_VALUE = "";
36
36
  const handlerFactory = ({ field, prop, onChange }) => ({ target }) => {
37
37
  onChange(field.name, target[prop]);
38
38
  };
39
- const AdditionalFieldInput = ({ field, isLoading, onChange, value, error }) => {
39
+ const AdditionalFieldInput = ({ field, isLoading, onChange, value, disabled, error }) => {
40
40
  const toggleNotification = (0, helper_plugin_1.useNotification)();
41
41
  const { formatMessage } = (0, react_intl_1.useIntl)();
42
42
  const defaultInputProps = (0, react_1.useMemo)(() => ({
43
43
  id: field.name,
44
44
  name: field.name,
45
45
  label: field.label,
46
- disabled: isLoading,
46
+ disabled: isLoading || disabled,
47
47
  error: error && formatMessage(error),
48
48
  }), [field, isLoading, error]);
49
49
  const handleBoolean = (0, react_1.useMemo)(() => handlerFactory({ field, onChange, prop: "checked" }), [onChange, field]);
@@ -5,6 +5,7 @@ export declare type AdditionalFieldInputProps = {
5
5
  isLoading: boolean;
6
6
  onChange: (name: string, value: string) => void;
7
7
  value: string | boolean | string[] | null;
8
+ disabled: boolean;
8
9
  error: MessageDescriptor | null;
9
10
  };
10
11
  export declare type TargetProp = "value" | "checked";
@@ -1,5 +1,5 @@
1
- /// <reference types="react" />
2
1
  export declare const pencilIcon: JSX.Element;
3
2
  export declare const refreshIcon: JSX.Element;
4
3
  export declare const trashIcon: JSX.Element;
4
+ export declare const eyeIcon: JSX.Element;
5
5
  //# sourceMappingURL=icons.d.ts.map
@@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.trashIcon = exports.refreshIcon = exports.pencilIcon = void 0;
6
+ exports.eyeIcon = exports.trashIcon = exports.refreshIcon = exports.pencilIcon = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const icons_1 = require("@strapi/icons");
9
9
  exports.pencilIcon = react_1.default.createElement(icons_1.Pencil, null);
10
10
  exports.refreshIcon = react_1.default.createElement(icons_1.Refresh, null);
11
11
  exports.trashIcon = react_1.default.createElement(icons_1.Trash, null);
12
+ exports.eyeIcon = react_1.default.createElement(icons_1.Eye, null);
12
13
  //# sourceMappingURL=icons.js.map
@@ -5,6 +5,7 @@ interface IProps {
5
5
  path: string;
6
6
  icon: ToBeFixed;
7
7
  removed: boolean;
8
+ canUpdate: boolean;
8
9
  onItemRemove: VoidEffect;
9
10
  onItemEdit: VoidEffect;
10
11
  onItemRestore: VoidEffect;
@@ -15,9 +15,9 @@ const utils_1 = require("../../../utils");
15
15
  const icons_1 = require("./icons");
16
16
  const wrapperStyle = { zIndex: 2 };
17
17
  const pathWrapperStyle = { maxWidth: "425px" };
18
- const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (react_1.default.createElement(Wrapper_1.default, null,
18
+ const ItemCardHeader = ({ title, path, icon, removed, canUpdate, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (react_1.default.createElement(Wrapper_1.default, null,
19
19
  react_1.default.createElement(Flex_1.Flex, { alignItems: "center" },
20
- react_1.default.createElement(DragButton_1.default, { ref: dragRef }),
20
+ canUpdate && (react_1.default.createElement(DragButton_1.default, { ref: dragRef })),
21
21
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold" }, title),
22
22
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold", textColor: 'neutral500', ellipsis: true, style: pathWrapperStyle }, path),
23
23
  react_1.default.createElement(Flex_1.Flex, null,
@@ -25,9 +25,9 @@ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit,
25
25
  react_1.default.createElement(Flex_1.Flex, { alignItems: "center", style: wrapperStyle },
26
26
  removed &&
27
27
  (react_1.default.createElement(ItemCardBadge_1.default, { borderColor: "danger200", backgroundColor: "danger100", textColor: "danger600" }, (0, utils_1.getMessage)("components.navigationItem.badge.removed"))),
28
- react_1.default.createElement(IconButton_1.IconButton, { disabled: removed, onClick: onItemEdit, label: "Edit", icon: icons_1.pencilIcon }),
29
- removed ?
30
- react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRestore, label: "Restore", icon: icons_1.refreshIcon }) :
31
- react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRemove, label: "Remove", icon: icons_1.trashIcon }))));
28
+ react_1.default.createElement(IconButton_1.IconButton, { disabled: removed, onClick: onItemEdit, label: (0, utils_1.getMessage)(`components.navigationItem.action.${canUpdate ? 'edit' : 'view'}`, canUpdate ? 'Edit' : 'View'), icon: canUpdate ? icons_1.pencilIcon : icons_1.eyeIcon }),
29
+ canUpdate && (react_1.default.createElement(react_1.default.Fragment, null, removed ?
30
+ react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRestore, label: (0, utils_1.getMessage)('components.navigationItem.action.restore', "Restore"), icon: icons_1.refreshIcon }) :
31
+ react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRemove, label: (0, utils_1.getMessage)('components.navigationItem.action.remove', "Remove"), icon: icons_1.trashIcon }))))));
32
32
  exports.default = ItemCardHeader;
33
33
  //# sourceMappingURL=index.js.map
@@ -46,7 +46,7 @@ const ItemCardRemovedOverlay_1 = require("./ItemCardRemovedOverlay");
46
46
  const utils_1 = require("../../utils");
47
47
  const CollapseButton_1 = __importDefault(require("../CollapseButton"));
48
48
  const Item = (props) => {
49
- const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, } = props;
49
+ const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, permissions = {}, } = props;
50
50
  const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, structureId, items = [], } = item;
51
51
  const { contentTypes = [], contentTypesNameFields } = config;
52
52
  const isExternal = type === utils_1.navigationItemType.EXTERNAL;
@@ -60,6 +60,7 @@ const Item = (props) => {
60
60
  const relatedItemLabel = !isExternal ? (0, parsers_1.extractRelatedItemLabel)(relatedRef, contentTypesNameFields, { contentTypes }) : '';
61
61
  const relatedTypeLabel = relatedRef?.labelSingular;
62
62
  const relatedBadgeColor = isPublished ? 'success' : 'secondary';
63
+ const { canUpdate } = permissions;
63
64
  const dragRef = (0, react_1.useRef)(null);
64
65
  const dropRef = (0, react_1.useRef)(null);
65
66
  const previewRef = (0, react_1.useRef)(null);
@@ -110,7 +111,7 @@ const Item = (props) => {
110
111
  const { isSingle } = contentType;
111
112
  return `/content-manager/${isSingle ? 'singleType' : 'collectionType'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}`;
112
113
  };
113
- const onNewItemClick = (0, react_1.useCallback)((event) => onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items]);
114
+ const onNewItemClick = (0, react_1.useCallback)((event) => canUpdate && onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items, canUpdate]);
114
115
  return (react_1.default.createElement(Wrapper_1.default, { level: level, isLast: isLast, style: { opacity: isDragging ? 0.2 : 1 }, ref: refs ? refs.dropRef : null },
115
116
  react_1.default.createElement(Card_1.Card, { style: { width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' } },
116
117
  removed && (react_1.default.createElement(ItemCardRemovedOverlay_1.ItemCardRemovedOverlay, null)),
@@ -120,14 +121,14 @@ const Item = (props) => {
120
121
  ...item,
121
122
  isMenuAllowedLevel,
122
123
  isParentAttachedToMenu,
123
- }, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed })),
124
+ }, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed, canUpdate: canUpdate })),
124
125
  react_1.default.createElement(Divider_1.Divider, null),
125
126
  !isExternal && (react_1.default.createElement(Card_1.CardBody, { style: { padding: '8px' } },
126
127
  react_1.default.createElement(Flex_1.Flex, { style: { width: '100%' }, direction: "row", alignItems: "center", justifyContent: "space-between" },
127
128
  react_1.default.createElement(Flex_1.Flex, null,
128
129
  !(0, lodash_1.isEmpty)(item.items) && react_1.default.createElement(CollapseButton_1.default, { toggle: () => onItemToggleCollapse(item), collapsed: collapsed, itemsCount: item.items.length }),
129
- react_1.default.createElement(TextButton_1.TextButton, { disabled: removed, startIcon: react_1.default.createElement(icons_1.Plus, null), onClick: onNewItemClick },
130
- react_1.default.createElement(Typography_1.Typography, { variant: "pi", fontWeight: "bold", textColor: removed ? "neutral600" : "primary600" }, (0, utils_1.getMessage)("components.navigationItem.action.newItem")))),
130
+ canUpdate && (react_1.default.createElement(TextButton_1.TextButton, { disabled: removed, startIcon: react_1.default.createElement(icons_1.Plus, null), onClick: onNewItemClick },
131
+ react_1.default.createElement(Typography_1.Typography, { variant: "pi", fontWeight: "bold", textColor: removed ? "neutral600" : "primary600" }, (0, utils_1.getMessage)("components.navigationItem.action.newItem"))))),
131
132
  relatedItemLabel && (react_1.default.createElement(Flex_1.Flex, { justifyContent: 'center', alignItems: 'center' },
132
133
  isHandledByPublishFlow && (react_1.default.createElement(ItemCardBadge_1.default, { borderColor: `${relatedBadgeColor}200`, backgroundColor: `${relatedBadgeColor}100`, textColor: `${relatedBadgeColor}600`, className: "action", small: true }, (0, utils_1.getMessage)({ id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}` }))),
133
134
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", textColor: 'neutral600' },
@@ -135,7 +136,7 @@ const Item = (props) => {
135
136
  "\u00A0/\u00A0"),
136
137
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", textColor: 'neutral800' }, relatedItemLabel),
137
138
  react_1.default.createElement(Link_1.Link, { to: `/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`, endIcon: react_1.default.createElement(icons_1.ArrowRight, null) }, "\u00A0")))))))),
138
- hasChildren && !removed && !collapsed && react_1.default.createElement(NavigationItemList_1.default, { onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemRestore: onItemRestore, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, allowedLevels: allowedLevels, isParentAttachedToMenu: menuAttached, items: item.items, level: level + 1, levelPath: absolutePath, contentTypes: contentTypes, contentTypesNameFields: contentTypesNameFields })));
139
+ hasChildren && !removed && !collapsed && react_1.default.createElement(NavigationItemList_1.default, { onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemRestore: onItemRestore, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, allowedLevels: allowedLevels, isParentAttachedToMenu: menuAttached, items: item.items, level: level + 1, levelPath: absolutePath, contentTypes: contentTypes, contentTypesNameFields: contentTypesNameFields, permissions: permissions })));
139
140
  };
140
141
  Item.propTypes = {
141
142
  item: prop_types_1.default.shape({
@@ -1,5 +1,5 @@
1
1
  export default List;
2
- declare function List({ allowedLevels, error, isParentAttachedToMenu, items, level, levelPath, onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, }: {
2
+ declare function List({ allowedLevels, error, isParentAttachedToMenu, items, level, levelPath, onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, permissions, }: {
3
3
  allowedLevels: any;
4
4
  error: any;
5
5
  isParentAttachedToMenu?: boolean | undefined;
@@ -15,6 +15,7 @@ declare function List({ allowedLevels, error, isParentAttachedToMenu, items, lev
15
15
  displayFlat: any;
16
16
  contentTypes: any;
17
17
  contentTypesNameFields: any;
18
+ permissions: any;
18
19
  }): JSX.Element;
19
20
  declare namespace List {
20
21
  namespace propTypes {
@@ -7,12 +7,12 @@ const react_1 = __importDefault(require("react"));
7
7
  const prop_types_1 = __importDefault(require("prop-types"));
8
8
  const Item_1 = __importDefault(require("../Item"));
9
9
  const Wrapper_1 = __importDefault(require("./Wrapper"));
10
- const List = ({ allowedLevels, error, isParentAttachedToMenu = false, items, level = 0, levelPath = '', onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, }) => (react_1.default.createElement(Wrapper_1.default, { level: level }, items.map((item, n) => {
10
+ const List = ({ allowedLevels, error, isParentAttachedToMenu = false, items, level = 0, levelPath = '', onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, permissions, }) => (react_1.default.createElement(Wrapper_1.default, { level: level }, items.map((item, n) => {
11
11
  const { relatedRef, ...itemProps } = item;
12
12
  return (react_1.default.createElement(Item_1.default, { key: `list-item-${item.viewId || n}`, item: itemProps, isLast: n === items.length - 1, relatedRef: relatedRef, level: level, levelPath: levelPath, isParentAttachedToMenu: isParentAttachedToMenu, allowedLevels: allowedLevels, onItemRestore: onItemRestore, onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, displayChildren: displayFlat, config: {
13
13
  contentTypes,
14
14
  contentTypesNameFields
15
- } }));
15
+ }, permissions: permissions }));
16
16
  })));
17
17
  List.propTypes = {
18
18
  allowedLevels: prop_types_1.default.number,
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  export declare const useNavigationManager: () => {
3
2
  navigationManagerModal: JSX.Element | null;
4
3
  openNavigationManagerModal: () => void;
@@ -50,7 +50,7 @@ exports.default = {
50
50
  const component = await Promise.resolve().then(() => __importStar(require('./pages/SettingsPage')));
51
51
  return component;
52
52
  },
53
- permissions: permissions_1.default.access,
53
+ permissions: permissions_1.default.settings,
54
54
  }
55
55
  ]);
56
56
  app.addMenuLink({
@@ -40,6 +40,8 @@ const reducer_1 = __importStar(require("./reducer"));
40
40
  const actions_1 = require("./actions");
41
41
  const parsers_1 = require("../View/utils/parsers");
42
42
  const utils_1 = require("../../utils");
43
+ const NoAccessPage_1 = __importDefault(require("../NoAccessPage"));
44
+ const permissions_1 = __importDefault(require("../../permissions"));
43
45
  const i18nAwareItems = ({ items, config }) => config.i18nEnabled ? items.filter(({ localeCode }) => localeCode === config.defaultLocale) : items;
44
46
  const DataManagerProvider = ({ children }) => {
45
47
  const [reducerState, dispatch] = (0, react_1.useReducer)(reducer_1.default, reducer_1.initialState, init_1.default);
@@ -50,6 +52,11 @@ const DataManagerProvider = ({ children }) => {
50
52
  const { pathname } = (0, react_router_dom_1.useLocation)();
51
53
  const formatMessageRef = (0, react_1.useRef)();
52
54
  formatMessageRef.current = formatMessage;
55
+ const viewPermissions = (0, react_1.useMemo)(() => ({
56
+ access: permissions_1.default.access || permissions_1.default.update,
57
+ update: permissions_1.default.update,
58
+ }), []);
59
+ const { isLoading: isLoadingForPermissions, allowedActions: { canAccess, canUpdate, }, } = (0, helper_plugin_1.useRBAC)(viewPermissions);
53
60
  const getLayoutSettingRef = (0, react_1.useRef)();
54
61
  getLayoutSettingRef.current = (settingName) => (0, lodash_1.get)({}, ["settings", settingName], "");
55
62
  const isInDevelopmentMode = autoReload;
@@ -289,6 +296,9 @@ const DataManagerProvider = ({ children }) => {
289
296
  });
290
297
  const slugify = (query) => (0, helper_plugin_1.request)(`/${pluginId_1.default}/slug?q=${query}`, { method: "GET", signal });
291
298
  const hardReset = () => getDataRef.current();
299
+ if (!canAccess && !isLoadingForPermissions) {
300
+ return (react_1.default.createElement(NoAccessPage_1.default, null));
301
+ }
292
302
  return (react_1.default.createElement(DataManagerContext_1.default.Provider, { value: {
293
303
  items: passedActiveItems,
294
304
  activeItem,
@@ -318,7 +328,10 @@ const DataManagerProvider = ({ children }) => {
318
328
  handleNavigationsDeletion,
319
329
  hardReset,
320
330
  slugify,
321
- } }, isLoading ? react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null) : children));
331
+ permissions: {
332
+ canAccess, canUpdate
333
+ },
334
+ } }, isLoading || isLoadingForPermissions ? react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null) : children));
322
335
  };
323
336
  DataManagerProvider.propTypes = {
324
337
  children: prop_types_1.default.node.isRequired,
@@ -1,5 +1,5 @@
1
1
  export default reducer;
2
- declare function reducer(state: any, action: any): any;
2
+ declare function reducer(state: any, action: any): (base?: ((draftState: any) => any) | undefined, ...args: unknown[]) => ((draftState: any) => any) | Promise<(draftState: any) => any>;
3
3
  export namespace initialState {
4
4
  const items: never[];
5
5
  const activeItem: undefined;
@@ -0,0 +1,3 @@
1
+ declare const NoAcccessPage: () => JSX.Element;
2
+ export default NoAcccessPage;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const helper_plugin_1 = require("@strapi/helper-plugin");
8
+ const Main_1 = require("@strapi/design-system/Main");
9
+ const Layout_1 = require("@strapi/design-system/Layout");
10
+ const EmptyStateLayout_1 = require("@strapi/design-system/EmptyStateLayout");
11
+ const icons_1 = require("@strapi/icons");
12
+ const react_intl_1 = require("react-intl");
13
+ const NoAcccessPage = () => {
14
+ const { formatMessage } = (0, react_intl_1.useIntl)();
15
+ (0, helper_plugin_1.useFocusWhenNavigate)();
16
+ return (react_1.default.createElement(Main_1.Main, { labelledBy: "title" },
17
+ react_1.default.createElement(Layout_1.HeaderLayout, { id: "title", title: formatMessage({
18
+ id: 'page.auth.noAccess',
19
+ defaultMessage: 'No access',
20
+ }) }),
21
+ react_1.default.createElement(Layout_1.ContentLayout, null,
22
+ react_1.default.createElement(EmptyStateLayout_1.EmptyStateLayout, { action: react_1.default.createElement(helper_plugin_1.LinkButton, { variant: "secondary", endIcon: react_1.default.createElement(icons_1.ArrowRight, null), to: "/" }, formatMessage({
23
+ id: 'components.notAccessPage.back',
24
+ defaultMessage: 'Back to homepage',
25
+ })), content: formatMessage({
26
+ id: 'page.auth.not.allowed',
27
+ defaultMessage: "Oops! It seems like You do not have access to this page...",
28
+ }), hasRadius: true, icon: react_1.default.createElement(icons_1.EmptyPictures, { width: "10rem" }), shadow: "tableShadow" }))));
29
+ };
30
+ exports.default = NoAcccessPage;
31
+ //# sourceMappingURL=index.js.map
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const SettingsPage: () => JSX.Element;
3
2
  export default SettingsPage;
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -55,6 +55,8 @@ const styles_1 = require("../../components/Alert/styles");
55
55
  const DisableI18nModal_1 = require("./components/DisableI18nModal");
56
56
  const CustomFieldModal_1 = __importDefault(require("./components/CustomFieldModal"));
57
57
  const CustomFieldTable_1 = __importDefault(require("./components/CustomFieldTable"));
58
+ const permissions_2 = __importDefault(require("../../permissions"));
59
+ const NoAccessPage_1 = __importDefault(require("../NoAccessPage"));
58
60
  const RESTART_NOT_REQUIRED = { required: false };
59
61
  const RESTART_REQUIRED = { required: true, reasons: [] };
60
62
  const RELATION_ATTRIBUTE_TYPES = ['relation', 'media', 'component'];
@@ -77,6 +79,10 @@ const SettingsPage = () => {
77
79
  const [contentTypeExpanded, setContentTypeExpanded] = (0, react_1.useState)(undefined);
78
80
  const { data: navigationConfigData, isLoading: isConfigLoading, error: configErr, submitMutation, restoreMutation, restartMutation } = (0, useNavigationConfig_1.default)();
79
81
  const { data: allContentTypesData, isLoading: isContentTypesLoading, error: contentTypesErr } = (0, useAllContentTypes_1.default)();
82
+ const viewPermissions = (0, react_1.useMemo)(() => ({
83
+ settings: permissions_2.default.settings
84
+ }), []);
85
+ const { isLoading: isLoadingForPermissions, allowedActions: { canSettings: canManageSettings, }, } = (0, helper_plugin_1.useRBAC)(viewPermissions);
80
86
  const isLoading = isConfigLoading || isContentTypesLoading;
81
87
  const isError = configErr || contentTypesErr;
82
88
  const configContentTypes = navigationConfigData?.contentTypes || [];
@@ -155,6 +161,9 @@ const SettingsPage = () => {
155
161
  };
156
162
  const handleRestartDiscard = () => setRestartStatus(RESTART_NOT_REQUIRED);
157
163
  const handleSetContentTypeExpanded = key => setContentTypeExpanded(key === contentTypeExpanded ? undefined : key);
164
+ if (!(isLoadingForPermissions || canManageSettings)) {
165
+ return (react_1.default.createElement(NoAccessPage_1.default, null));
166
+ }
158
167
  if (isLoading || isError) {
159
168
  return (react_1.default.createElement(react_1.default.Fragment, null,
160
169
  react_1.default.createElement(helper_plugin_1.SettingsPageTitle, { name: (0, utils_2.getMessage)('Settings.email.plugin.title', 'Configuration') }),
@@ -1,5 +1,5 @@
1
1
  export default NavigationHeader;
2
- declare function NavigationHeader({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, }: {
2
+ declare function NavigationHeader({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, permissions, }: {
3
3
  activeNavigation: any;
4
4
  availableNavigations: any;
5
5
  structureHasErrors: any;
@@ -8,5 +8,6 @@ declare function NavigationHeader({ activeNavigation, availableNavigations, stru
8
8
  handleLocalizationSelection: any;
9
9
  handleSave: any;
10
10
  config: any;
11
+ permissions?: {} | undefined;
11
12
  }): JSX.Element;
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -46,7 +46,7 @@ const pickDefaultLocaleNavigation = ({ activeNavigation, config }) => config.i18
46
46
  : activeNavigation?.localizations.find(({ localeCode }) => localeCode === config.defaultLocale)
47
47
  : null
48
48
  : activeNavigation;
49
- const NavigationHeader = ({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, }) => {
49
+ const NavigationHeader = ({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, permissions = {}, }) => {
50
50
  const { formatMessage } = (0, react_intl_1.useIntl)();
51
51
  const allLocaleVersions = (0, react_1.useMemo)(() => activeNavigation?.localizations.length && config.i18nEnabled
52
52
  ? (0, lodash_1.uniqBy)([activeNavigation, ...(activeNavigation.localizations ?? [])].sort((a, b) => a.localeCode.localeCompare(b.localeCode)), 'id')
@@ -54,21 +54,22 @@ const NavigationHeader = ({ activeNavigation, availableNavigations, structureHas
54
54
  const hasLocalizations = config.i18nEnabled && allLocaleVersions.length;
55
55
  const passedActiveNavigation = pickDefaultLocaleNavigation({ activeNavigation, config });
56
56
  const { closeNavigationManagerModal, openNavigationManagerModal, navigationManagerModal } = (0, useNavigationManager_1.useNavigationManager)();
57
+ const { canUpdate } = permissions;
57
58
  return (react_1.default.createElement(Layout_1.HeaderLayout, { primaryAction: react_1.default.createElement(Stack_1.Stack, { horizontal: true, size: 2 },
58
- react_1.default.createElement(Box_1.Box, { width: "27vw", marginRight: "8px" },
59
+ react_1.default.createElement(Box_1.Box, { marginRight: "8px" },
59
60
  react_1.default.createElement(Grid_1.Grid, { gap: 4 },
60
61
  !hasLocalizations ? (react_1.default.createElement(Grid_1.GridItem, { col: 2 })) : null,
61
- react_1.default.createElement(Grid_1.GridItem, { col: 3 },
62
- react_1.default.createElement(Button_1.Button, { onClick: openNavigationManagerModal, startIcon: null, type: "button", variant: "secondary", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('header.action.manage')))),
63
- react_1.default.createElement(Grid_1.GridItem, { col: 4 },
62
+ canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 3 },
63
+ react_1.default.createElement(Button_1.Button, { onClick: openNavigationManagerModal, startIcon: null, type: "button", variant: "secondary", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('header.action.manage'))))),
64
+ react_1.default.createElement(Grid_1.GridItem, { col: canUpdate ? 4 : 10 },
64
65
  react_1.default.createElement(Select_1.Select, { type: "select", placeholder: "Change navigation", name: "navigationSelect", onChange: handleChangeSelection, value: passedActiveNavigation?.id, size: "S", style: null }, availableNavigations.map(({ id, name }) => react_1.default.createElement(Select_1.Option, { key: id, value: id }, name)))),
65
66
  hasLocalizations
66
67
  ? react_1.default.createElement(Grid_1.GridItem, { col: 2 },
67
68
  react_1.default.createElement(Select_1.Select, { type: "select", placeholder: formatMessage((0, translations_1.getTrad)('pages.main.header.localization.select.placeholder')), name: "navigationLocalizationSelect", onChange: handleLocalizationSelection, value: activeNavigation?.id, size: "S" }, allLocaleVersions.map(({ id, localeCode }) => react_1.default.createElement(Select_1.Option, { key: id, value: id }, localeCode))))
68
69
  : null,
69
- react_1.default.createElement(Grid_1.GridItem, { col: 3 },
70
- react_1.default.createElement(Button_1.Button, { onClick: handleSave, startIcon: submitIcon, disabled: structureHasErrors || !structureHasChanged, type: "submit", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('submit.cta.save')))))),
71
- navigationManagerModal), title: formatMessage({
70
+ canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 3 },
71
+ react_1.default.createElement(Button_1.Button, { onClick: handleSave, startIcon: submitIcon, disabled: structureHasErrors || !structureHasChanged, type: "submit", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('submit.cta.save'))))))),
72
+ canUpdate && navigationManagerModal), title: formatMessage({
72
73
  id: (0, translations_1.getTrad)('header.title'),
73
74
  defaultMessage: 'UI Navigation',
74
75
  }), subtitle: formatMessage({
@@ -44,12 +44,13 @@ const translations_1 = require("../../../../translations");
44
44
  const types_1 = require("../../../../../../types");
45
45
  const AdditionalFieldInput_1 = __importDefault(require("../../../../components/AdditionalFieldInput"));
46
46
  const utils_2 = require("../../../../utils");
47
- const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = appendLabelPublicationStatusFallback, locale, readNavigationItemFromLocale, slugify, }) => {
47
+ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = appendLabelPublicationStatusFallback, locale, readNavigationItemFromLocale, slugify, permissions = {}, }) => {
48
48
  const [isLoading, setIsLoading] = (0, react_1.useState)(isPreloading);
49
49
  const [hasBeenInitialized, setInitializedState] = (0, react_1.useState)(false);
50
50
  const [hasChanged, setChangedState] = (0, react_1.useState)(false);
51
51
  const [contentTypeSearchQuery, setContentTypeSearchQuery] = (0, react_1.useState)(undefined);
52
52
  const [contentTypeSearchInputValue, setContentTypeSearchInputValue] = (0, react_1.useState)(undefined);
53
+ const { canUpdate } = permissions;
53
54
  const formik = (0, formik_1.useFormik)({
54
55
  initialValues: formDefinition.defaultValues,
55
56
  onSubmit: loadingAware(async (payload) => onSubmit(await sanitizePayload(slugify, payload, data)), setIsLoading),
@@ -380,20 +381,20 @@ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading,
380
381
  react_1.default.createElement(ModalLayout_1.ModalBody, null,
381
382
  react_1.default.createElement(Grid_1.Grid, { gap: 5 },
382
383
  react_1.default.createElement(Grid_1.GridItem, { key: "title", col: 12 },
383
- react_1.default.createElement(helper_plugin_1.GenericInput, { autoFocused: true, intlLabel: (0, translations_1.getTrad)('popup.item.form.title.label', 'Title'), name: "title", placeholder: (0, translations_1.getTrad)("e.g. Blog", 'e.g. Blog'), description: (0, translations_1.getTrad)('popup.item.form.title.placeholder', 'e.g. Blog'), type: "text", error: formik.errors.title, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.title })),
384
+ react_1.default.createElement(helper_plugin_1.GenericInput, { autoFocused: true, intlLabel: (0, translations_1.getTrad)('popup.item.form.title.label', 'Title'), name: "title", placeholder: (0, translations_1.getTrad)("e.g. Blog", 'e.g. Blog'), description: (0, translations_1.getTrad)('popup.item.form.title.placeholder', 'e.g. Blog'), type: "text", disabled: !canUpdate, error: formik.errors.title, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.title })),
384
385
  react_1.default.createElement(Grid_1.GridItem, { key: "type", col: 4, lg: 12 },
385
- react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.type.label', 'Internal link'), name: "type", options: navigationItemTypeOptions, type: "select", error: formik.errors.type, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.type })),
386
+ react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.type.label', 'Internal link'), name: "type", options: navigationItemTypeOptions, type: "select", disabled: !canUpdate, error: formik.errors.type, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.type })),
386
387
  react_1.default.createElement(Grid_1.GridItem, { key: "menuAttached", col: 4, lg: 12 },
387
- react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.menuAttached.label', 'MenuAttached'), name: "menuAttached", type: "bool", error: formik.errors.menuAttached, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.menuAttached, disabled: config.cascadeMenuAttached ? !(data.isMenuAllowedLevel && data.parentAttachedToMenu) : false })),
388
+ react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.menuAttached.label', 'MenuAttached'), name: "menuAttached", type: "bool", error: formik.errors.menuAttached, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.menuAttached, disabled: !canUpdate || (config.cascadeMenuAttached ? !(data.isMenuAllowedLevel && data.parentAttachedToMenu) : false) })),
388
389
  react_1.default.createElement(Grid_1.GridItem, { key: "path", col: 12 },
389
- react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.label`, 'Path'), name: pathSourceName, placeholder: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.placeholder`, 'e.g. Blog'), type: "text", error: formik.errors[pathSourceName], onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values[pathSourceName], description: generatePreviewPath() })),
390
+ react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.label`, 'Path'), name: pathSourceName, placeholder: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.placeholder`, 'e.g. Blog'), type: "text", disabled: !canUpdate, error: formik.errors[pathSourceName], onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values[pathSourceName], description: generatePreviewPath() })),
390
391
  formik.values.type === utils_1.navigationItemType.INTERNAL && (react_1.default.createElement(react_1.default.Fragment, null,
391
392
  react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
392
- react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.relatedType.label', 'Related Type'), placeholder: (0, translations_1.getTrad)('popup.item.form.relatedType.placeholder', 'Related Type'), name: "relatedType", error: formik.errors.relatedType, onChange: onChangeRelatedType, options: relatedTypeSelectOptions, value: formik.values.relatedType, disabled: isLoading || (0, lodash_1.isEmpty)(relatedTypeSelectOptions), description: !isLoading && (0, lodash_1.isEmpty)(relatedTypeSelectOptions)
393
+ react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.relatedType.label', 'Related Type'), placeholder: (0, translations_1.getTrad)('popup.item.form.relatedType.placeholder', 'Related Type'), name: "relatedType", error: formik.errors.relatedType, onChange: onChangeRelatedType, options: relatedTypeSelectOptions, value: formik.values.relatedType, disabled: isLoading || (0, lodash_1.isEmpty)(relatedTypeSelectOptions) || !canUpdate, description: !isLoading && (0, lodash_1.isEmpty)(relatedTypeSelectOptions)
393
394
  ? (0, translations_1.getTrad)('popup.item.form.relatedType.empty', 'There are no more content types')
394
395
  : undefined })),
395
396
  formik.values.relatedType && !isSingleSelected && (react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
396
- react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), placeholder: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), name: "related", error: formik.errors.related, onChange: ({ target: { name, value } }) => onChange({ name, value }), onInputChange: debounceContentTypeSearchQuery, inputValue: contentTypeSearchInputValue, options: relatedSelectOptions, value: formik.values.related, disabled: isLoading || thereAreNoMoreContentTypes, description: !isLoading && thereAreNoMoreContentTypes
397
+ react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), placeholder: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), name: "related", error: formik.errors.related, onChange: ({ target: { name, value } }) => onChange({ name, value }), onInputChange: debounceContentTypeSearchQuery, inputValue: contentTypeSearchInputValue, options: relatedSelectOptions, value: formik.values.related, disabled: isLoading || thereAreNoMoreContentTypes || !canUpdate, description: !isLoading && thereAreNoMoreContentTypes
397
398
  ? {
398
399
  id: (0, translations_1.getTradId)('popup.item.form.related.empty'),
399
400
  defaultMessage: 'There are no more entities',
@@ -405,19 +406,19 @@ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading,
405
406
  return (react_1.default.createElement(Grid_1.GridItem, { key: "audience", col: 6, lg: 12 },
406
407
  react_1.default.createElement(Select_1.Select, { id: "audience", placeholder: (0, utils_2.getMessage)('popup.item.form.audience.placeholder'), label: (0, utils_2.getMessage)('popup.item.form.audience.label'), onChange: onAudienceChange, value: formik.values.audience, hint: !isLoading && (0, lodash_1.isEmpty)(audienceOptions)
407
408
  ? (0, utils_2.getMessage)('popup.item.form.audience.empty', 'There are no more audiences')
408
- : undefined, multi: true, withTags: true, disabled: (0, lodash_1.isEmpty)(audienceOptions) }, audienceOptions.map(({ value, label }) => react_1.default.createElement(Select_1.Option, { key: value, value: value }, label)))));
409
+ : undefined, multi: true, withTags: true, disabled: (0, lodash_1.isEmpty)(audienceOptions) || !canUpdate }, audienceOptions.map(({ value, label }) => react_1.default.createElement(Select_1.Option, { key: value, value: value }, label)))));
409
410
  }
410
411
  else {
411
412
  return (react_1.default.createElement(Grid_1.GridItem, { key: additionalField.name, col: 6, lg: 12 },
412
- react_1.default.createElement(AdditionalFieldInput_1.default, { field: additionalField, isLoading: isLoading, onChange: onAdditionalFieldChange, value: (0, lodash_1.get)(formik.values, `additionalFields.${additionalField.name}`, null), error: (0, lodash_1.get)(formik.errors, `additionalFields.${additionalField.name}`, null) })));
413
+ react_1.default.createElement(AdditionalFieldInput_1.default, { field: additionalField, isLoading: isLoading, onChange: onAdditionalFieldChange, value: (0, lodash_1.get)(formik.values, `additionalFields.${additionalField.name}`, null), disabled: !canUpdate, error: (0, lodash_1.get)(formik.errors, `additionalFields.${additionalField.name}`, null) })));
413
414
  }
414
415
  })),
415
416
  isI18nBootstrapAvailable ? (react_1.default.createElement(Grid_1.Grid, { gap: 5, paddingTop: 5 },
416
417
  react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
417
- react_1.default.createElement(helper_plugin_1.GenericInput, { ...itemCopyProps, type: "select", name: itemLocaleCopyField, error: (0, lodash_1.get)(formik.errors, itemLocaleCopyField), onChange: onChangeLocaleCopy, options: availableLocaleOptions, value: itemLocaleCopyValue, disabled: isLoading })),
418
- react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12, paddingTop: 6 },
419
- react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: onCopyFromLocale, disabled: isLoading || !itemLocaleCopyValue }, (0, utils_2.getMessage)('popup.item.form.i18n.locale.button'))))) : null)),
420
- react_1.default.createElement(NavigationItemPopupFooter_1.NavigationItemPopupFooter, { handleSubmit: formik.handleSubmit, handleCancel: onCancel, submitDisabled: submitDisabled })));
418
+ react_1.default.createElement(helper_plugin_1.GenericInput, { ...itemCopyProps, type: "select", name: itemLocaleCopyField, error: (0, lodash_1.get)(formik.errors, itemLocaleCopyField), onChange: onChangeLocaleCopy, options: availableLocaleOptions, value: itemLocaleCopyValue, disabled: isLoading || !canUpdate })),
419
+ canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12, paddingTop: 6 },
420
+ react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: onCopyFromLocale, disabled: isLoading || !itemLocaleCopyValue }, (0, utils_2.getMessage)('popup.item.form.i18n.locale.button')))))) : null)),
421
+ react_1.default.createElement(NavigationItemPopupFooter_1.NavigationItemPopupFooter, { handleSubmit: formik.handleSubmit, handleCancel: onCancel, submitDisabled: submitDisabled, canUpdate: canUpdate })));
421
422
  };
422
423
  const appendLabelPublicationStatusFallback = () => "";
423
424
  const loadingAware = (action, isLoading) => async (input) => {
@@ -1,4 +1,4 @@
1
- import { Audience, Effect, ContentTypeEntity, NavigationItemAdditionalField, NavigationItemAdditionalFieldValues, NavigationItemType, NavigationPluginConfig, PluginConfigNameFields, ToBeFixed, VoidEffect } from '../../../../../../types';
1
+ import { Audience, Effect, ContentTypeEntity, NavigationItemAdditionalField, NavigationItemAdditionalFieldValues, NavigationItemType, NavigationPluginConfig, PluginConfigNameFields, PluginPermissions, ToBeFixed, VoidEffect } from '../../../../../../types';
2
2
  import { Id } from 'strapi-typed';
3
3
  import { StrapiContentTypeSchema } from '../../../SettingsPage/types';
4
4
  export declare type FormEventTarget<TValue = unknown> = {
@@ -63,6 +63,7 @@ export declare type NavigationItemFormProps = {
63
63
  slugify: (q: string) => Promise<{
64
64
  slug: string;
65
65
  }>;
66
+ permissions: PluginPermissions;
66
67
  };
67
68
  export declare type ContentTypeSearchQuery = ToBeFixed;
68
69
  export declare type RawFormPayload = {
@@ -1,9 +1,10 @@
1
- export function NavigationItemPopupFooter({ handleCancel, handleSubmit, submitDisabled, formViewId }: {
1
+ export function NavigationItemPopupFooter({ handleCancel, handleSubmit, submitDisabled, formViewId, canUpdate }: {
2
2
  handleCancel: any;
3
3
  handleSubmit: any;
4
4
  submitDisabled: any;
5
5
  formViewId: any;
6
- }): JSX.Element;
6
+ canUpdate: any;
7
+ }): JSX.Element | null;
7
8
  export namespace NavigationItemPopupFooter {
8
9
  namespace defaultProps {
9
10
  const onValidate: undefined;
@@ -17,6 +18,7 @@ export namespace NavigationItemPopupFooter {
17
18
  export { submitDisabled_1 as submitDisabled };
18
19
  const formViewId_1: PropTypes.Requireable<object>;
19
20
  export { formViewId_1 as formViewId };
21
+ export const canUpdate: PropTypes.Requireable<boolean>;
20
22
  }
21
23
  }
22
24
  import PropTypes from "prop-types";
@@ -9,7 +9,10 @@ const prop_types_1 = __importDefault(require("prop-types"));
9
9
  const Button_1 = require("@strapi/design-system/Button");
10
10
  const ModalLayout_1 = require("@strapi/design-system/ModalLayout");
11
11
  const utils_1 = require("../../../../utils");
12
- const NavigationItemPopupFooter = ({ handleCancel, handleSubmit, submitDisabled, formViewId }) => {
12
+ const NavigationItemPopupFooter = ({ handleCancel, handleSubmit, submitDisabled, formViewId, canUpdate }) => {
13
+ if (!canUpdate) {
14
+ return null;
15
+ }
13
16
  return (react_1.default.createElement(ModalLayout_1.ModalFooter, { startActions: react_1.default.createElement(Button_1.Button, { onClick: handleCancel, variant: "tertiary" }, (0, utils_1.getMessage)('popup.item.form.button.cancel')), endActions: react_1.default.createElement(Button_1.Button, { onClick: handleSubmit, disabled: submitDisabled }, (0, utils_1.getMessage)(`popup.item.form.button.save`)) }));
14
17
  };
15
18
  exports.NavigationItemPopupFooter = NavigationItemPopupFooter;
@@ -23,5 +26,6 @@ exports.NavigationItemPopupFooter.propTypes = {
23
26
  handleSubmit: prop_types_1.default.func,
24
27
  submitDisabled: prop_types_1.default.bool,
25
28
  formViewId: prop_types_1.default.object,
29
+ canUpdate: prop_types_1.default.bool,
26
30
  };
27
31
  //# sourceMappingURL=NavigationItemPopupFooter.js.map