@plone/volto 18.33.1 → 18.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/.release-it.json +3 -0
  2. package/CHANGELOG.md +52 -0
  3. package/README.md +0 -1
  4. package/locales/af/LC_MESSAGES/volto.po +55 -0
  5. package/locales/af.json +1 -1
  6. package/locales/ar/LC_MESSAGES/volto.po +55 -0
  7. package/locales/ar.json +1 -1
  8. package/locales/bg/LC_MESSAGES/volto.po +55 -0
  9. package/locales/bg.json +1 -1
  10. package/locales/bn/LC_MESSAGES/volto.po +55 -0
  11. package/locales/bn.json +1 -1
  12. package/locales/ca/LC_MESSAGES/volto.po +57 -2
  13. package/locales/ca.json +1 -1
  14. package/locales/cs/LC_MESSAGES/volto.po +55 -0
  15. package/locales/cs.json +1 -1
  16. package/locales/cy/LC_MESSAGES/volto.po +55 -0
  17. package/locales/cy.json +1 -1
  18. package/locales/da/LC_MESSAGES/volto.po +55 -0
  19. package/locales/da.json +1 -1
  20. package/locales/de/LC_MESSAGES/volto.po +59 -4
  21. package/locales/de.json +1 -1
  22. package/locales/el/LC_MESSAGES/volto.po +55 -0
  23. package/locales/el.json +1 -1
  24. package/locales/en/LC_MESSAGES/volto.po +55 -0
  25. package/locales/en.json +1 -1
  26. package/locales/en_AU/LC_MESSAGES/volto.po +55 -0
  27. package/locales/en_AU.json +1 -1
  28. package/locales/en_GB/LC_MESSAGES/volto.po +55 -0
  29. package/locales/en_GB.json +1 -1
  30. package/locales/eo/LC_MESSAGES/volto.po +55 -0
  31. package/locales/eo.json +1 -1
  32. package/locales/es/LC_MESSAGES/volto.po +73 -18
  33. package/locales/es.json +1 -1
  34. package/locales/et/LC_MESSAGES/volto.po +55 -0
  35. package/locales/et.json +1 -1
  36. package/locales/eu/LC_MESSAGES/volto.po +58 -3
  37. package/locales/eu.json +1 -1
  38. package/locales/fa/LC_MESSAGES/volto.po +55 -0
  39. package/locales/fa.json +1 -1
  40. package/locales/fi/LC_MESSAGES/volto.po +55 -0
  41. package/locales/fi.json +1 -1
  42. package/locales/fr/LC_MESSAGES/volto.po +56 -1
  43. package/locales/fr.json +1 -1
  44. package/locales/fu/LC_MESSAGES/volto.po +55 -0
  45. package/locales/fu.json +1 -1
  46. package/locales/gl/LC_MESSAGES/volto.po +60 -5
  47. package/locales/gl.json +1 -1
  48. package/locales/he/LC_MESSAGES/volto.po +55 -0
  49. package/locales/he.json +1 -1
  50. package/locales/hi/LC_MESSAGES/volto.po +58 -3
  51. package/locales/hi.json +1 -1
  52. package/locales/hr/LC_MESSAGES/volto.po +55 -0
  53. package/locales/hr.json +1 -1
  54. package/locales/hu/LC_MESSAGES/volto.po +55 -0
  55. package/locales/hu.json +1 -1
  56. package/locales/hy/LC_MESSAGES/volto.po +55 -0
  57. package/locales/hy.json +1 -1
  58. package/locales/id/LC_MESSAGES/volto.po +55 -0
  59. package/locales/id.json +1 -1
  60. package/locales/it/LC_MESSAGES/volto.po +56 -1
  61. package/locales/it.json +1 -1
  62. package/locales/ja/LC_MESSAGES/volto.po +55 -0
  63. package/locales/ja.json +1 -1
  64. package/locales/ka/LC_MESSAGES/volto.po +55 -0
  65. package/locales/ka.json +1 -1
  66. package/locales/kn/LC_MESSAGES/volto.po +55 -0
  67. package/locales/kn.json +1 -1
  68. package/locales/ko/LC_MESSAGES/volto.po +55 -0
  69. package/locales/ko.json +1 -1
  70. package/locales/lt/LC_MESSAGES/volto.po +55 -0
  71. package/locales/lt.json +1 -1
  72. package/locales/lv/LC_MESSAGES/volto.po +55 -0
  73. package/locales/lv.json +1 -1
  74. package/locales/mi/LC_MESSAGES/volto.po +55 -0
  75. package/locales/mi.json +1 -1
  76. package/locales/mk/LC_MESSAGES/volto.po +55 -0
  77. package/locales/mk.json +1 -1
  78. package/locales/my/LC_MESSAGES/volto.po +55 -0
  79. package/locales/my.json +1 -1
  80. package/locales/nb_NO/LC_MESSAGES/volto.po +55 -0
  81. package/locales/nb_NO.json +1 -1
  82. package/locales/nl/LC_MESSAGES/volto.po +56 -1
  83. package/locales/nl.json +1 -1
  84. package/locales/nn/LC_MESSAGES/volto.po +55 -0
  85. package/locales/nn.json +1 -1
  86. package/locales/pl/LC_MESSAGES/volto.po +55 -0
  87. package/locales/pl.json +1 -1
  88. package/locales/pt/LC_MESSAGES/volto.po +55 -0
  89. package/locales/pt.json +1 -1
  90. package/locales/pt_BR/LC_MESSAGES/volto.po +69 -14
  91. package/locales/pt_BR.json +1 -1
  92. package/locales/rm/LC_MESSAGES/volto.po +55 -0
  93. package/locales/rm.json +1 -1
  94. package/locales/ro/LC_MESSAGES/volto.po +56 -1
  95. package/locales/ro.json +1 -1
  96. package/locales/ru/LC_MESSAGES/volto.po +56 -1
  97. package/locales/ru.json +1 -1
  98. package/locales/sk/LC_MESSAGES/volto.po +55 -0
  99. package/locales/sk.json +1 -1
  100. package/locales/sl/LC_MESSAGES/volto.po +55 -0
  101. package/locales/sl.json +1 -1
  102. package/locales/sm/LC_MESSAGES/volto.po +55 -0
  103. package/locales/sm.json +1 -1
  104. package/locales/sq/LC_MESSAGES/volto.po +55 -0
  105. package/locales/sq.json +1 -1
  106. package/locales/sr/LC_MESSAGES/volto.po +55 -0
  107. package/locales/sr.json +1 -1
  108. package/locales/sr@cyrl/LC_MESSAGES/volto.po +55 -0
  109. package/locales/sr@cyrl.json +1 -1
  110. package/locales/sr@latn/LC_MESSAGES/volto.po +55 -0
  111. package/locales/sr@latn.json +1 -1
  112. package/locales/sv/LC_MESSAGES/volto.po +55 -0
  113. package/locales/sv.json +1 -1
  114. package/locales/ta/LC_MESSAGES/volto.po +56 -1
  115. package/locales/ta.json +1 -1
  116. package/locales/te/LC_MESSAGES/volto.po +55 -0
  117. package/locales/te.json +1 -1
  118. package/locales/th/LC_MESSAGES/volto.po +55 -0
  119. package/locales/th.json +1 -1
  120. package/locales/to/LC_MESSAGES/volto.po +55 -0
  121. package/locales/to.json +1 -1
  122. package/locales/tr/LC_MESSAGES/volto.po +55 -0
  123. package/locales/tr.json +1 -1
  124. package/locales/uk/LC_MESSAGES/volto.po +55 -0
  125. package/locales/uk.json +1 -1
  126. package/locales/vi/LC_MESSAGES/volto.po +55 -0
  127. package/locales/vi.json +1 -1
  128. package/locales/volto.pot +56 -1
  129. package/locales/zh_CN/LC_MESSAGES/volto.po +55 -0
  130. package/locales/zh_CN.json +1 -1
  131. package/locales/zh_Hant/LC_MESSAGES/volto.po +55 -0
  132. package/locales/zh_Hant.json +1 -1
  133. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +55 -0
  134. package/locales/zh_Hant_HK.json +1 -1
  135. package/news/7308.fix +1 -0
  136. package/news/8084.fix +1 -0
  137. package/package.json +19 -19
  138. package/razzle.config.js +1 -0
  139. package/src/actions/users/users.js +2 -2
  140. package/src/components/manage/Blocks/Block/Order/Item.jsx +18 -10
  141. package/src/components/manage/Blocks/Block/Order/Item.test.jsx +90 -0
  142. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  143. package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
  144. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  145. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  146. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +3 -0
  147. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
  148. package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
  149. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
  150. package/src/components/manage/Form/Form.jsx +6 -1
  151. package/src/components/manage/Form/ModalForm.jsx +165 -87
  152. package/src/components/manage/Sidebar/Sidebar.jsx +1 -0
  153. package/src/components/manage/Toast/Toast.jsx +35 -1
  154. package/src/components/manage/Toast/Toast.test.jsx +8 -5
  155. package/src/components/manage/Widgets/DatetimeWidget.jsx +92 -58
  156. package/src/components/manage/Widgets/DatetimeWidget.test.jsx +55 -0
  157. package/src/components/manage/Widgets/FormFieldWrapper.jsx +7 -5
  158. package/src/components/manage/Widgets/TextWidget.jsx +4 -0
  159. package/src/components/manage/Widgets/UrlWidget.jsx +51 -6
  160. package/src/components/theme/Search/Search.jsx +24 -1
  161. package/src/components/theme/Unauthorized/Unauthorized.jsx +30 -22
  162. package/src/components/theme/Unauthorized/Unauthorized.test.jsx +28 -1
  163. package/src/helpers/FormValidation/validators.ts +15 -2
  164. package/theme/themes/default/globals/site.variables +2 -2
  165. package/theme/themes/pastanaga/collections/form.overrides +21 -0
  166. package/theme/themes/pastanaga/elements/button.overrides +30 -3
  167. package/theme/themes/pastanaga/extras/main.less +16 -0
  168. package/theme/themes/pastanaga/globals/site.variables +0 -2
  169. package/types/components/manage/Blocks/Block/Order/Item.test.d.ts +1 -0
  170. package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
  171. package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
  172. package/types/components/manage/Controlpanels/index.d.ts +1 -1
  173. package/webpack-plugins/webpack-less-plugin.js +1 -1
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "18.33.1",
12
+ "version": "18.35.0",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -142,17 +142,17 @@
142
142
  "@dnd-kit/core": "6.0.8",
143
143
  "@dnd-kit/sortable": "7.0.2",
144
144
  "@dnd-kit/utilities": "3.2.2",
145
- "@loadable/component": "5.14.1",
146
- "@loadable/server": "5.14.0",
145
+ "@loadable/component": "5.16.7",
146
+ "@loadable/server": "5.16.7",
147
147
  "@redux-devtools/extension": "^3.3.0",
148
148
  "classnames": "2.5.1",
149
149
  "connected-react-router": "6.8.0",
150
- "debug": "4.3.2",
150
+ "debug": "4.3.4",
151
151
  "decorate-component-with-props": "1.2.1",
152
152
  "dependency-graph": "0.10.0",
153
153
  "detect-browser": "5.1.0",
154
- "diff": "3.5.0",
155
- "express": "4.19.2",
154
+ "diff": "3.5.1",
155
+ "express": "4.22.1",
156
156
  "filesize": "6",
157
157
  "full-icu": "1.4.0",
158
158
  "github-slugger": "1.4.0",
@@ -166,7 +166,7 @@
166
166
  "jwt-decode": "2.2.0",
167
167
  "linkify-it": "3.0.2",
168
168
  "locale": "0.1.0",
169
- "lodash": "4.17.23",
169
+ "lodash": "4.18.1",
170
170
  "lodash-move": "1.1.1",
171
171
  "moment": "2.29.4",
172
172
  "object-assign": "4.1.1",
@@ -177,7 +177,7 @@
177
177
  "process": "^0.11.10",
178
178
  "promise-file-reader": "1.0.2",
179
179
  "prop-types": "15.7.2",
180
- "query-string": "7.1.0",
180
+ "query-string": "7.1.3",
181
181
  "rc-time-picker": "3.7.3",
182
182
  "react": "18.2.0",
183
183
  "react-anchor-link-smooth-scroll": "1.0.12",
@@ -220,7 +220,7 @@
220
220
  "rrule": "2.7.1",
221
221
  "semantic-ui-less": "2.4.1",
222
222
  "semantic-ui-react": "2.1.5",
223
- "serialize-javascript": "7.0.4",
223
+ "serialize-javascript": "7.0.5",
224
224
  "slate": "0.100.0",
225
225
  "slate-hyperscript": "0.100.0",
226
226
  "slate-react": "0.98.4",
@@ -232,9 +232,9 @@
232
232
  "url": "^0.11.3",
233
233
  "use-deep-compare-effect": "1.8.1",
234
234
  "uuid": "^8.3.2",
235
- "@plone/registry": "2.7.1",
236
- "@plone/scripts": "3.10.5",
237
- "@plone/volto-slate": "18.9.1"
235
+ "@plone/scripts": "3.10.6",
236
+ "@plone/registry": "2.7.2",
237
+ "@plone/volto-slate": "18.10.0"
238
238
  },
239
239
  "devDependencies": {
240
240
  "@babel/core": "^7.0.0",
@@ -249,7 +249,7 @@
249
249
  "@babel/types": "7.20.5",
250
250
  "@fiverr/afterbuild-webpack-plugin": "^1.0.0",
251
251
  "@jest/globals": "^29.7.0",
252
- "@loadable/babel-plugin": "5.13.2",
252
+ "@loadable/babel-plugin": "5.16.1",
253
253
  "@loadable/webpack-plugin": "5.15.2",
254
254
  "@sinonjs/fake-timers": "^6.0.1",
255
255
  "@storybook/addon-actions": "^8.0.4",
@@ -268,7 +268,7 @@
268
268
  "@testing-library/react-hooks": "8.0.1",
269
269
  "@types/history": "^4.7.11",
270
270
  "@types/jest": "^29.5.8",
271
- "@types/loadable__component": "^5.13.9",
271
+ "@types/loadable__component": "^5.13.10",
272
272
  "@types/lodash": "^4.14.201",
273
273
  "@types/node": "^22",
274
274
  "@types/react": "^18",
@@ -308,7 +308,7 @@
308
308
  "eslint-plugin-prettier": "^5.1.3",
309
309
  "eslint-plugin-react": "^7.34.1",
310
310
  "eslint-plugin-react-hooks": "^4.6.0",
311
- "html-webpack-plugin": "5.5.0",
311
+ "html-webpack-plugin": "5.6.7",
312
312
  "identity-obj-proxy": "3.0.0",
313
313
  "jest": "26.6.3",
314
314
  "jest-environment-jsdom": "^26",
@@ -319,7 +319,7 @@
319
319
  "less": "3.13.1",
320
320
  "less-loader": "11.1.0",
321
321
  "lodash-webpack-plugin": "0.11.6",
322
- "mini-css-extract-plugin": "2.7.2",
322
+ "mini-css-extract-plugin": "2.10.2",
323
323
  "moment-locales-webpack-plugin": "1.2.0",
324
324
  "postcss": "8.4.31",
325
325
  "postcss-flexbugs-fixes": "5.0.2",
@@ -331,7 +331,7 @@
331
331
  "razzle": "4.2.18",
332
332
  "razzle-dev-utils": "4.2.18",
333
333
  "razzle-plugin-scss": "4.2.18",
334
- "react-docgen-typescript-plugin": "^1.0.5",
334
+ "react-docgen-typescript-plugin": "^1.0.8",
335
335
  "react-error-overlay": "6.0.9",
336
336
  "react-is": "^18.2.0",
337
337
  "release-it": "^19.0.5",
@@ -345,7 +345,7 @@
345
345
  "svg-loader": "0.0.2",
346
346
  "svgo": "^3.0.0",
347
347
  "svgo-loader": "3.0.3",
348
- "terser-webpack-plugin": "5.3.6",
348
+ "terser-webpack-plugin": "5.4.0",
349
349
  "ts-jest": "^26.4.2",
350
350
  "ts-loader": "9.4.4",
351
351
  "typescript": "^5.7.3",
@@ -357,7 +357,7 @@
357
357
  "webpack-dev-server": "4.11.1",
358
358
  "webpack-node-externals": "3.0.0",
359
359
  "why": "0.6.2",
360
- "@plone/types": "1.6.1",
360
+ "@plone/types": "1.6.2",
361
361
  "@plone/volto-coresandbox": "1.0.0"
362
362
  },
363
363
  "scripts": {
package/razzle.config.js CHANGED
@@ -310,6 +310,7 @@ const defaultModify = ({
310
310
  'load-volto-addons': addonsLoaderPath,
311
311
  ...registry.getResolveAliases(),
312
312
  '@plone/volto': `${registry.voltoPath}/src`,
313
+ '@plone/volto-slate': `${registry.voltoPath}/../volto-slate/src`,
313
314
  // to be able to reference path uncustomized by webpack
314
315
  '@plone/volto-original': `${registry.voltoPath}/src`,
315
316
  // be able to reference current package from customized package
@@ -3,7 +3,7 @@
3
3
  * @module actions/users/users
4
4
  */
5
5
 
6
- import { stringify } from 'query-string';
6
+ import qs from 'query-string';
7
7
 
8
8
  import {
9
9
  CREATE_USER,
@@ -92,7 +92,7 @@ export function listUsers(options = {}) {
92
92
 
93
93
  let filterarg =
94
94
  groups_filter.length > 0
95
- ? stringify(
95
+ ? qs.stringify(
96
96
  { 'groups-filter': groups_filter },
97
97
  { arrayFormat: 'colon-list-separator' },
98
98
  )
@@ -44,6 +44,12 @@ export const Item = forwardRef(
44
44
  config.blocks.blocksConfig[data?.['@type']]?.icon ||
45
45
  config.blocks.blocksConfig.title?.icon;
46
46
 
47
+ const required =
48
+ typeof data?.required === 'boolean'
49
+ ? data.required
50
+ : includes(config.blocks.requiredBlocks, data?.['@type']);
51
+ const fixed = !!data?.fixed;
52
+
47
53
  return (
48
54
  <li
49
55
  className={classNames(
@@ -93,15 +99,17 @@ export const Item = forwardRef(
93
99
  ref={ref}
94
100
  style={style}
95
101
  >
96
- <button
97
- ref={ref}
98
- {...handleProps}
99
- className={classNames('action', 'drag')}
100
- tabIndex={0}
101
- data-cypress="draggable-handle"
102
- >
103
- <Icon name={dragSVG} size="16px" />
104
- </button>
102
+ {!fixed && (
103
+ <button
104
+ ref={ref}
105
+ {...handleProps}
106
+ className={classNames('action', 'drag')}
107
+ tabIndex={0}
108
+ data-cypress="draggable-handle"
109
+ >
110
+ <Icon name={dragSVG} size="16px" />
111
+ </button>
112
+ )}
105
113
  <span
106
114
  className={cx('text', {
107
115
  errored: errors && Object.keys(errors).length > 0,
@@ -118,7 +126,7 @@ export const Item = forwardRef(
118
126
  config.blocks.blocksConfig[data?.['@type']]?.title ||
119
127
  data?.title}
120
128
  </span>
121
- {!clone && onRemove && (
129
+ {!clone && onRemove && !required && (
122
130
  <button
123
131
  onClick={onRemove}
124
132
  className={classNames('action', 'delete')}
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import configureStore from 'redux-mock-store';
3
+ import { Provider } from 'react-intl-redux';
4
+ import { render } from '@testing-library/react';
5
+ import config from '@plone/volto/registry';
6
+
7
+ import { Item } from './Item';
8
+
9
+ const mockStore = configureStore();
10
+
11
+ const defaultStoreState = {
12
+ intl: {
13
+ locale: 'en',
14
+ messages: {},
15
+ },
16
+ form: {
17
+ ui: {
18
+ selected: null,
19
+ hovered: null,
20
+ multiSelected: [],
21
+ gridSelected: null,
22
+ },
23
+ },
24
+ };
25
+
26
+ const renderItem = (data = {}) => {
27
+ const store = mockStore(defaultStoreState);
28
+
29
+ return render(
30
+ <Provider store={store}>
31
+ <Item
32
+ id="title-block-id"
33
+ data={{ '@type': 'title', ...data }}
34
+ depth={0}
35
+ indentationWidth={25}
36
+ onRemove={() => {}}
37
+ onSelectBlock={() => {}}
38
+ handleProps={{}}
39
+ />
40
+ </Provider>,
41
+ );
42
+ };
43
+
44
+ describe('Order Item', () => {
45
+ let requiredBlocks;
46
+
47
+ beforeEach(() => {
48
+ requiredBlocks = [...(config.blocks.requiredBlocks || [])];
49
+ config.blocks.requiredBlocks = [];
50
+ });
51
+
52
+ afterEach(() => {
53
+ config.blocks.requiredBlocks = requiredBlocks;
54
+ });
55
+
56
+ test('renders drag and delete actions for movable and removable blocks', () => {
57
+ const { container } = renderItem();
58
+
59
+ expect(container.querySelector('.action.drag')).not.toBeNull();
60
+ expect(container.querySelector('.action.delete')).not.toBeNull();
61
+ });
62
+
63
+ test('hides delete action for required blocks', () => {
64
+ const { container } = renderItem({ required: true });
65
+
66
+ expect(container.querySelector('.action.delete')).toBeNull();
67
+ });
68
+
69
+ test('hides delete action for block types configured as required', () => {
70
+ config.blocks.requiredBlocks = ['title'];
71
+
72
+ const { container } = renderItem();
73
+
74
+ expect(container.querySelector('.action.delete')).toBeNull();
75
+ });
76
+
77
+ test('allows explicit required=false to override required block types', () => {
78
+ config.blocks.requiredBlocks = ['title'];
79
+
80
+ const { container } = renderItem({ required: false });
81
+
82
+ expect(container.querySelector('.action.delete')).not.toBeNull();
83
+ });
84
+
85
+ test('hides drag action for fixed blocks', () => {
86
+ const { container } = renderItem({ fixed: true });
87
+
88
+ expect(container.querySelector('.action.drag')).toBeNull();
89
+ });
90
+ });
@@ -27,6 +27,7 @@ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
27
27
  import Icon from '@plone/volto/components/theme/Icon/Icon';
28
28
  import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
29
29
  import Toast from '@plone/volto/components/manage/Toast/Toast';
30
+ import Error from '@plone/volto/components/theme/Error/Error';
30
31
  import circleBottomSVG from '@plone/volto/icons/circle-bottom.svg';
31
32
  import circleTopSVG from '@plone/volto/icons/circle-top.svg';
32
33
  import backSVG from '@plone/volto/icons/back.svg';
@@ -148,6 +149,7 @@ const AddonsControlpanel = (props) => {
148
149
  shallowEqual,
149
150
  );
150
151
  const loadingAddons = useSelector((state) => state.addons.loading);
152
+ const addonsError = useSelector((state) => state.addons.error);
151
153
 
152
154
  useEffect(() => {
153
155
  dispatch(listAddons());
@@ -245,6 +247,11 @@ const AddonsControlpanel = (props) => {
245
247
  setactiveIndex(newIndex);
246
248
  };
247
249
 
250
+ // Error handling for unauthorized access
251
+ if (addonsError) {
252
+ return <Error error={addonsError} />;
253
+ }
254
+
248
255
  return (
249
256
  <Container id="page-addons" className="controlpanel-addons">
250
257
  <Helmet title={intl.formatMessage(messages.addOns)} />
@@ -227,6 +227,7 @@ class ContentTypes extends Component {
227
227
  addTypeError: undefined,
228
228
  addTypeSetFormDataCallback: undefined,
229
229
  });
230
+ this._addTypeTrigger?.focus();
230
231
  toast.success(
231
232
  <Toast
232
233
  success
@@ -369,10 +370,13 @@ class ContentTypes extends Component {
369
370
  />
370
371
  <ModalForm
371
372
  open={this.state.showAddType}
372
- className="modal"
373
+ className="modal add-content-type"
373
374
  onSubmit={this.onAddTypeSubmit}
374
375
  submitError={this.state.addTypeError}
375
- onCancel={() => this.setState({ showAddType: false })}
376
+ onCancel={() => {
377
+ this.setState({ showAddType: false });
378
+ this._addTypeTrigger?.focus();
379
+ }}
376
380
  title={this.props.intl.formatMessage(messages.addTypeFormTitle)}
377
381
  loading={this.props.cpanelRequest.post.loading}
378
382
  schema={{
@@ -474,6 +478,9 @@ class ContentTypes extends Component {
474
478
  aria-label={this.props.intl.formatMessage(messages.add)}
475
479
  tabIndex={0}
476
480
  id="toolbar-add"
481
+ ref={(el) => {
482
+ this._addTypeTrigger = el;
483
+ }}
477
484
  onClick={() => {
478
485
  this.setState({ showAddType: true });
479
486
  }}
@@ -10,6 +10,7 @@ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
10
10
  import { useClient } from '@plone/volto/hooks/client/useClient';
11
11
  import Icon from '@plone/volto/components/theme/Icon/Icon';
12
12
  import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
13
+ import Error from '@plone/volto/components/theme/Error/Error';
13
14
  import backSVG from '@plone/volto/icons/back.svg';
14
15
 
15
16
  const messages = defineMessages({
@@ -31,11 +32,19 @@ const DatabaseInformation = () => {
31
32
  const databaseInformation = useSelector(
32
33
  (state) => state.controlpanels.databaseinformation,
33
34
  );
35
+ const databaseError = useSelector(
36
+ (state) => state.controlpanels.database?.error,
37
+ );
34
38
 
35
39
  useEffect(() => {
36
40
  dispatch(getDatabaseInformation());
37
41
  }, [dispatch]);
38
42
 
43
+ // Error handling for unauthorized access
44
+ if (databaseError) {
45
+ return <Error error={databaseError} />;
46
+ }
47
+
39
48
  return databaseInformation ? (
40
49
  <Container id="database-page" className="controlpanel-database">
41
50
  <Helmet title={intl.formatMessage(messages.databaseInformation)} />
@@ -19,6 +19,7 @@ import { searchContent } from '@plone/volto/actions/search/search';
19
19
  import FormattedRelativeDate from '@plone/volto/components/theme/FormattedDate/FormattedRelativeDate';
20
20
  import Icon from '@plone/volto/components/theme/Icon/Icon';
21
21
  import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
22
+ import Error from '@plone/volto/components/theme/Error/Error';
22
23
  import { CommentEditModal } from '@plone/volto/components/theme/Comments';
23
24
 
24
25
  import backSVG from '@plone/volto/icons/back.svg';
@@ -65,6 +66,7 @@ class ModerateComments extends Component {
65
66
  loaded: PropTypes.bool,
66
67
  }).isRequired,
67
68
  pathname: PropTypes.string.isRequired,
69
+ searchError: PropTypes.object,
68
70
  };
69
71
 
70
72
  /**
@@ -186,6 +188,11 @@ class ModerateComments extends Component {
186
188
  * @returns {string} Markup for the component.
187
189
  */
188
190
  render() {
191
+ // Error handling for unauthorized access
192
+ if (this.props.searchError) {
193
+ return <Error error={this.props.searchError} />;
194
+ }
195
+
189
196
  return (
190
197
  <div id="page-moderate-comments">
191
198
  <CommentEditModal
@@ -297,6 +304,7 @@ export default compose(
297
304
  items: state.search.items,
298
305
  deleteRequest: state.comments.delete,
299
306
  pathname: props.location.pathname,
307
+ searchError: state.search.error,
300
308
  }),
301
309
  { deleteComment, searchContent },
302
310
  ),
@@ -14,6 +14,9 @@ vi.mock('../../Toolbar/Toolbar', () => ({
14
14
  describe('UserGroupMembershipControlPanel', () => {
15
15
  it('renders a user group membership control component', () => {
16
16
  const store = mockStore({
17
+ userSession: {
18
+ token: '1234',
19
+ },
17
20
  controlpanels: {
18
21
  controlpanel: {
19
22
  data: {
@@ -13,6 +13,7 @@ import { listRoles } from '@plone/volto/actions/roles/roles';
13
13
  import { listGroups, updateGroup } from '@plone/volto/actions/groups/groups';
14
14
  import { getControlpanel } from '@plone/volto/actions/controlpanels/controlpanels';
15
15
  import { getUserSchema } from '@plone/volto/actions/userschema/userschema';
16
+ import { asyncConnect } from '@plone/volto/helpers/AsyncConnect';
16
17
  import jwtDecode from 'jwt-decode';
17
18
  import Icon from '@plone/volto/components/theme/Icon/Icon';
18
19
  import Toast from '@plone/volto/components/manage/Toast/Toast';
@@ -57,7 +58,8 @@ import {
57
58
  * UsersControlpanel functional component.
58
59
  * @function UsersControlpanel
59
60
  */
60
- const UsersControlpanel = () => {
61
+ const UsersControlpanel = (props) => {
62
+ const { staticContext } = props;
61
63
  const intl = useIntl();
62
64
  const dispatch = useDispatch();
63
65
 
@@ -143,7 +145,10 @@ const UsersControlpanel = () => {
143
145
  await listUsersAction();
144
146
  setEntries(users);
145
147
  }
146
- await getUserSchemaAction();
148
+ // Only fetch user schema if it hasn't been loaded yet (e.g. by asyncConnect SSR)
149
+ if (!userschema?.loaded) {
150
+ await getUserSchemaAction();
151
+ }
147
152
  await getUserAction(userId);
148
153
  }, [
149
154
  getControlpanelAction,
@@ -152,6 +157,7 @@ const UsersControlpanel = () => {
152
157
  listGroupsAction,
153
158
  listUsersAction,
154
159
  users,
160
+ userschema,
155
161
  getUserSchemaAction,
156
162
  getUserAction,
157
163
  userId,
@@ -451,6 +457,8 @@ const UsersControlpanel = () => {
451
457
 
452
458
  useEffect(() => {
453
459
  setIsClient(true);
460
+ // Skip fetching if the store already has an error.
461
+ if (loadRolesRequest?.error) return;
454
462
  fetchData();
455
463
  checkLoginUsingEmailStatus();
456
464
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
@@ -471,8 +479,14 @@ const UsersControlpanel = () => {
471
479
  }
472
480
  }, [loadRolesRequest?.error, loadRolesRequest?.loading]);
473
481
 
474
- if (error) {
475
- return <Error error={error} />;
482
+ const effectiveError =
483
+ error ||
484
+ (loadRolesRequest?.error && !loadRolesRequest?.loading
485
+ ? loadRolesRequest.error
486
+ : null);
487
+
488
+ if (effectiveError) {
489
+ return <Error error={effectiveError} staticContext={staticContext} />;
476
490
  }
477
491
 
478
492
  const usernameToDelete = userToDelete ? userToDelete.username : '';
@@ -729,4 +743,43 @@ const UsersControlpanel = () => {
729
743
  );
730
744
  };
731
745
 
732
- export default UsersControlpanel;
746
+ export default asyncConnect([
747
+ {
748
+ key: 'controlpanels',
749
+ promise: ({ store: { dispatch, getState } }) => {
750
+ return dispatch(getControlpanel('usergroup')).then(() => {
751
+ const state = getState();
752
+ const many_users = state.controlpanels?.controlpanel?.data?.many_users;
753
+ if (!many_users) {
754
+ dispatch(listUsers());
755
+ dispatch(listGroups());
756
+ }
757
+ });
758
+ },
759
+ },
760
+ {
761
+ key: 'roles',
762
+ promise: ({ store: { dispatch } }) => {
763
+ return dispatch(listRoles());
764
+ },
765
+ },
766
+ {
767
+ key: 'userschema',
768
+ promise: ({ store: { dispatch } }) => {
769
+ return dispatch(getUserSchema());
770
+ },
771
+ },
772
+ {
773
+ key: 'user',
774
+ promise: ({ store: { dispatch, getState } }) => {
775
+ const state = getState();
776
+ const token = state.userSession.token;
777
+ if (token) {
778
+ const userId = jwtDecode(token).sub;
779
+ if (userId) {
780
+ return dispatch(getUser(userId));
781
+ }
782
+ }
783
+ },
784
+ },
785
+ ])(UsersControlpanel);