@strapi/plugin-users-permissions 0.0.0-next.fab38a080023b0850fc7a8ec8605d1ee6970e3c9 → 0.0.0-next.fc1775f7731f8999840e56e298a216b9a6c5c4ad

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 (198) hide show
  1. package/.eslintignore +1 -2
  2. package/.eslintrc +17 -0
  3. package/LICENSE +18 -3
  4. package/admin/src/components/BoundRoute/{index.js → index.jsx} +2 -2
  5. package/admin/src/components/FormModal/Input/{index.js → index.jsx} +32 -31
  6. package/admin/src/components/FormModal/index.jsx +115 -0
  7. package/admin/src/components/Permissions/PermissionRow/{CheckboxWrapper.js → CheckboxWrapper.jsx} +4 -3
  8. package/admin/src/components/Permissions/PermissionRow/{SubCategory.js → SubCategory.jsx} +13 -22
  9. package/admin/src/components/Permissions/index.jsx +47 -0
  10. package/admin/src/components/Permissions/reducer.js +1 -1
  11. package/admin/src/components/Policies/{index.js → index.jsx} +7 -5
  12. package/admin/src/components/UsersPermissions/{index.js → index.jsx} +15 -7
  13. package/admin/src/components/UsersPermissions/reducer.js +1 -1
  14. package/admin/src/index.js +16 -43
  15. package/admin/src/pages/AdvancedSettings/index.jsx +214 -0
  16. package/admin/src/pages/AdvancedSettings/utils/layout.js +20 -35
  17. package/admin/src/pages/AdvancedSettings/utils/schema.js +5 -2
  18. package/admin/src/pages/EmailTemplates/components/EmailForm.jsx +156 -0
  19. package/admin/src/pages/EmailTemplates/components/{EmailTable.js → EmailTable.jsx} +20 -17
  20. package/admin/src/pages/EmailTemplates/{index.js → index.jsx} +36 -62
  21. package/admin/src/pages/EmailTemplates/utils/schema.js +18 -6
  22. package/admin/src/pages/Providers/{index.js → index.jsx} +98 -113
  23. package/admin/src/pages/Providers/utils/forms.js +23 -11
  24. package/admin/src/pages/Roles/constants.js +3 -3
  25. package/admin/src/pages/Roles/hooks/usePlugins.js +4 -4
  26. package/admin/src/pages/Roles/index.jsx +24 -0
  27. package/admin/src/pages/Roles/pages/{CreatePage.js → CreatePage.jsx} +53 -58
  28. package/admin/src/pages/Roles/pages/{EditPage.js → EditPage.jsx} +63 -68
  29. package/admin/src/pages/Roles/pages/ListPage/components/{TableBody.js → TableBody.jsx} +27 -31
  30. package/admin/src/pages/Roles/pages/ListPage/{index.js → index.jsx} +79 -55
  31. package/admin/src/translations/en.json +1 -1
  32. package/admin/src/utils/prefixPluginTranslations.js +13 -0
  33. package/dist/_chunks/ar-BguGUqwK.js +44 -0
  34. package/dist/_chunks/ar-BguGUqwK.js.map +1 -0
  35. package/dist/_chunks/ar-CK8BRRXB.mjs +44 -0
  36. package/dist/_chunks/ar-CK8BRRXB.mjs.map +1 -0
  37. package/dist/_chunks/cs-BVigMk0l.mjs +50 -0
  38. package/dist/_chunks/cs-BVigMk0l.mjs.map +1 -0
  39. package/dist/_chunks/cs-BW8-K_GY.js +50 -0
  40. package/dist/_chunks/cs-BW8-K_GY.js.map +1 -0
  41. package/dist/_chunks/de-BKUdRFI4.mjs +62 -0
  42. package/dist/_chunks/de-BKUdRFI4.mjs.map +1 -0
  43. package/dist/_chunks/de-owXpVluI.js +62 -0
  44. package/dist/_chunks/de-owXpVluI.js.map +1 -0
  45. package/dist/_chunks/dk-BQiTK50l.mjs +86 -0
  46. package/dist/_chunks/dk-BQiTK50l.mjs.map +1 -0
  47. package/dist/_chunks/dk-LXAnbuBk.js +86 -0
  48. package/dist/_chunks/dk-LXAnbuBk.js.map +1 -0
  49. package/dist/_chunks/en-DOHtPf-2.mjs +86 -0
  50. package/dist/_chunks/en-DOHtPf-2.mjs.map +1 -0
  51. package/dist/_chunks/en-MHo5mcsU.js +86 -0
  52. package/dist/_chunks/en-MHo5mcsU.js.map +1 -0
  53. package/dist/_chunks/es-BwLCLXAQ.js +86 -0
  54. package/dist/_chunks/es-BwLCLXAQ.js.map +1 -0
  55. package/dist/_chunks/es-DNgOVMjD.mjs +86 -0
  56. package/dist/_chunks/es-DNgOVMjD.mjs.map +1 -0
  57. package/dist/_chunks/fr-DkgRugiU.mjs +50 -0
  58. package/dist/_chunks/fr-DkgRugiU.mjs.map +1 -0
  59. package/dist/_chunks/fr-DkhpSjjm.js +50 -0
  60. package/dist/_chunks/fr-DkhpSjjm.js.map +1 -0
  61. package/dist/_chunks/id-BTemOeTZ.js +62 -0
  62. package/dist/_chunks/id-BTemOeTZ.js.map +1 -0
  63. package/dist/_chunks/id-BdEsvnaF.mjs +62 -0
  64. package/dist/_chunks/id-BdEsvnaF.mjs.map +1 -0
  65. package/dist/_chunks/index-6IfauKkm.js +365 -0
  66. package/dist/_chunks/index-6IfauKkm.js.map +1 -0
  67. package/dist/_chunks/index-BH2Kbto3.mjs +344 -0
  68. package/dist/_chunks/index-BH2Kbto3.mjs.map +1 -0
  69. package/dist/_chunks/index-BPLSKK9l.mjs +1142 -0
  70. package/dist/_chunks/index-BPLSKK9l.mjs.map +1 -0
  71. package/dist/_chunks/index-BteVBZ5H.js +252 -0
  72. package/dist/_chunks/index-BteVBZ5H.js.map +1 -0
  73. package/dist/_chunks/index-CItjLvCp.mjs +617 -0
  74. package/dist/_chunks/index-CItjLvCp.mjs.map +1 -0
  75. package/dist/_chunks/index-CU5dmnhc.js +280 -0
  76. package/dist/_chunks/index-CU5dmnhc.js.map +1 -0
  77. package/dist/_chunks/index-D4QvcqXm-BWNRataA.mjs +11090 -0
  78. package/dist/_chunks/index-D4QvcqXm-BWNRataA.mjs.map +1 -0
  79. package/dist/_chunks/index-D4QvcqXm-HpyyuCo7.js +11113 -0
  80. package/dist/_chunks/index-D4QvcqXm-HpyyuCo7.js.map +1 -0
  81. package/dist/_chunks/index-DDd0fVR6.js +1171 -0
  82. package/dist/_chunks/index-DDd0fVR6.js.map +1 -0
  83. package/dist/_chunks/index-DMkiPQOl.mjs +262 -0
  84. package/dist/_chunks/index-DMkiPQOl.mjs.map +1 -0
  85. package/dist/_chunks/index-DZnDAnLg.mjs +253 -0
  86. package/dist/_chunks/index-DZnDAnLg.mjs.map +1 -0
  87. package/dist/_chunks/index-Dd0JjBda.js +639 -0
  88. package/dist/_chunks/index-Dd0JjBda.js.map +1 -0
  89. package/dist/_chunks/it-B-rv0E24.mjs +62 -0
  90. package/dist/_chunks/it-B-rv0E24.mjs.map +1 -0
  91. package/dist/_chunks/it-D1rH6V6_.js +62 -0
  92. package/dist/_chunks/it-D1rH6V6_.js.map +1 -0
  93. package/dist/_chunks/ja-C8K-VBPD.mjs +48 -0
  94. package/dist/_chunks/ja-C8K-VBPD.mjs.map +1 -0
  95. package/dist/_chunks/ja-DqShgTMf.js +48 -0
  96. package/dist/_chunks/ja-DqShgTMf.js.map +1 -0
  97. package/dist/_chunks/ko-B9DGEPWH.js +86 -0
  98. package/dist/_chunks/ko-B9DGEPWH.js.map +1 -0
  99. package/dist/_chunks/ko-Busb0wIY.mjs +86 -0
  100. package/dist/_chunks/ko-Busb0wIY.mjs.map +1 -0
  101. package/dist/_chunks/ms-ByvsQjRt.mjs +49 -0
  102. package/dist/_chunks/ms-ByvsQjRt.mjs.map +1 -0
  103. package/dist/_chunks/ms-CPBU3LWf.js +49 -0
  104. package/dist/_chunks/ms-CPBU3LWf.js.map +1 -0
  105. package/dist/_chunks/nl-5qO8Rpcy.mjs +48 -0
  106. package/dist/_chunks/nl-5qO8Rpcy.mjs.map +1 -0
  107. package/dist/_chunks/nl-CwNB6YoO.js +48 -0
  108. package/dist/_chunks/nl-CwNB6YoO.js.map +1 -0
  109. package/dist/_chunks/pl-BdIzifBE.mjs +86 -0
  110. package/dist/_chunks/pl-BdIzifBE.mjs.map +1 -0
  111. package/dist/_chunks/pl-Do9UD69f.js +86 -0
  112. package/dist/_chunks/pl-Do9UD69f.js.map +1 -0
  113. package/dist/_chunks/pt-BIO24ioG.mjs +48 -0
  114. package/dist/_chunks/pt-BIO24ioG.mjs.map +1 -0
  115. package/dist/_chunks/pt-BR-D7dZhxuP.js +44 -0
  116. package/dist/_chunks/pt-BR-D7dZhxuP.js.map +1 -0
  117. package/dist/_chunks/pt-BR-f0p23AQZ.mjs +44 -0
  118. package/dist/_chunks/pt-BR-f0p23AQZ.mjs.map +1 -0
  119. package/dist/_chunks/pt-fdvyOnUp.js +48 -0
  120. package/dist/_chunks/pt-fdvyOnUp.js.map +1 -0
  121. package/dist/_chunks/ru-C94rjPGA.js +86 -0
  122. package/dist/_chunks/ru-C94rjPGA.js.map +1 -0
  123. package/dist/_chunks/ru-VWy-IB7K.mjs +86 -0
  124. package/dist/_chunks/ru-VWy-IB7K.mjs.map +1 -0
  125. package/dist/_chunks/sk-BABEhykl.js +50 -0
  126. package/dist/_chunks/sk-BABEhykl.js.map +1 -0
  127. package/dist/_chunks/sk-B_LIcepm.mjs +50 -0
  128. package/dist/_chunks/sk-B_LIcepm.mjs.map +1 -0
  129. package/dist/_chunks/sv-ABLKOokl.mjs +86 -0
  130. package/dist/_chunks/sv-ABLKOokl.mjs.map +1 -0
  131. package/dist/_chunks/sv-Be43LhA9.js +86 -0
  132. package/dist/_chunks/sv-Be43LhA9.js.map +1 -0
  133. package/dist/_chunks/th-DKyP7ueR.mjs +60 -0
  134. package/dist/_chunks/th-DKyP7ueR.mjs.map +1 -0
  135. package/dist/_chunks/th-DgVhVLhL.js +60 -0
  136. package/dist/_chunks/th-DgVhVLhL.js.map +1 -0
  137. package/dist/_chunks/tr-B_idhkEs.js +85 -0
  138. package/dist/_chunks/tr-B_idhkEs.js.map +1 -0
  139. package/dist/_chunks/tr-qa1Q5UjC.mjs +85 -0
  140. package/dist/_chunks/tr-qa1Q5UjC.mjs.map +1 -0
  141. package/dist/_chunks/uk-BmRqbeQc.mjs +49 -0
  142. package/dist/_chunks/uk-BmRqbeQc.mjs.map +1 -0
  143. package/dist/_chunks/uk-LHOivnhP.js +49 -0
  144. package/dist/_chunks/uk-LHOivnhP.js.map +1 -0
  145. package/dist/_chunks/vi-CdVRdKDw.js +50 -0
  146. package/dist/_chunks/vi-CdVRdKDw.js.map +1 -0
  147. package/dist/_chunks/vi-HW-EdMea.mjs +50 -0
  148. package/dist/_chunks/vi-HW-EdMea.mjs.map +1 -0
  149. package/dist/_chunks/zh-5hKkVPA4.mjs +86 -0
  150. package/dist/_chunks/zh-5hKkVPA4.mjs.map +1 -0
  151. package/dist/_chunks/zh-Cuq8gMnF.js +86 -0
  152. package/dist/_chunks/zh-Cuq8gMnF.js.map +1 -0
  153. package/dist/_chunks/zh-Hans-BHilK-yc.mjs +86 -0
  154. package/dist/_chunks/zh-Hans-BHilK-yc.mjs.map +1 -0
  155. package/dist/_chunks/zh-Hans-GQDMKtY4.js +86 -0
  156. package/dist/_chunks/zh-Hans-GQDMKtY4.js.map +1 -0
  157. package/dist/admin/index.js +4 -0
  158. package/dist/admin/index.js.map +1 -0
  159. package/dist/admin/index.mjs +5 -0
  160. package/dist/admin/index.mjs.map +1 -0
  161. package/package.json +45 -27
  162. package/packup.config.ts +22 -0
  163. package/server/bootstrap/index.js +18 -51
  164. package/server/bootstrap/users-permissions-actions.js +6 -0
  165. package/server/config.js +29 -0
  166. package/server/content-types/user/index.js +0 -1
  167. package/server/controllers/auth.js +62 -63
  168. package/server/controllers/content-manager-user.js +28 -30
  169. package/server/controllers/role.js +17 -4
  170. package/server/controllers/user.js +8 -9
  171. package/server/controllers/validation/auth.js +81 -25
  172. package/server/graphql/types/index.js +1 -0
  173. package/server/graphql/types/me.js +1 -0
  174. package/server/graphql/types/user-input.js +20 -0
  175. package/server/middlewares/rateLimit.js +1 -1
  176. package/server/register.js +1 -1
  177. package/server/services/jwt.js +3 -3
  178. package/server/services/permission.js +3 -7
  179. package/server/services/providers-registry.js +469 -261
  180. package/server/services/providers.js +10 -5
  181. package/server/services/role.js +15 -13
  182. package/server/services/user.js +56 -19
  183. package/server/services/users-permissions.js +15 -13
  184. package/server/utils/index.d.ts +2 -1
  185. package/server/utils/sanitize/sanitizers.js +7 -3
  186. package/server/utils/sanitize/visitors/remove-user-relation-from-role-entities.js +2 -2
  187. package/.eslintrc.js +0 -14
  188. package/admin/src/components/FormModal/index.js +0 -126
  189. package/admin/src/components/Permissions/index.js +0 -55
  190. package/admin/src/pages/AdvancedSettings/index.js +0 -259
  191. package/admin/src/pages/EmailTemplates/components/EmailForm.js +0 -176
  192. package/admin/src/pages/Roles/index.js +0 -33
  193. package/admin/src/pages/Roles/pages/ListPage/utils/api.js +0 -30
  194. package/server/bootstrap/grant-config.js +0 -131
  195. package/strapi-admin.js +0 -3
  196. package/strapi-server.js +0 -3
  197. /package/admin/src/components/Permissions/PermissionRow/{index.js → index.jsx} +0 -0
  198. /package/admin/src/contexts/UsersPermissionsContext/{index.js → index.jsx} +0 -0
@@ -0,0 +1,1142 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { createContext, useContext, useMemo, useCallback, useReducer, forwardRef, useImperativeHandle, memo, useEffect, useState } from "react";
4
+ import { translatedErrors, useNotification, useFetchClient, useAPIErrorHandler, Page, useTracking, Layouts, BackButton, useQueryParams, useRBAC, SearchInput, ConfirmDialog } from "@strapi/strapi/admin";
5
+ import { useNavigate, useMatch, NavLink, Routes, Route } from "react-router-dom";
6
+ import { g as getTrad, P as PERMISSIONS } from "./index-DZnDAnLg.mjs";
7
+ import { Box, Flex, Typography, Checkbox, Grid, VisuallyHidden, Accordion, Main, Button, Field, TextInput, Textarea, Link, Tbody, Tr, Td, IconButton, useNotifyAT, useFilter, useCollator, LinkButton, Table, Thead, Th, EmptyStateLayout, Dialog } from "@strapi/design-system";
8
+ import { Cog, Check, Pencil, Trash, Plus } from "@strapi/icons";
9
+ import { Formik, Form } from "formik";
10
+ import { useIntl } from "react-intl";
11
+ import { useQueries, useMutation, useQuery } from "react-query";
12
+ import PropTypes from "prop-types";
13
+ import upperFirst from "lodash/upperFirst";
14
+ import sortBy from "lodash/sortBy";
15
+ import get from "lodash/get";
16
+ import { css, styled } from "styled-components";
17
+ import { produce } from "immer";
18
+ import isEmpty from "lodash/isEmpty";
19
+ import without from "lodash/without";
20
+ import map from "lodash/map";
21
+ import tail from "lodash/tail";
22
+ import set from "lodash/set";
23
+ import take from "lodash/take";
24
+ import * as yup from "yup";
25
+ const UsersPermissions$2 = createContext({});
26
+ const UsersPermissionsProvider = ({ children, value }) => {
27
+ return /* @__PURE__ */ jsx(UsersPermissions$2.Provider, { value, children });
28
+ };
29
+ const useUsersPermissions = () => useContext(UsersPermissions$2);
30
+ UsersPermissionsProvider.propTypes = {
31
+ children: PropTypes.node.isRequired,
32
+ value: PropTypes.object.isRequired
33
+ };
34
+ function formatPluginName(pluginSlug) {
35
+ switch (pluginSlug) {
36
+ case "application":
37
+ return "Application";
38
+ case "plugin::content-manager":
39
+ return "Content manager";
40
+ case "plugin::content-type-builder":
41
+ return "Content types builder";
42
+ case "plugin::documentation":
43
+ return "Documentation";
44
+ case "plugin::email":
45
+ return "Email";
46
+ case "plugin::i18n":
47
+ return "i18n";
48
+ case "plugin::upload":
49
+ return "Upload";
50
+ case "plugin::users-permissions":
51
+ return "Users-permissions";
52
+ default:
53
+ return upperFirst(pluginSlug.replace("api::", "").replace("plugin::", ""));
54
+ }
55
+ }
56
+ const init$1 = (initialState2, permissions) => {
57
+ const collapses = Object.keys(permissions).sort().map((name) => ({ name, isOpen: false }));
58
+ return { ...initialState2, collapses };
59
+ };
60
+ const activeCheckboxWrapperStyles = css`
61
+ background: ${(props) => props.theme.colors.primary100};
62
+
63
+ #cog {
64
+ opacity: 1;
65
+ }
66
+ `;
67
+ const CheckboxWrapper = styled(Box)`
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+
72
+ #cog {
73
+ opacity: 0;
74
+ path {
75
+ fill: ${(props) => props.theme.colors.primary600};
76
+ }
77
+ }
78
+
79
+ /* Show active style both on hover and when the action is selected */
80
+ ${(props) => props.isActive && activeCheckboxWrapperStyles}
81
+ &:hover {
82
+ ${activeCheckboxWrapperStyles}
83
+ }
84
+ `;
85
+ const Border = styled.div`
86
+ flex: 1;
87
+ align-self: center;
88
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
89
+ `;
90
+ const SubCategory = ({ subCategory }) => {
91
+ const { formatMessage } = useIntl();
92
+ const { onChange, onChangeSelectAll, onSelectedAction, selectedAction, modifiedData } = useUsersPermissions();
93
+ const currentScopedModifiedData = useMemo(() => {
94
+ return get(modifiedData, subCategory.name, {});
95
+ }, [modifiedData, subCategory]);
96
+ const hasAllActionsSelected = useMemo(() => {
97
+ return Object.values(currentScopedModifiedData).every((action) => action.enabled === true);
98
+ }, [currentScopedModifiedData]);
99
+ const hasSomeActionsSelected = useMemo(() => {
100
+ return Object.values(currentScopedModifiedData).some((action) => action.enabled === true) && !hasAllActionsSelected;
101
+ }, [currentScopedModifiedData, hasAllActionsSelected]);
102
+ const handleChangeSelectAll = useCallback(
103
+ ({ target: { name } }) => {
104
+ onChangeSelectAll({ target: { name, value: !hasAllActionsSelected } });
105
+ },
106
+ [hasAllActionsSelected, onChangeSelectAll]
107
+ );
108
+ const isActionSelected = useCallback(
109
+ (actionName) => {
110
+ return selectedAction === actionName;
111
+ },
112
+ [selectedAction]
113
+ );
114
+ return /* @__PURE__ */ jsxs(Box, { children: [
115
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
116
+ /* @__PURE__ */ jsx(Box, { paddingRight: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: subCategory.label }) }),
117
+ /* @__PURE__ */ jsx(Border, {}),
118
+ /* @__PURE__ */ jsx(Box, { paddingLeft: 4, children: /* @__PURE__ */ jsx(
119
+ Checkbox,
120
+ {
121
+ name: subCategory.name,
122
+ checked: hasSomeActionsSelected ? "indeterminate" : hasAllActionsSelected,
123
+ onCheckedChange: (value) => handleChangeSelectAll({ target: { name: subCategory.name, value } }),
124
+ children: formatMessage({ id: "app.utils.select-all", defaultMessage: "Select all" })
125
+ }
126
+ ) })
127
+ ] }),
128
+ /* @__PURE__ */ jsx(Flex, { paddingTop: 6, paddingBottom: 6, children: /* @__PURE__ */ jsx(Grid.Root, { gap: 2, style: { flex: 1 }, children: subCategory.actions.map((action) => {
129
+ const name = `${action.name}.enabled`;
130
+ return /* @__PURE__ */ jsx(Grid.Item, { col: 6, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxs(CheckboxWrapper, { isActive: isActionSelected(action.name), padding: 2, hasRadius: true, children: [
131
+ /* @__PURE__ */ jsx(
132
+ Checkbox,
133
+ {
134
+ checked: get(modifiedData, name, false),
135
+ name,
136
+ onCheckedChange: (value) => onChange({ target: { name, value } }),
137
+ children: action.label
138
+ }
139
+ ),
140
+ /* @__PURE__ */ jsxs(
141
+ "button",
142
+ {
143
+ type: "button",
144
+ onClick: () => onSelectedAction(action.name),
145
+ style: { display: "inline-flex", alignItems: "center" },
146
+ children: [
147
+ /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: formatMessage(
148
+ {
149
+ id: "app.utils.show-bound-route",
150
+ defaultMessage: "Show bound route for {route}"
151
+ },
152
+ {
153
+ route: action.name
154
+ }
155
+ ) }),
156
+ /* @__PURE__ */ jsx(Cog, { id: "cog", cursor: "pointer" })
157
+ ]
158
+ }
159
+ )
160
+ ] }) }, action.name);
161
+ }) }) })
162
+ ] });
163
+ };
164
+ SubCategory.propTypes = {
165
+ subCategory: PropTypes.object.isRequired
166
+ };
167
+ const PermissionRow = ({ name, permissions }) => {
168
+ const subCategories = useMemo(() => {
169
+ return sortBy(
170
+ Object.values(permissions.controllers).reduce((acc, curr, index) => {
171
+ const currentName = `${name}.controllers.${Object.keys(permissions.controllers)[index]}`;
172
+ const actions = sortBy(
173
+ Object.keys(curr).reduce((acc2, current) => {
174
+ return [
175
+ ...acc2,
176
+ {
177
+ ...curr[current],
178
+ label: current,
179
+ name: `${currentName}.${current}`
180
+ }
181
+ ];
182
+ }, []),
183
+ "label"
184
+ );
185
+ return [
186
+ ...acc,
187
+ {
188
+ actions,
189
+ label: Object.keys(permissions.controllers)[index],
190
+ name: currentName
191
+ }
192
+ ];
193
+ }, []),
194
+ "label"
195
+ );
196
+ }, [name, permissions]);
197
+ return /* @__PURE__ */ jsx(Box, { padding: 6, children: subCategories.map((subCategory) => /* @__PURE__ */ jsx(SubCategory, { subCategory }, subCategory.name)) });
198
+ };
199
+ PermissionRow.propTypes = {
200
+ name: PropTypes.string.isRequired,
201
+ permissions: PropTypes.object.isRequired
202
+ };
203
+ const initialState$1 = {
204
+ collapses: []
205
+ };
206
+ const reducer$1 = (state, action) => (
207
+ // eslint-disable-next-line consistent-return
208
+ produce(state, (draftState) => {
209
+ switch (action.type) {
210
+ case "TOGGLE_COLLAPSE": {
211
+ draftState.collapses = state.collapses.map((collapse, index) => {
212
+ if (index === action.index) {
213
+ return { ...collapse, isOpen: !collapse.isOpen };
214
+ }
215
+ return { ...collapse, isOpen: false };
216
+ });
217
+ break;
218
+ }
219
+ default:
220
+ return draftState;
221
+ }
222
+ })
223
+ );
224
+ const Permissions = () => {
225
+ const { modifiedData } = useUsersPermissions();
226
+ const { formatMessage } = useIntl();
227
+ const [{ collapses }] = useReducer(reducer$1, initialState$1, (state) => init$1(state, modifiedData));
228
+ return /* @__PURE__ */ jsx(Accordion.Root, { size: "M", children: /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: collapses.map((collapse, index) => /* @__PURE__ */ jsxs(Accordion.Item, { value: collapse.name, children: [
229
+ /* @__PURE__ */ jsx(Accordion.Header, { variant: index % 2 === 0 ? "secondary" : void 0, children: /* @__PURE__ */ jsx(
230
+ Accordion.Trigger,
231
+ {
232
+ caretPosition: "right",
233
+ description: formatMessage(
234
+ {
235
+ id: "users-permissions.Plugin.permissions.plugins.description",
236
+ defaultMessage: "Define all allowed actions for the {name} plugin."
237
+ },
238
+ { name: collapse.name }
239
+ ),
240
+ children: formatPluginName(collapse.name)
241
+ }
242
+ ) }),
243
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsx(PermissionRow, { permissions: modifiedData[collapse.name], name: collapse.name }) })
244
+ ] }, collapse.name)) }) });
245
+ };
246
+ const getMethodColor = (verb) => {
247
+ switch (verb) {
248
+ case "POST": {
249
+ return {
250
+ text: "success600",
251
+ border: "success200",
252
+ background: "success100"
253
+ };
254
+ }
255
+ case "GET": {
256
+ return {
257
+ text: "secondary600",
258
+ border: "secondary200",
259
+ background: "secondary100"
260
+ };
261
+ }
262
+ case "PUT": {
263
+ return {
264
+ text: "warning600",
265
+ border: "warning200",
266
+ background: "warning100"
267
+ };
268
+ }
269
+ case "DELETE": {
270
+ return {
271
+ text: "danger600",
272
+ border: "danger200",
273
+ background: "danger100"
274
+ };
275
+ }
276
+ default: {
277
+ return {
278
+ text: "neutral600",
279
+ border: "neutral200",
280
+ background: "neutral100"
281
+ };
282
+ }
283
+ }
284
+ };
285
+ const MethodBox = styled(Box)`
286
+ margin: -1px;
287
+ border-radius: ${({ theme }) => theme.spaces[1]} 0 0 ${({ theme }) => theme.spaces[1]};
288
+ `;
289
+ function BoundRoute({ route }) {
290
+ const { formatMessage } = useIntl();
291
+ const { method, handler: title, path } = route;
292
+ const formattedRoute = path ? tail(path.split("/")) : [];
293
+ const [controller = "", action = ""] = title ? title.split(".") : [];
294
+ const colors = getMethodColor(route.method);
295
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
296
+ /* @__PURE__ */ jsxs(Typography, { variant: "delta", tag: "h3", children: [
297
+ formatMessage({
298
+ id: "users-permissions.BoundRoute.title",
299
+ defaultMessage: "Bound route to"
300
+ }),
301
+ " ",
302
+ /* @__PURE__ */ jsx("span", { children: controller }),
303
+ /* @__PURE__ */ jsxs(Typography, { variant: "delta", textColor: "primary600", children: [
304
+ ".",
305
+ action
306
+ ] })
307
+ ] }),
308
+ /* @__PURE__ */ jsxs(Flex, { hasRadius: true, background: "neutral0", borderColor: "neutral200", gap: 0, children: [
309
+ /* @__PURE__ */ jsx(MethodBox, { background: colors.background, borderColor: colors.border, padding: 2, children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: colors.text, children: method }) }),
310
+ /* @__PURE__ */ jsx(Box, { paddingLeft: 2, paddingRight: 2, children: map(formattedRoute, (value) => /* @__PURE__ */ jsxs(Typography, { textColor: value.includes(":") ? "neutral600" : "neutral900", children: [
311
+ "/",
312
+ value
313
+ ] }, value)) })
314
+ ] })
315
+ ] });
316
+ }
317
+ BoundRoute.defaultProps = {
318
+ route: {
319
+ handler: "Nocontroller.error",
320
+ method: "GET",
321
+ path: "/there-is-no-path"
322
+ }
323
+ };
324
+ BoundRoute.propTypes = {
325
+ route: PropTypes.shape({
326
+ handler: PropTypes.string,
327
+ method: PropTypes.string,
328
+ path: PropTypes.string
329
+ })
330
+ };
331
+ const Policies = () => {
332
+ const { formatMessage } = useIntl();
333
+ const { selectedAction, routes } = useUsersPermissions();
334
+ const path = without(selectedAction.split("."), "controllers");
335
+ const controllerRoutes = get(routes, path[0]);
336
+ const pathResolved = path.slice(1).join(".");
337
+ const displayedRoutes = isEmpty(controllerRoutes) ? [] : controllerRoutes.filter((o) => o.handler.endsWith(pathResolved));
338
+ return /* @__PURE__ */ jsx(
339
+ Grid.Item,
340
+ {
341
+ col: 5,
342
+ background: "neutral150",
343
+ paddingTop: 6,
344
+ paddingBottom: 6,
345
+ paddingLeft: 7,
346
+ paddingRight: 7,
347
+ style: { minHeight: "100%" },
348
+ direction: "column",
349
+ alignItems: "stretch",
350
+ children: selectedAction ? /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: displayedRoutes.map((route, key) => (
351
+ // eslint-disable-next-line react/no-array-index-key
352
+ /* @__PURE__ */ jsx(BoundRoute, { route }, key)
353
+ )) }) : /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
354
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", children: formatMessage({
355
+ id: "users-permissions.Policies.header.title",
356
+ defaultMessage: "Advanced settings"
357
+ }) }),
358
+ /* @__PURE__ */ jsx(Typography, { tag: "p", textColor: "neutral600", children: formatMessage({
359
+ id: "users-permissions.Policies.header.hint",
360
+ defaultMessage: "Select the application's actions or the plugin's actions and click on the cog icon to display the bound route"
361
+ }) })
362
+ ] })
363
+ }
364
+ );
365
+ };
366
+ const init = (state, permissions, routes) => {
367
+ return {
368
+ ...state,
369
+ initialData: permissions,
370
+ modifiedData: permissions,
371
+ routes
372
+ };
373
+ };
374
+ const initialState = {
375
+ initialData: {},
376
+ modifiedData: {},
377
+ routes: {},
378
+ selectedAction: "",
379
+ policies: []
380
+ };
381
+ const reducer = (state, action) => produce(state, (draftState) => {
382
+ switch (action.type) {
383
+ case "ON_CHANGE": {
384
+ const keysLength = action.keys.length;
385
+ const isChangingCheckbox = action.keys[keysLength - 1] === "enabled";
386
+ if (action.value && isChangingCheckbox) {
387
+ const selectedAction = take(action.keys, keysLength - 1).join(".");
388
+ draftState.selectedAction = selectedAction;
389
+ }
390
+ set(draftState, ["modifiedData", ...action.keys], action.value);
391
+ break;
392
+ }
393
+ case "ON_CHANGE_SELECT_ALL": {
394
+ const pathToValue = ["modifiedData", ...action.keys];
395
+ const oldValues = get(state, pathToValue, {});
396
+ const updatedValues = Object.keys(oldValues).reduce((acc, current) => {
397
+ acc[current] = { ...oldValues[current], enabled: action.value };
398
+ return acc;
399
+ }, {});
400
+ set(draftState, pathToValue, updatedValues);
401
+ break;
402
+ }
403
+ case "ON_RESET": {
404
+ draftState.modifiedData = state.initialData;
405
+ break;
406
+ }
407
+ case "ON_SUBMIT_SUCCEEDED": {
408
+ draftState.initialData = state.modifiedData;
409
+ break;
410
+ }
411
+ case "SELECT_ACTION": {
412
+ const { actionToSelect } = action;
413
+ draftState.selectedAction = actionToSelect === state.selectedAction ? "" : actionToSelect;
414
+ break;
415
+ }
416
+ default:
417
+ return draftState;
418
+ }
419
+ });
420
+ const UsersPermissions = forwardRef(({ permissions, routes }, ref) => {
421
+ const { formatMessage } = useIntl();
422
+ const [state, dispatch] = useReducer(
423
+ reducer,
424
+ initialState,
425
+ (state2) => init(state2, permissions, routes)
426
+ );
427
+ useImperativeHandle(ref, () => ({
428
+ getPermissions() {
429
+ return {
430
+ permissions: state.modifiedData
431
+ };
432
+ },
433
+ resetForm() {
434
+ dispatch({ type: "ON_RESET" });
435
+ },
436
+ setFormAfterSubmit() {
437
+ dispatch({ type: "ON_SUBMIT_SUCCEEDED" });
438
+ }
439
+ }));
440
+ const handleChange = ({ target: { name, value } }) => dispatch({
441
+ type: "ON_CHANGE",
442
+ keys: name.split("."),
443
+ value: value === "empty__string_value" ? "" : value
444
+ });
445
+ const handleChangeSelectAll = ({ target: { name, value } }) => dispatch({
446
+ type: "ON_CHANGE_SELECT_ALL",
447
+ keys: name.split("."),
448
+ value
449
+ });
450
+ const handleSelectedAction = (actionToSelect) => dispatch({
451
+ type: "SELECT_ACTION",
452
+ actionToSelect
453
+ });
454
+ const providerValue = {
455
+ ...state,
456
+ onChange: handleChange,
457
+ onChangeSelectAll: handleChangeSelectAll,
458
+ onSelectedAction: handleSelectedAction
459
+ };
460
+ return /* @__PURE__ */ jsx(UsersPermissionsProvider, { value: providerValue, children: /* @__PURE__ */ jsxs(Grid.Root, { gap: 0, shadow: "filterShadow", hasRadius: true, background: "neutral0", children: [
461
+ /* @__PURE__ */ jsx(
462
+ Grid.Item,
463
+ {
464
+ col: 7,
465
+ paddingTop: 6,
466
+ paddingBottom: 6,
467
+ paddingLeft: 7,
468
+ paddingRight: 7,
469
+ direction: "column",
470
+ alignItems: "stretch",
471
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
472
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
473
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
474
+ id: getTrad("Plugins.header.title"),
475
+ defaultMessage: "Permissions"
476
+ }) }),
477
+ /* @__PURE__ */ jsx(Typography, { tag: "p", textColor: "neutral600", children: formatMessage({
478
+ id: getTrad("Plugins.header.description"),
479
+ defaultMessage: "Only actions bound by a route are listed below."
480
+ }) })
481
+ ] }),
482
+ /* @__PURE__ */ jsx(Permissions, {})
483
+ ] })
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx(Policies, {})
487
+ ] }) });
488
+ });
489
+ UsersPermissions.propTypes = {
490
+ permissions: PropTypes.object.isRequired,
491
+ routes: PropTypes.object.isRequired
492
+ };
493
+ const UsersPermissions$1 = memo(UsersPermissions);
494
+ const createRoleSchema = yup.object().shape({
495
+ name: yup.string().required(translatedErrors.required.id),
496
+ description: yup.string().required(translatedErrors.required.id)
497
+ });
498
+ const cleanPermissions = (permissions) => Object.keys(permissions).reduce((acc, current) => {
499
+ const currentPermission = permissions[current].controllers;
500
+ const cleanedControllers = Object.keys(currentPermission).reduce((acc2, curr) => {
501
+ if (isEmpty(currentPermission[curr])) {
502
+ return acc2;
503
+ }
504
+ acc2[curr] = currentPermission[curr];
505
+ return acc2;
506
+ }, {});
507
+ if (isEmpty(cleanedControllers)) {
508
+ return acc;
509
+ }
510
+ acc[current] = { controllers: cleanedControllers };
511
+ return acc;
512
+ }, {});
513
+ const usePlugins = () => {
514
+ const { toggleNotification } = useNotification();
515
+ const { get: get2 } = useFetchClient();
516
+ const { formatAPIError } = useAPIErrorHandler(getTrad);
517
+ const [
518
+ {
519
+ data: permissions,
520
+ isLoading: isLoadingPermissions,
521
+ error: permissionsError,
522
+ refetch: refetchPermissions
523
+ },
524
+ { data: routes, isLoading: isLoadingRoutes, error: routesError, refetch: refetchRoutes }
525
+ ] = useQueries([
526
+ {
527
+ queryKey: ["users-permissions", "permissions"],
528
+ async queryFn() {
529
+ const {
530
+ data: { permissions: permissions2 }
531
+ } = await get2(`/users-permissions/permissions`);
532
+ return permissions2;
533
+ }
534
+ },
535
+ {
536
+ queryKey: ["users-permissions", "routes"],
537
+ async queryFn() {
538
+ const {
539
+ data: { routes: routes2 }
540
+ } = await get2(`/users-permissions/routes`);
541
+ return routes2;
542
+ }
543
+ }
544
+ ]);
545
+ const refetchQueries = async () => {
546
+ await Promise.all([refetchPermissions(), refetchRoutes()]);
547
+ };
548
+ useEffect(() => {
549
+ if (permissionsError) {
550
+ toggleNotification({
551
+ type: "danger",
552
+ message: formatAPIError(permissionsError)
553
+ });
554
+ }
555
+ }, [toggleNotification, permissionsError, formatAPIError]);
556
+ useEffect(() => {
557
+ if (routesError) {
558
+ toggleNotification({
559
+ type: "danger",
560
+ message: formatAPIError(routesError)
561
+ });
562
+ }
563
+ }, [toggleNotification, routesError, formatAPIError]);
564
+ const isLoading = isLoadingPermissions || isLoadingRoutes;
565
+ return {
566
+ // TODO: these return values need to be memoized, otherwise
567
+ // they will create infinite rendering loops when used as
568
+ // effect dependencies
569
+ permissions: permissions ? cleanPermissions(permissions) : {},
570
+ routes: routes ?? {},
571
+ getData: refetchQueries,
572
+ isLoading
573
+ };
574
+ };
575
+ const CreatePage = () => {
576
+ const { formatMessage } = useIntl();
577
+ const { toggleNotification } = useNotification();
578
+ const navigate = useNavigate();
579
+ const { isLoading: isLoadingPlugins, permissions, routes } = usePlugins();
580
+ const { trackUsage } = useTracking();
581
+ const permissionsRef = React.useRef();
582
+ const { post } = useFetchClient();
583
+ const mutation = useMutation((body) => post(`/users-permissions/roles`, body), {
584
+ onError() {
585
+ toggleNotification({
586
+ type: "danger",
587
+ message: formatMessage({
588
+ id: "notification.error",
589
+ defaultMessage: "An error occurred"
590
+ })
591
+ });
592
+ },
593
+ onSuccess() {
594
+ trackUsage("didCreateRole");
595
+ toggleNotification({
596
+ type: "success",
597
+ message: formatMessage({
598
+ id: getTrad("Settings.roles.created"),
599
+ defaultMessage: "Role created"
600
+ })
601
+ });
602
+ navigate(-1);
603
+ }
604
+ });
605
+ const handleCreateRoleSubmit = async (data) => {
606
+ const permissions2 = permissionsRef.current.getPermissions();
607
+ await mutation.mutate({ ...data, ...permissions2, users: [] });
608
+ };
609
+ return /* @__PURE__ */ jsxs(Main, { children: [
610
+ /* @__PURE__ */ jsx(Page.Title, { children: formatMessage(
611
+ { id: "Settings.PageTitle", defaultMessage: "Settings - {name}" },
612
+ { name: "Roles" }
613
+ ) }),
614
+ /* @__PURE__ */ jsx(
615
+ Formik,
616
+ {
617
+ enableReinitialize: true,
618
+ initialValues: { name: "", description: "" },
619
+ onSubmit: handleCreateRoleSubmit,
620
+ validationSchema: createRoleSchema,
621
+ children: ({ handleSubmit, values, handleChange, errors }) => /* @__PURE__ */ jsxs(Form, { noValidate: true, onSubmit: handleSubmit, children: [
622
+ /* @__PURE__ */ jsx(
623
+ Layouts.Header,
624
+ {
625
+ primaryAction: !isLoadingPlugins && /* @__PURE__ */ jsx(Button, { type: "submit", loading: mutation.isLoading, startIcon: /* @__PURE__ */ jsx(Check, {}), children: formatMessage({
626
+ id: "global.save",
627
+ defaultMessage: "Save"
628
+ }) }),
629
+ title: formatMessage({
630
+ id: "Settings.roles.create.title",
631
+ defaultMessage: "Create a role"
632
+ }),
633
+ subtitle: formatMessage({
634
+ id: "Settings.roles.create.description",
635
+ defaultMessage: "Define the rights given to the role"
636
+ })
637
+ }
638
+ ),
639
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(
640
+ Flex,
641
+ {
642
+ background: "neutral0",
643
+ direction: "column",
644
+ alignItems: "stretch",
645
+ gap: 7,
646
+ hasRadius: true,
647
+ paddingTop: 6,
648
+ paddingBottom: 6,
649
+ paddingLeft: 7,
650
+ paddingRight: 7,
651
+ shadow: "filterShadow",
652
+ children: [
653
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", children: [
654
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
655
+ id: getTrad("EditPage.form.roles"),
656
+ defaultMessage: "Role details"
657
+ }) }),
658
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
659
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxs(
660
+ Field.Root,
661
+ {
662
+ name: "name",
663
+ error: errors?.name ? formatMessage({ id: errors.name, defaultMessage: "Name is required" }) : false,
664
+ required: true,
665
+ children: [
666
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
667
+ id: "global.name",
668
+ defaultMessage: "Name"
669
+ }) }),
670
+ /* @__PURE__ */ jsx(TextInput, { value: values.name || "", onChange: handleChange }),
671
+ /* @__PURE__ */ jsx(Field.Error, {})
672
+ ]
673
+ }
674
+ ) }),
675
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxs(
676
+ Field.Root,
677
+ {
678
+ name: "description",
679
+ error: errors?.description ? formatMessage({
680
+ id: errors.description,
681
+ defaultMessage: "Description is required"
682
+ }) : false,
683
+ required: true,
684
+ children: [
685
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
686
+ id: "global.description",
687
+ defaultMessage: "Description"
688
+ }) }),
689
+ /* @__PURE__ */ jsx(Textarea, { value: values.description || "", onChange: handleChange }),
690
+ /* @__PURE__ */ jsx(Field.Error, {})
691
+ ]
692
+ }
693
+ ) })
694
+ ] })
695
+ ] }),
696
+ !isLoadingPlugins && /* @__PURE__ */ jsx(
697
+ UsersPermissions$1,
698
+ {
699
+ ref: permissionsRef,
700
+ permissions,
701
+ routes
702
+ }
703
+ )
704
+ ]
705
+ }
706
+ ) })
707
+ ] })
708
+ }
709
+ )
710
+ ] });
711
+ };
712
+ const ProtectedRolesCreatePage = () => /* @__PURE__ */ jsx(Page.Protect, { permissions: PERMISSIONS.createRole, children: /* @__PURE__ */ jsx(CreatePage, {}) });
713
+ const EditPage = () => {
714
+ const { formatMessage } = useIntl();
715
+ const { toggleNotification } = useNotification();
716
+ const {
717
+ params: { id }
718
+ } = useMatch(`/settings/users-permissions/roles/:id`);
719
+ const { get: get2 } = useFetchClient();
720
+ const { isLoading: isLoadingPlugins, routes } = usePlugins();
721
+ const {
722
+ data: role,
723
+ isLoading: isLoadingRole,
724
+ refetch: refetchRole
725
+ } = useQuery(["users-permissions", "role", id], async () => {
726
+ const {
727
+ data: { role: role2 }
728
+ } = await get2(`/users-permissions/roles/${id}`);
729
+ return role2;
730
+ });
731
+ const permissionsRef = React.useRef();
732
+ const { put } = useFetchClient();
733
+ const { formatAPIError } = useAPIErrorHandler();
734
+ const mutation = useMutation((body) => put(`/users-permissions/roles/${id}`, body), {
735
+ onError(error) {
736
+ toggleNotification({
737
+ type: "danger",
738
+ message: formatAPIError(error)
739
+ });
740
+ },
741
+ async onSuccess() {
742
+ toggleNotification({
743
+ type: "success",
744
+ message: formatMessage({
745
+ id: getTrad("Settings.roles.created"),
746
+ defaultMessage: "Role edited"
747
+ })
748
+ });
749
+ await refetchRole();
750
+ }
751
+ });
752
+ const handleEditRoleSubmit = async (data) => {
753
+ const permissions = permissionsRef.current.getPermissions();
754
+ await mutation.mutate({ ...data, ...permissions, users: [] });
755
+ };
756
+ if (isLoadingRole) {
757
+ return /* @__PURE__ */ jsx(Page.Loading, {});
758
+ }
759
+ return /* @__PURE__ */ jsxs(Main, { children: [
760
+ /* @__PURE__ */ jsx(Page.Title, { children: formatMessage(
761
+ { id: "Settings.PageTitle", defaultMessage: "Settings - {name}" },
762
+ { name: "Roles" }
763
+ ) }),
764
+ /* @__PURE__ */ jsx(
765
+ Formik,
766
+ {
767
+ enableReinitialize: true,
768
+ initialValues: { name: role.name, description: role.description },
769
+ onSubmit: handleEditRoleSubmit,
770
+ validationSchema: createRoleSchema,
771
+ children: ({ handleSubmit, values, handleChange, errors }) => /* @__PURE__ */ jsxs(Form, { noValidate: true, onSubmit: handleSubmit, children: [
772
+ /* @__PURE__ */ jsx(
773
+ Layouts.Header,
774
+ {
775
+ primaryAction: !isLoadingPlugins ? /* @__PURE__ */ jsx(
776
+ Button,
777
+ {
778
+ disabled: role.code === "strapi-super-admin",
779
+ type: "submit",
780
+ loading: mutation.isLoading,
781
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
782
+ children: formatMessage({
783
+ id: "global.save",
784
+ defaultMessage: "Save"
785
+ })
786
+ }
787
+ ) : null,
788
+ title: role.name,
789
+ subtitle: role.description,
790
+ navigationAction: /* @__PURE__ */ jsx(BackButton, { fallback: ".." })
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(
794
+ Flex,
795
+ {
796
+ background: "neutral0",
797
+ direction: "column",
798
+ alignItems: "stretch",
799
+ gap: 7,
800
+ hasRadius: true,
801
+ paddingTop: 6,
802
+ paddingBottom: 6,
803
+ paddingLeft: 7,
804
+ paddingRight: 7,
805
+ shadow: "filterShadow",
806
+ children: [
807
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
808
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
809
+ id: getTrad("EditPage.form.roles"),
810
+ defaultMessage: "Role details"
811
+ }) }),
812
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
813
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxs(
814
+ Field.Root,
815
+ {
816
+ name: "name",
817
+ error: errors?.name ? formatMessage({
818
+ id: errors.name,
819
+ defaultMessage: "Name is required"
820
+ }) : false,
821
+ required: true,
822
+ children: [
823
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
824
+ id: "global.name",
825
+ defaultMessage: "Name"
826
+ }) }),
827
+ /* @__PURE__ */ jsx(TextInput, { value: values.name || "", onChange: handleChange }),
828
+ /* @__PURE__ */ jsx(Field.Error, {})
829
+ ]
830
+ }
831
+ ) }),
832
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxs(
833
+ Field.Root,
834
+ {
835
+ name: "description",
836
+ error: errors?.description ? formatMessage({
837
+ id: errors.description,
838
+ defaultMessage: "Description is required"
839
+ }) : false,
840
+ required: true,
841
+ children: [
842
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
843
+ id: "global.description",
844
+ defaultMessage: "Description"
845
+ }) }),
846
+ /* @__PURE__ */ jsx(Textarea, { value: values.description || "", onChange: handleChange }),
847
+ /* @__PURE__ */ jsx(Field.Error, {})
848
+ ]
849
+ }
850
+ ) })
851
+ ] })
852
+ ] }),
853
+ !isLoadingPlugins && /* @__PURE__ */ jsx(
854
+ UsersPermissions$1,
855
+ {
856
+ ref: permissionsRef,
857
+ permissions: role.permissions,
858
+ routes
859
+ }
860
+ )
861
+ ]
862
+ }
863
+ ) })
864
+ ] })
865
+ }
866
+ )
867
+ ] });
868
+ };
869
+ const ProtectedRolesEditPage = () => /* @__PURE__ */ jsx(Page.Protect, { permissions: PERMISSIONS.updateRole, children: /* @__PURE__ */ jsx(EditPage, {}) });
870
+ const EditLink = styled(Link)`
871
+ align-items: center;
872
+ height: 3.2rem;
873
+ width: 3.2rem;
874
+ display: flex;
875
+ justify-content: center;
876
+ padding: ${({ theme }) => `${theme.spaces[2]}`};
877
+
878
+ svg {
879
+ height: 1.6rem;
880
+ width: 1.6rem;
881
+
882
+ path {
883
+ fill: ${({ theme }) => theme.colors.neutral500};
884
+ }
885
+ }
886
+
887
+ &:hover,
888
+ &:focus {
889
+ svg {
890
+ path {
891
+ fill: ${({ theme }) => theme.colors.neutral800};
892
+ }
893
+ }
894
+ }
895
+ `;
896
+ const TableBody = ({ sortedRoles, canDelete, canUpdate, setRoleToDelete, onDelete }) => {
897
+ const { formatMessage } = useIntl();
898
+ const navigate = useNavigate();
899
+ const [showConfirmDelete, setShowConfirmDelete] = onDelete;
900
+ const checkCanDeleteRole = (role) => canDelete && !["public", "authenticated"].includes(role.type);
901
+ const handleClickDelete = (id) => {
902
+ setRoleToDelete(id);
903
+ setShowConfirmDelete(!showConfirmDelete);
904
+ };
905
+ return /* @__PURE__ */ jsx(Tbody, { children: sortedRoles?.map((role) => /* @__PURE__ */ jsxs(Tr, { cursor: "pointer", onClick: () => navigate(role.id.toString()), children: [
906
+ /* @__PURE__ */ jsx(Td, { width: "20%", children: /* @__PURE__ */ jsx(Typography, { children: role.name }) }),
907
+ /* @__PURE__ */ jsx(Td, { width: "50%", children: /* @__PURE__ */ jsx(Typography, { children: role.description }) }),
908
+ /* @__PURE__ */ jsx(Td, { width: "30%", children: /* @__PURE__ */ jsx(Typography, { children: formatMessage(
909
+ {
910
+ id: "Roles.RoleRow.user-count",
911
+ defaultMessage: "{number, plural, =0 {# user} one {# user} other {# users}}"
912
+ },
913
+ { number: role.nb_users }
914
+ ) }) }),
915
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "end", onClick: (e) => e.stopPropagation(), children: [
916
+ canUpdate ? /* @__PURE__ */ jsx(
917
+ EditLink,
918
+ {
919
+ tag: NavLink,
920
+ to: role.id.toString(),
921
+ "aria-label": formatMessage(
922
+ { id: "app.component.table.edit", defaultMessage: "Edit {target}" },
923
+ { target: `${role.name}` }
924
+ ),
925
+ children: /* @__PURE__ */ jsx(Pencil, {})
926
+ }
927
+ ) : null,
928
+ checkCanDeleteRole(role) && /* @__PURE__ */ jsx(
929
+ IconButton,
930
+ {
931
+ onClick: () => handleClickDelete(role.id.toString()),
932
+ variant: "ghost",
933
+ label: formatMessage(
934
+ { id: "global.delete-target", defaultMessage: "Delete {target}" },
935
+ { target: `${role.name}` }
936
+ ),
937
+ children: /* @__PURE__ */ jsx(Trash, {})
938
+ }
939
+ )
940
+ ] }) })
941
+ ] }, role.name)) });
942
+ };
943
+ TableBody.defaultProps = {
944
+ canDelete: false,
945
+ canUpdate: false
946
+ };
947
+ TableBody.propTypes = {
948
+ onDelete: PropTypes.array.isRequired,
949
+ setRoleToDelete: PropTypes.func.isRequired,
950
+ sortedRoles: PropTypes.array.isRequired,
951
+ canDelete: PropTypes.bool,
952
+ canUpdate: PropTypes.bool
953
+ };
954
+ const RolesListPage = () => {
955
+ const { trackUsage } = useTracking();
956
+ const { formatMessage, locale } = useIntl();
957
+ const { toggleNotification } = useNotification();
958
+ const { notifyStatus } = useNotifyAT();
959
+ const [{ query }] = useQueryParams();
960
+ const _q = query?._q || "";
961
+ const [showConfirmDelete, setShowConfirmDelete] = useState(false);
962
+ const [roleToDelete, setRoleToDelete] = useState();
963
+ const { del, get: get2 } = useFetchClient();
964
+ const {
965
+ isLoading: isLoadingForPermissions,
966
+ allowedActions: { canRead, canDelete, canCreate, canUpdate }
967
+ } = useRBAC({
968
+ create: PERMISSIONS.createRole,
969
+ read: PERMISSIONS.readRoles,
970
+ update: PERMISSIONS.updateRole,
971
+ delete: PERMISSIONS.deleteRole
972
+ });
973
+ const {
974
+ isLoading: isLoadingForData,
975
+ data: { roles },
976
+ isFetching,
977
+ refetch
978
+ } = useQuery("get-roles", () => fetchData(toggleNotification, formatMessage, notifyStatus), {
979
+ initialData: {},
980
+ enabled: canRead
981
+ });
982
+ const { contains } = useFilter(locale, {
983
+ sensitivity: "base"
984
+ });
985
+ const formatter = useCollator(locale, {
986
+ sensitivity: "base"
987
+ });
988
+ const isLoading = isLoadingForData || isFetching || isLoadingForPermissions;
989
+ const handleShowConfirmDelete = () => {
990
+ setShowConfirmDelete(!showConfirmDelete);
991
+ };
992
+ const deleteData = async (id, formatMessage2, toggleNotification2) => {
993
+ try {
994
+ await del(`/users-permissions/roles/${id}`);
995
+ } catch (error) {
996
+ toggleNotification2({
997
+ type: "danger",
998
+ message: formatMessage2({ id: "notification.error", defaultMessage: "An error occured" })
999
+ });
1000
+ }
1001
+ };
1002
+ const fetchData = async (toggleNotification2, formatMessage2, notifyStatus2) => {
1003
+ try {
1004
+ const { data } = await get2("/users-permissions/roles");
1005
+ notifyStatus2("The roles have loaded successfully");
1006
+ return data;
1007
+ } catch (err) {
1008
+ toggleNotification2({
1009
+ type: "danger",
1010
+ message: formatMessage2({ id: "notification.error", defaultMessage: "An error occurred" })
1011
+ });
1012
+ throw new Error(err);
1013
+ }
1014
+ };
1015
+ const emptyLayout = {
1016
+ roles: {
1017
+ id: getTrad("Roles.empty"),
1018
+ defaultMessage: "You don't have any roles yet."
1019
+ },
1020
+ search: {
1021
+ id: getTrad("Roles.empty.search"),
1022
+ defaultMessage: "No roles match the search."
1023
+ }
1024
+ };
1025
+ const pageTitle = formatMessage({
1026
+ id: "global.roles",
1027
+ defaultMessage: "Roles"
1028
+ });
1029
+ const deleteMutation = useMutation((id) => deleteData(id, formatMessage, toggleNotification), {
1030
+ async onSuccess() {
1031
+ await refetch();
1032
+ }
1033
+ });
1034
+ const handleConfirmDelete = async () => {
1035
+ await deleteMutation.mutateAsync(roleToDelete);
1036
+ setShowConfirmDelete(!showConfirmDelete);
1037
+ };
1038
+ const sortedRoles = (roles || []).filter((role) => contains(role.name, _q) || contains(role.description, _q)).sort(
1039
+ (a, b) => formatter.compare(a.name, b.name) || formatter.compare(a.description, b.description)
1040
+ );
1041
+ const emptyContent = _q && !sortedRoles.length ? "search" : "roles";
1042
+ const colCount = 4;
1043
+ const rowCount = (roles?.length || 0) + 1;
1044
+ if (isLoading) {
1045
+ return /* @__PURE__ */ jsx(Page.Loading, {});
1046
+ }
1047
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
1048
+ /* @__PURE__ */ jsx(Page.Title, { children: formatMessage(
1049
+ { id: "Settings.PageTitle", defaultMessage: "Settings - {name}" },
1050
+ { name: pageTitle }
1051
+ ) }),
1052
+ /* @__PURE__ */ jsxs(Page.Main, { children: [
1053
+ /* @__PURE__ */ jsx(
1054
+ Layouts.Header,
1055
+ {
1056
+ title: formatMessage({
1057
+ id: "global.roles",
1058
+ defaultMessage: "Roles"
1059
+ }),
1060
+ subtitle: formatMessage({
1061
+ id: "Settings.roles.list.description",
1062
+ defaultMessage: "List of roles"
1063
+ }),
1064
+ primaryAction: canCreate ? /* @__PURE__ */ jsx(
1065
+ LinkButton,
1066
+ {
1067
+ to: "new",
1068
+ tag: NavLink,
1069
+ onClick: () => trackUsage("willCreateRole"),
1070
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1071
+ size: "S",
1072
+ children: formatMessage({
1073
+ id: getTrad("List.button.roles"),
1074
+ defaultMessage: "Add new role"
1075
+ })
1076
+ }
1077
+ ) : null
1078
+ }
1079
+ ),
1080
+ /* @__PURE__ */ jsx(
1081
+ Layouts.Action,
1082
+ {
1083
+ startActions: /* @__PURE__ */ jsx(
1084
+ SearchInput,
1085
+ {
1086
+ label: formatMessage({
1087
+ id: "app.component.search.label",
1088
+ defaultMessage: "Search"
1089
+ })
1090
+ }
1091
+ )
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ jsxs(Layouts.Content, { children: [
1095
+ !canRead && /* @__PURE__ */ jsx(Page.NoPermissions, {}),
1096
+ canRead && sortedRoles && sortedRoles?.length ? /* @__PURE__ */ jsxs(Table, { colCount, rowCount, children: [
1097
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
1098
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: formatMessage({ id: "global.name", defaultMessage: "Name" }) }) }),
1099
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: formatMessage({
1100
+ id: "global.description",
1101
+ defaultMessage: "Description"
1102
+ }) }) }),
1103
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: formatMessage({
1104
+ id: "global.users",
1105
+ defaultMessage: "Users"
1106
+ }) }) }),
1107
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(VisuallyHidden, { children: formatMessage({
1108
+ id: "global.actions",
1109
+ defaultMessage: "Actions"
1110
+ }) }) })
1111
+ ] }) }),
1112
+ /* @__PURE__ */ jsx(
1113
+ TableBody,
1114
+ {
1115
+ sortedRoles,
1116
+ canDelete,
1117
+ canUpdate,
1118
+ permissions: PERMISSIONS,
1119
+ setRoleToDelete,
1120
+ onDelete: [showConfirmDelete, setShowConfirmDelete]
1121
+ }
1122
+ )
1123
+ ] }) : /* @__PURE__ */ jsx(EmptyStateLayout, { content: formatMessage(emptyLayout[emptyContent]) })
1124
+ ] }),
1125
+ /* @__PURE__ */ jsx(Dialog.Root, { open: showConfirmDelete, onOpenChange: handleShowConfirmDelete, children: /* @__PURE__ */ jsx(ConfirmDialog, { onConfirm: handleConfirmDelete }) })
1126
+ ] })
1127
+ ] });
1128
+ };
1129
+ const ProtectedRolesListPage = () => {
1130
+ return /* @__PURE__ */ jsx(Page.Protect, { permissions: PERMISSIONS.accessRoles, children: /* @__PURE__ */ jsx(RolesListPage, {}) });
1131
+ };
1132
+ const Roles = () => {
1133
+ return /* @__PURE__ */ jsx(Page.Protect, { permissions: PERMISSIONS.accessRoles, children: /* @__PURE__ */ jsxs(Routes, { children: [
1134
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(ProtectedRolesListPage, {}) }),
1135
+ /* @__PURE__ */ jsx(Route, { path: "new", element: /* @__PURE__ */ jsx(ProtectedRolesCreatePage, {}) }),
1136
+ /* @__PURE__ */ jsx(Route, { path: ":id", element: /* @__PURE__ */ jsx(ProtectedRolesEditPage, {}) })
1137
+ ] }) });
1138
+ };
1139
+ export {
1140
+ Roles as default
1141
+ };
1142
+ //# sourceMappingURL=index-BPLSKK9l.mjs.map