@plone/volto 19.0.0-alpha.26 → 19.0.0-alpha.28

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 (203) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc +0 -3
  3. package/CHANGELOG.md +69 -0
  4. package/README.md +0 -1
  5. package/babel.js +0 -6
  6. package/locales/af/LC_MESSAGES/volto.po +5455 -0
  7. package/locales/af.json +1 -1
  8. package/locales/ar/LC_MESSAGES/volto.po +5455 -0
  9. package/locales/ar.json +1 -1
  10. package/locales/bg/LC_MESSAGES/volto.po +5455 -0
  11. package/locales/bg.json +1 -1
  12. package/locales/bn/LC_MESSAGES/volto.po +5455 -0
  13. package/locales/bn.json +1 -1
  14. package/locales/ca/LC_MESSAGES/volto.po +406 -345
  15. package/locales/ca.json +1 -1
  16. package/locales/cs/LC_MESSAGES/volto.po +5455 -0
  17. package/locales/cs.json +1 -1
  18. package/locales/cy/LC_MESSAGES/volto.po +5455 -0
  19. package/locales/cy.json +1 -1
  20. package/locales/da/LC_MESSAGES/volto.po +5455 -0
  21. package/locales/da.json +1 -1
  22. package/locales/de/LC_MESSAGES/volto.po +70 -8
  23. package/locales/de.json +1 -1
  24. package/locales/el/LC_MESSAGES/volto.po +5455 -0
  25. package/locales/el.json +1 -1
  26. package/locales/en/LC_MESSAGES/volto.po +66 -0
  27. package/locales/en.json +1 -1
  28. package/locales/en_AU/LC_MESSAGES/volto.po +5455 -0
  29. package/locales/en_AU.json +1 -1
  30. package/locales/en_GB/LC_MESSAGES/volto.po +5455 -0
  31. package/locales/en_GB.json +1 -1
  32. package/locales/eo/LC_MESSAGES/volto.po +5455 -0
  33. package/locales/eo.json +1 -1
  34. package/locales/es/LC_MESSAGES/volto.po +173 -112
  35. package/locales/es.json +1 -1
  36. package/locales/et/LC_MESSAGES/volto.po +5455 -0
  37. package/locales/et.json +1 -1
  38. package/locales/eu/LC_MESSAGES/volto.po +256 -195
  39. package/locales/eu.json +1 -1
  40. package/locales/fa/LC_MESSAGES/volto.po +5455 -0
  41. package/locales/fa.json +1 -1
  42. package/locales/fi/LC_MESSAGES/volto.po +62 -1
  43. package/locales/fi.json +1 -1
  44. package/locales/fr/LC_MESSAGES/volto.po +61 -0
  45. package/locales/fr.json +1 -1
  46. package/locales/fu/LC_MESSAGES/volto.po +5455 -0
  47. package/locales/fu.json +1 -1
  48. package/locales/gl/LC_MESSAGES/volto.po +5456 -0
  49. package/locales/gl.json +1 -1
  50. package/locales/he/LC_MESSAGES/volto.po +5455 -0
  51. package/locales/he.json +1 -1
  52. package/locales/hi/LC_MESSAGES/volto.po +66 -0
  53. package/locales/hi.json +1 -1
  54. package/locales/hr/LC_MESSAGES/volto.po +5455 -0
  55. package/locales/hr.json +1 -1
  56. package/locales/hu/LC_MESSAGES/volto.po +5455 -0
  57. package/locales/hu.json +1 -1
  58. package/locales/hy/LC_MESSAGES/volto.po +5455 -0
  59. package/locales/hy.json +1 -1
  60. package/locales/id/LC_MESSAGES/volto.po +5455 -0
  61. package/locales/id.json +1 -1
  62. package/locales/it/LC_MESSAGES/volto.po +90 -24
  63. package/locales/it.json +1 -1
  64. package/locales/ja/LC_MESSAGES/volto.po +105 -43
  65. package/locales/ja.json +1 -1
  66. package/locales/ka/LC_MESSAGES/volto.po +5455 -0
  67. package/locales/ka.json +1 -1
  68. package/locales/kn/LC_MESSAGES/volto.po +5455 -0
  69. package/locales/kn.json +1 -1
  70. package/locales/ko/LC_MESSAGES/volto.po +5455 -0
  71. package/locales/ko.json +1 -1
  72. package/locales/lt/LC_MESSAGES/volto.po +5455 -0
  73. package/locales/lt.json +1 -1
  74. package/locales/lv/LC_MESSAGES/volto.po +5455 -0
  75. package/locales/lv.json +1 -1
  76. package/locales/mi/LC_MESSAGES/volto.po +5455 -0
  77. package/locales/mi.json +1 -1
  78. package/locales/mk/LC_MESSAGES/volto.po +5455 -0
  79. package/locales/mk.json +1 -1
  80. package/locales/my/LC_MESSAGES/volto.po +5455 -0
  81. package/locales/my.json +1 -1
  82. package/locales/nb_NO/LC_MESSAGES/volto.po +5455 -0
  83. package/locales/nb_NO.json +1 -1
  84. package/locales/nl/LC_MESSAGES/volto.po +93 -31
  85. package/locales/nl.json +1 -1
  86. package/locales/nn/LC_MESSAGES/volto.po +5455 -0
  87. package/locales/nn.json +1 -1
  88. package/locales/pl/LC_MESSAGES/volto.po +5455 -0
  89. package/locales/pl.json +1 -1
  90. package/locales/pt/LC_MESSAGES/volto.po +723 -662
  91. package/locales/pt.json +1 -1
  92. package/locales/pt_BR/LC_MESSAGES/volto.po +61 -0
  93. package/locales/pt_BR.json +1 -1
  94. package/locales/rm/LC_MESSAGES/volto.po +5455 -0
  95. package/locales/rm.json +1 -1
  96. package/locales/ro/LC_MESSAGES/volto.po +61 -0
  97. package/locales/ro.json +1 -1
  98. package/locales/ru/LC_MESSAGES/volto.po +61 -0
  99. package/locales/ru.json +1 -1
  100. package/locales/sk/LC_MESSAGES/volto.po +5455 -0
  101. package/locales/sk.json +1 -1
  102. package/locales/sl/LC_MESSAGES/volto.po +5455 -0
  103. package/locales/sl.json +1 -1
  104. package/locales/sm/LC_MESSAGES/volto.po +5455 -0
  105. package/locales/sm.json +1 -1
  106. package/locales/sq/LC_MESSAGES/volto.po +5455 -0
  107. package/locales/sq.json +1 -1
  108. package/locales/sr/LC_MESSAGES/volto.po +5455 -0
  109. package/locales/sr.json +1 -1
  110. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5455 -0
  111. package/locales/sr@cyrl.json +1 -1
  112. package/locales/sr@latn/LC_MESSAGES/volto.po +5455 -0
  113. package/locales/sr@latn.json +1 -1
  114. package/locales/sv/LC_MESSAGES/volto.po +5455 -0
  115. package/locales/sv.json +1 -1
  116. package/locales/ta/LC_MESSAGES/volto.po +5456 -0
  117. package/locales/ta.json +1 -1
  118. package/locales/te/LC_MESSAGES/volto.po +5455 -0
  119. package/locales/te.json +1 -1
  120. package/locales/th/LC_MESSAGES/volto.po +5455 -0
  121. package/locales/th.json +1 -1
  122. package/locales/to/LC_MESSAGES/volto.po +5455 -0
  123. package/locales/to.json +1 -1
  124. package/locales/tr/LC_MESSAGES/volto.po +5456 -0
  125. package/locales/tr.json +1 -1
  126. package/locales/uk/LC_MESSAGES/volto.po +5455 -0
  127. package/locales/uk.json +1 -1
  128. package/locales/vi/LC_MESSAGES/volto.po +5455 -0
  129. package/locales/vi.json +1 -1
  130. package/locales/volto.pot +62 -1
  131. package/locales/zh_CN/LC_MESSAGES/volto.po +62 -0
  132. package/locales/zh_CN.json +1 -1
  133. package/locales/zh_Hant/LC_MESSAGES/volto.po +5455 -0
  134. package/locales/zh_Hant.json +1 -1
  135. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5455 -0
  136. package/locales/zh_Hant_HK.json +1 -1
  137. package/package.json +19 -35
  138. package/razzle.config.js +12 -5
  139. package/src/actions/blockTypes/blockTypes.ts +24 -0
  140. package/src/components/manage/Add/Add.jsx +7 -6
  141. package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -3
  142. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  143. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  144. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  145. package/src/components/manage/Blocks/Title/Edit.jsx +5 -0
  146. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  147. package/src/components/manage/Controlpanels/BlockType.tsx +166 -0
  148. package/src/components/manage/Controlpanels/BlockTypes.tsx +145 -0
  149. package/src/components/manage/Controlpanels/Controlpanels.jsx +28 -5
  150. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +10 -0
  151. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  152. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  153. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +4 -5
  154. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +57 -11
  155. package/src/components/manage/Diff/Diff.jsx +201 -298
  156. package/src/components/manage/Multilingual/CreateTranslation.jsx +8 -5
  157. package/src/components/manage/Multilingual/ManageTranslations.jsx +1 -1
  158. package/src/components/manage/Multilingual/TranslationObject.jsx +9 -6
  159. package/src/components/manage/Preferences/PersonalPreferences.jsx +8 -5
  160. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +5 -1
  161. package/src/components/manage/Toolbar/Types.crash.test.jsx +48 -0
  162. package/src/components/manage/Widgets/FileWidget.jsx +7 -0
  163. package/src/components/manage/Widgets/RegistryImageWidget.jsx +1 -1
  164. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  165. package/src/config/ControlPanels.js +2 -0
  166. package/src/config/index.js +1 -1
  167. package/src/config/validation.ts +8 -0
  168. package/src/constants/ActionTypes.js +1 -0
  169. package/src/express-middleware/devproxy.js +3 -1
  170. package/src/helpers/Blocks/Blocks.js +81 -18
  171. package/src/helpers/FormValidation/FormValidation.test.js +31 -0
  172. package/src/helpers/FormValidation/validators.ts +21 -0
  173. package/src/helpers/MessageLabels/MessageLabels.js +5 -0
  174. package/src/helpers/Utils/Utils.jsx +17 -0
  175. package/src/helpers/Utils/Utils.test.jsx +39 -0
  176. package/src/middleware/api.js +7 -3
  177. package/src/reducers/blockTypes/blockTypes.js +38 -0
  178. package/src/reducers/index.js +2 -0
  179. package/src/reducers/users/users.js +1 -1
  180. package/src/routes.js +10 -0
  181. package/src/server.jsx +7 -5
  182. package/test-setup-globals.js +26 -0
  183. package/theme/themes/pastanaga/extras/block-types.less +17 -0
  184. package/theme/themes/pastanaga/extras/main.less +1 -2
  185. package/types/actions/blockTypes/blockTypes.d.ts +7 -0
  186. package/types/components/index.d.ts +1 -1
  187. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  188. package/types/components/manage/Controlpanels/BlockType.d.ts +7 -0
  189. package/types/components/manage/Controlpanels/BlockTypes.d.ts +7 -0
  190. package/types/components/manage/Diff/Diff.d.ts +7 -2
  191. package/types/components/manage/Toolbar/Types.crash.test.d.ts +1 -0
  192. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  193. package/types/config/ControlPanels.d.ts +1 -0
  194. package/types/constants/ActionTypes.d.ts +1 -0
  195. package/types/helpers/Blocks/Blocks.d.ts +3 -0
  196. package/types/helpers/FormValidation/validators.d.ts +7 -0
  197. package/types/helpers/MessageLabels/MessageLabels.d.ts +100 -94
  198. package/types/helpers/Utils/Utils.d.ts +1 -0
  199. package/types/reducers/blockTypes/blockTypes.d.ts +16 -0
  200. package/types/reducers/index.d.ts +2 -0
  201. package/types/routes.d.ts +7 -5
  202. package/vitest.config.mjs +84 -42
  203. package/webpack-plugins/webpack-less-plugin.js +1 -1
@@ -0,0 +1,145 @@
1
+ import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
2
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
3
+ import { getParentUrl } from '@plone/volto/helpers/Url/Url';
4
+ import { useClient } from '@plone/volto/hooks';
5
+ import config from '@plone/volto/registry';
6
+ import { createPortal } from 'react-dom';
7
+ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
8
+ import { Link } from 'react-router-dom';
9
+ import { useEffect } from 'react';
10
+ import { getBlockTypes } from '@plone/volto/actions/blockTypes/blockTypes';
11
+ import { useDispatch, useSelector } from 'react-redux';
12
+ import Error from '@plone/volto/components/theme/Error/Error';
13
+ import { Spinner, Table } from '@plone/components';
14
+ import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
15
+
16
+ import backSVG from '@plone/volto/icons/back.svg';
17
+ import type { Location } from 'history';
18
+
19
+ const messages = defineMessages({
20
+ back: {
21
+ id: 'Back',
22
+ defaultMessage: 'Back',
23
+ },
24
+ loading: {
25
+ id: 'loading',
26
+ defaultMessage: 'Loading',
27
+ },
28
+ blockType: {
29
+ id: 'Block Type',
30
+ defaultMessage: 'Block Type',
31
+ },
32
+ occurrences: {
33
+ id: 'Occurrences',
34
+ defaultMessage: 'Occurrences',
35
+ },
36
+ });
37
+
38
+ type RouteProps = {
39
+ history: History;
40
+ location: Location;
41
+ };
42
+
43
+ type BlockTypesState = {
44
+ error: {
45
+ status?: number;
46
+ } | null;
47
+ items: Record<string, number> | unknown[];
48
+ loaded: boolean;
49
+ loading: boolean;
50
+ };
51
+
52
+ type SelectorState = {
53
+ blockTypes: BlockTypesState;
54
+ };
55
+
56
+ const BlockTypesControlpanel = (props: RouteProps) => {
57
+ const { location } = props;
58
+ const intl = useIntl();
59
+ const blockTypes = useSelector((state: SelectorState) => state.blockTypes);
60
+ const blocksConfig = config.blocks.blocksConfig;
61
+ const dispatch = useDispatch();
62
+ const pathname = location.pathname;
63
+ const isClient = useClient();
64
+
65
+ const blocks = Object.values(blocksConfig)
66
+ .map((blockConfig) => ({
67
+ ...blockConfig,
68
+ title: blockConfig.title
69
+ ? intl.formatMessage({
70
+ id: blockConfig.title,
71
+ defaultMessage: blockConfig.title,
72
+ })
73
+ : blockConfig.id,
74
+ }))
75
+ .sort((a, b) => (a.title === b.title ? 0 : a.title > b.title ? 1 : -1));
76
+
77
+ useEffect(() => {
78
+ dispatch(getBlockTypes());
79
+ }, [dispatch]);
80
+
81
+ if (blockTypes.loading) {
82
+ return <Spinner label={intl.formatMessage(messages.loading)} />;
83
+ }
84
+
85
+ if (blockTypes?.error?.status) {
86
+ return <Error error={blockTypes.error} />;
87
+ }
88
+
89
+ return (
90
+ blockTypes.loaded && (
91
+ <div
92
+ id="page-block_types"
93
+ className="ui container controlpanel-block-types"
94
+ >
95
+ <h1>
96
+ <FormattedMessage id="Block Types" defaultMessage="Block Types" />
97
+ </h1>
98
+ <Table
99
+ className="react-aria-Table cmsui-table"
100
+ columns={[
101
+ {
102
+ id: 'blockType',
103
+ name: intl.formatMessage(messages.blockType),
104
+ isRowHeader: true,
105
+ },
106
+ {
107
+ id: 'occurrences',
108
+ name: intl.formatMessage(messages.occurrences),
109
+ },
110
+ ]}
111
+ rows={blocks.map((block) => ({
112
+ id: block.id,
113
+ textValue: block.title,
114
+ blockType: (
115
+ <UniversalLink href={`${pathname}/${block.id}`}>
116
+ {block.title}
117
+ </UniversalLink>
118
+ ),
119
+ occurrences: blockTypes.items?.[block.id] || 0,
120
+ }))}
121
+ />
122
+ {isClient &&
123
+ createPortal(
124
+ <Toolbar
125
+ pathname={pathname}
126
+ hideDefaultViewButtons
127
+ inner={
128
+ <Link to={getParentUrl(pathname)} className="item">
129
+ <Icon
130
+ name={backSVG}
131
+ className="contents circled"
132
+ size="30px"
133
+ title={intl.formatMessage(messages.back)}
134
+ />
135
+ </Link>
136
+ }
137
+ />,
138
+ document.getElementById('toolbar') as HTMLElement,
139
+ )}
140
+ </div>
141
+ )
142
+ );
143
+ };
144
+
145
+ export default BlockTypesControlpanel;
@@ -75,6 +75,10 @@ const messages = defineMessages({
75
75
  id: 'Add-Ons',
76
76
  defaultMessage: 'Add-Ons',
77
77
  },
78
+ blockTypes: {
79
+ id: 'Block Types',
80
+ defaultMessage: 'Block Types',
81
+ },
78
82
  database: {
79
83
  id: 'Database',
80
84
  defaultMessage: 'Database',
@@ -127,6 +131,15 @@ export default function Controlpanels({ location }) {
127
131
  return <Error error={error} />;
128
132
  }
129
133
 
134
+ /**
135
+ * Determine whether the backend exposes the Discussion control panel.
136
+ * The Moderate Comments view should only be shown if the Discussion
137
+ * control panel is available to the current user.
138
+ */
139
+ const hasDiscussionControlPanel = controlpanels?.some((panel) =>
140
+ panel['@id']?.endsWith('/discussion'),
141
+ );
142
+
130
143
  let customcontrolpanels = config.settings.controlpanels
131
144
  ? config.settings.controlpanels.map((el) => {
132
145
  el.group =
@@ -146,6 +159,11 @@ export default function Controlpanels({ location }) {
146
159
  group: intl.formatMessage(messages.general),
147
160
  title: intl.formatMessage(messages.addons),
148
161
  },
162
+ {
163
+ '@id': '/block-types',
164
+ group: intl.formatMessage(messages.content),
165
+ title: intl.formatMessage(messages.blockTypes),
166
+ },
149
167
  {
150
168
  '@id': '/database',
151
169
  group: intl.formatMessage(messages.general),
@@ -171,11 +189,16 @@ export default function Controlpanels({ location }) {
171
189
  group: intl.formatMessage(messages.content),
172
190
  title: intl.formatMessage(messages.relations),
173
191
  },
174
- {
175
- '@id': '/moderate-comments',
176
- group: intl.formatMessage(messages.content),
177
- title: intl.formatMessage(messages.moderatecomments),
178
- },
192
+ // only shown if Discussion support addon is available from the backend.
193
+ ...(hasDiscussionControlPanel
194
+ ? [
195
+ {
196
+ '@id': '/moderate-comments',
197
+ group: intl.formatMessage(messages.content),
198
+ title: intl.formatMessage(messages.moderatecomments),
199
+ },
200
+ ]
201
+ : []),
179
202
  {
180
203
  '@id': '/users',
181
204
  group: intl.formatMessage(messages.usersControlPanelCategory),
@@ -22,6 +22,11 @@ describe('Controlpanels', () => {
22
22
  const store = mockStore({
23
23
  controlpanels: {
24
24
  controlpanels: [
25
+ {
26
+ '@id': 'http://localhost:8080/Plone/@controlpanels/discussion',
27
+ group: 'Content',
28
+ title: 'Discussion',
29
+ },
25
30
  {
26
31
  '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
27
32
  group: 'General',
@@ -47,6 +52,11 @@ describe('Controlpanels', () => {
47
52
  reduxAsyncConnect: {
48
53
  // Mocked in redux async connect as it isn't fetch client-side.
49
54
  controlpanels: [
55
+ {
56
+ '@id': 'http://localhost:8080/Plone/@controlpanels/discussion',
57
+ group: 'Content',
58
+ title: 'Discussion',
59
+ },
50
60
  {
51
61
  '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
52
62
  group: 'General',
@@ -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
  ),
@@ -25,6 +25,7 @@ import { Link } from 'react-router-dom';
25
25
  import Helmet from '@plone/volto/helpers/Helmet/Helmet';
26
26
  import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
27
27
  import { isManager, canAssignGroup } from '@plone/volto/helpers/User/User';
28
+ import { getErrorMessage } from '@plone/volto/helpers/Utils/Utils';
28
29
  import clearSVG from '@plone/volto/icons/clear.svg';
29
30
  import addUserSvg from '@plone/volto/icons/add-user.svg';
30
31
  import saveSVG from '@plone/volto/icons/save.svg';
@@ -351,9 +352,7 @@ const UsersControlpanel = () => {
351
352
  })
352
353
  .catch((error) => {
353
354
  // Handle error
354
- setAddUserError(
355
- error.response?.body?.error?.message || 'Error creating user',
356
- );
355
+ setAddUserError(getErrorMessage(error));
357
356
  });
358
357
  }
359
358
  },
@@ -417,11 +416,11 @@ const UsersControlpanel = () => {
417
416
  /**
418
417
  * Handle Errors after createUser()
419
418
  *
420
- * @param {object} error object. Requires the property .message
419
+ * @param {object} error object
421
420
  * @returns {undefined}
422
421
  */
423
422
  const onAddUserError = useCallback((error) => {
424
- setAddUserError(error.response.body.error.message);
423
+ setAddUserError(getErrorMessage(error));
425
424
  }, []);
426
425
 
427
426
  /**
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render, act } from '@testing-library/react';
2
+ import { render, waitFor } from '@testing-library/react';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
5
  import { MemoryRouter } from 'react-router-dom';
@@ -39,17 +39,63 @@ describe('UsersControlpanel', () => {
39
39
  messages: {},
40
40
  },
41
41
  });
42
- const { container } = await act(async () => {
43
- return render(
44
- <Provider store={store}>
45
- <MemoryRouter initialEntries={['/controlpanel/users']}>
46
- <UsersControlpanel />
47
- <div id="toolbar"></div>
48
- </MemoryRouter>
49
- </Provider>,
50
- );
51
- });
42
+ const { container } = render(
43
+ <Provider store={store}>
44
+ <MemoryRouter initialEntries={['/controlpanel/users']}>
45
+ <UsersControlpanel />
46
+ <div id="toolbar"></div>
47
+ </MemoryRouter>
48
+ </Provider>,
49
+ );
50
+ await waitFor(() => {});
52
51
 
53
52
  expect(container).toMatchSnapshot();
54
53
  });
54
+
55
+ it('handles createRequest error when response body has only message', async () => {
56
+ const store = mockStore({
57
+ userSession: {
58
+ token: jwt.sign({ sub: 'john' }, 'secret'),
59
+ },
60
+ roles: { roles: [] },
61
+ users: {
62
+ users: [],
63
+ create: {
64
+ loading: false,
65
+ error: {
66
+ response: { body: { message: 'SMTP relay access denied' } },
67
+ },
68
+ },
69
+ user: {
70
+ roles: ['Manager'],
71
+ '@id': 'admin',
72
+ },
73
+ },
74
+ groups: {
75
+ groups: [],
76
+ create: { loading: false },
77
+ },
78
+ authRole: {
79
+ authenticatedRole: [],
80
+ },
81
+ intl: {
82
+ locale: 'en',
83
+ messages: {},
84
+ },
85
+ });
86
+
87
+ render(
88
+ <Provider store={store}>
89
+ <MemoryRouter initialEntries={['/controlpanel/users']}>
90
+ <UsersControlpanel />
91
+ <div id="toolbar"></div>
92
+ </MemoryRouter>
93
+ </Provider>,
94
+ );
95
+ await waitFor(() => {});
96
+
97
+ // If the component attempted to read a missing property it would throw.
98
+ // Reaching this line means the error shape was handled without exceptions.
99
+ expect(true).toBe(true);
100
+ });
55
101
  });