@plone/volto 19.0.0-alpha.3 → 19.0.0-alpha.30

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 (502) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +37 -6
  3. package/CHANGELOG.md +534 -2
  4. package/README.md +23 -21
  5. package/babel.js +1 -9
  6. package/cypress/docker/prefixed-rules.yml +26 -0
  7. package/cypress/docker/prefixed.yml +24 -0
  8. package/cypress/support/commands.js +12 -6
  9. package/cypress/support/guillotina.js +1 -0
  10. package/cypress.config.js +1 -0
  11. package/global-test-setup.js +1 -2
  12. package/locales/af/LC_MESSAGES/volto.po +5500 -0
  13. package/locales/af.json +1 -1
  14. package/locales/ar/LC_MESSAGES/volto.po +5500 -0
  15. package/locales/ar.json +1 -1
  16. package/locales/bg/LC_MESSAGES/volto.po +5500 -0
  17. package/locales/bg.json +1 -1
  18. package/locales/bn/LC_MESSAGES/volto.po +5500 -0
  19. package/locales/bn.json +1 -1
  20. package/locales/ca/LC_MESSAGES/volto.po +559 -349
  21. package/locales/ca.json +1 -1
  22. package/locales/cs/LC_MESSAGES/volto.po +5500 -0
  23. package/locales/cs.json +1 -1
  24. package/locales/cy/LC_MESSAGES/volto.po +5500 -0
  25. package/locales/cy.json +1 -1
  26. package/locales/da/LC_MESSAGES/volto.po +5500 -0
  27. package/locales/da.json +1 -1
  28. package/locales/de/LC_MESSAGES/volto.po +232 -21
  29. package/locales/de.json +1 -1
  30. package/locales/el/LC_MESSAGES/volto.po +5500 -0
  31. package/locales/el.json +1 -1
  32. package/locales/en/LC_MESSAGES/volto.po +226 -11
  33. package/locales/en.json +1 -1
  34. package/locales/en_AU/LC_MESSAGES/volto.po +5500 -0
  35. package/locales/en_AU.json +1 -1
  36. package/locales/en_GB/LC_MESSAGES/volto.po +5500 -0
  37. package/locales/en_GB.json +1 -1
  38. package/locales/eo/LC_MESSAGES/volto.po +5500 -0
  39. package/locales/eo.json +1 -1
  40. package/locales/es/LC_MESSAGES/volto.po +321 -111
  41. package/locales/es.json +1 -1
  42. package/locales/et/LC_MESSAGES/volto.po +5500 -0
  43. package/locales/et.json +1 -1
  44. package/locales/eu/LC_MESSAGES/volto.po +404 -194
  45. package/locales/eu.json +1 -1
  46. package/locales/fa/LC_MESSAGES/volto.po +5500 -0
  47. package/locales/fa.json +1 -1
  48. package/locales/fi/LC_MESSAGES/volto.po +221 -11
  49. package/locales/fi.json +1 -1
  50. package/locales/fr/LC_MESSAGES/volto.po +220 -10
  51. package/locales/fr.json +1 -1
  52. package/locales/fu/LC_MESSAGES/volto.po +5500 -0
  53. package/locales/fu.json +1 -1
  54. package/locales/gl/LC_MESSAGES/volto.po +5501 -0
  55. package/locales/gl.json +1 -1
  56. package/locales/he/LC_MESSAGES/volto.po +5500 -0
  57. package/locales/he.json +1 -1
  58. package/locales/hi/LC_MESSAGES/volto.po +225 -10
  59. package/locales/hi.json +1 -1
  60. package/locales/hr/LC_MESSAGES/volto.po +5500 -0
  61. package/locales/hr.json +1 -1
  62. package/locales/hu/LC_MESSAGES/volto.po +5500 -0
  63. package/locales/hu.json +1 -1
  64. package/locales/hy/LC_MESSAGES/volto.po +5500 -0
  65. package/locales/hy.json +1 -1
  66. package/locales/id/LC_MESSAGES/volto.po +5500 -0
  67. package/locales/id.json +1 -1
  68. package/locales/it/LC_MESSAGES/volto.po +239 -24
  69. package/locales/it.json +1 -1
  70. package/locales/ja/LC_MESSAGES/volto.po +264 -53
  71. package/locales/ja.json +1 -1
  72. package/locales/ka/LC_MESSAGES/volto.po +5500 -0
  73. package/locales/ka.json +1 -1
  74. package/locales/kn/LC_MESSAGES/volto.po +5500 -0
  75. package/locales/kn.json +1 -1
  76. package/locales/ko/LC_MESSAGES/volto.po +5500 -0
  77. package/locales/ko.json +1 -1
  78. package/locales/lt/LC_MESSAGES/volto.po +5500 -0
  79. package/locales/lt.json +1 -1
  80. package/locales/lv/LC_MESSAGES/volto.po +5500 -0
  81. package/locales/lv.json +1 -1
  82. package/locales/mi/LC_MESSAGES/volto.po +5500 -0
  83. package/locales/mi.json +1 -1
  84. package/locales/mk/LC_MESSAGES/volto.po +5500 -0
  85. package/locales/mk.json +1 -1
  86. package/locales/my/LC_MESSAGES/volto.po +5500 -0
  87. package/locales/my.json +1 -1
  88. package/locales/nb_NO/LC_MESSAGES/volto.po +5500 -0
  89. package/locales/nb_NO.json +1 -1
  90. package/locales/nl/LC_MESSAGES/volto.po +243 -32
  91. package/locales/nl.json +1 -1
  92. package/locales/nn/LC_MESSAGES/volto.po +5500 -0
  93. package/locales/nn.json +1 -1
  94. package/locales/pl/LC_MESSAGES/volto.po +5500 -0
  95. package/locales/pl.json +1 -1
  96. package/locales/pt/LC_MESSAGES/volto.po +869 -659
  97. package/locales/pt.json +1 -1
  98. package/locales/pt_BR/LC_MESSAGES/volto.po +229 -19
  99. package/locales/pt_BR.json +1 -1
  100. package/locales/rm/LC_MESSAGES/volto.po +5500 -0
  101. package/locales/rm.json +1 -1
  102. package/locales/ro/LC_MESSAGES/volto.po +252 -42
  103. package/locales/ro.json +1 -1
  104. package/locales/ru/LC_MESSAGES/volto.po +220 -10
  105. package/locales/ru.json +1 -1
  106. package/locales/sk/LC_MESSAGES/volto.po +5500 -0
  107. package/locales/sk.json +1 -1
  108. package/locales/sl/LC_MESSAGES/volto.po +5500 -0
  109. package/locales/sl.json +1 -1
  110. package/locales/sm/LC_MESSAGES/volto.po +5500 -0
  111. package/locales/sm.json +1 -1
  112. package/locales/sq/LC_MESSAGES/volto.po +5500 -0
  113. package/locales/sq.json +1 -1
  114. package/locales/sr/LC_MESSAGES/volto.po +5500 -0
  115. package/locales/sr.json +1 -1
  116. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5500 -0
  117. package/locales/sr@cyrl.json +1 -1
  118. package/locales/sr@latn/LC_MESSAGES/volto.po +5500 -0
  119. package/locales/sr@latn.json +1 -1
  120. package/locales/sv/LC_MESSAGES/volto.po +5500 -0
  121. package/locales/sv.json +1 -1
  122. package/locales/ta/LC_MESSAGES/volto.po +5501 -0
  123. package/locales/ta.json +1 -1
  124. package/locales/te/LC_MESSAGES/volto.po +5500 -0
  125. package/locales/te.json +1 -1
  126. package/locales/th/LC_MESSAGES/volto.po +5500 -0
  127. package/locales/th.json +1 -1
  128. package/locales/to/LC_MESSAGES/volto.po +5500 -0
  129. package/locales/to.json +1 -1
  130. package/locales/tr/LC_MESSAGES/volto.po +5501 -0
  131. package/locales/tr.json +1 -1
  132. package/locales/uk/LC_MESSAGES/volto.po +5500 -0
  133. package/locales/uk.json +1 -1
  134. package/locales/vi/LC_MESSAGES/volto.po +5500 -0
  135. package/locales/vi.json +1 -1
  136. package/locales/volto.pot +220 -10
  137. package/locales/zh_CN/LC_MESSAGES/volto.po +221 -10
  138. package/locales/zh_CN.json +1 -1
  139. package/locales/zh_Hant/LC_MESSAGES/volto.po +5500 -0
  140. package/locales/zh_Hant.json +1 -1
  141. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5500 -0
  142. package/locales/zh_Hant_HK.json +1 -1
  143. package/package.json +72 -146
  144. package/razzle.config.js +32 -25
  145. package/src/actions/blockTypes/blockTypes.ts +24 -0
  146. package/src/components/manage/Actions/Actions.test.jsx +1 -5
  147. package/src/components/manage/Add/Add.jsx +15 -10
  148. package/src/components/manage/BlockChooser/BlockChooser.jsx +1 -0
  149. package/src/components/manage/Blocks/Block/BlocksForm.jsx +10 -7
  150. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +3 -14
  151. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +9 -4
  152. package/src/components/manage/Blocks/Block/Order/Item.jsx +27 -13
  153. package/src/components/manage/Blocks/Block/Order/Item.test.jsx +90 -0
  154. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  155. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  156. package/src/components/manage/Blocks/Block/Settings.test.jsx +1 -5
  157. package/src/components/manage/Blocks/Grid/View.jsx +14 -11
  158. package/src/components/manage/Blocks/Grid/context.js +3 -0
  159. package/src/components/manage/Blocks/HTML/Edit.test.jsx +1 -5
  160. package/src/components/manage/Blocks/Image/Edit.jsx +5 -1
  161. package/src/components/manage/Blocks/Image/ImageSidebar.test.jsx +2 -5
  162. package/src/components/manage/Blocks/LeadImage/Edit.jsx +2 -2
  163. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +1 -1
  164. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +2 -5
  165. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  166. package/src/components/manage/Blocks/Listing/ImageGallery.jsx +6 -4
  167. package/src/components/manage/Blocks/Listing/ListingBody.jsx +4 -0
  168. package/src/components/manage/Blocks/Maps/Edit.jsx +2 -1
  169. package/src/components/manage/Blocks/Maps/MapsSidebar.test.jsx +1 -5
  170. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +21 -4
  171. package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +1 -6
  172. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +22 -1
  173. package/src/components/manage/Blocks/Search/components/SelectFacet.test.jsx +1 -6
  174. package/src/components/manage/Blocks/Search/components/SortOn.jsx +8 -2
  175. package/src/components/manage/Blocks/Search/components/ToggleFacet.jsx +14 -0
  176. package/src/components/manage/Blocks/Teaser/Data.jsx +21 -7
  177. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +11 -3
  178. package/src/components/manage/Blocks/Teaser/View.jsx +0 -1
  179. package/src/components/manage/Blocks/Teaser/utils.js +13 -0
  180. package/src/components/manage/Blocks/Teaser/utils.test.js +34 -0
  181. package/src/components/manage/Blocks/Title/Edit.jsx +5 -0
  182. package/src/components/manage/Blocks/Video/Body.jsx +69 -43
  183. package/src/components/manage/Blocks/Video/Body.test.jsx +122 -5
  184. package/src/components/manage/Blocks/Video/Edit.jsx +22 -3
  185. package/src/components/manage/Blocks/Video/Edit.test.jsx +6 -0
  186. package/src/components/manage/Blocks/Video/VideoSidebar.test.jsx +1 -5
  187. package/src/components/manage/Blocks/Video/View.jsx +1 -0
  188. package/src/components/manage/Blocks/Video/View.test.jsx +29 -15
  189. package/src/components/manage/Blocks/Video/schema.js +14 -1
  190. package/src/components/manage/Contents/Contents.jsx +697 -659
  191. package/src/components/manage/Contents/Contents.test.jsx +1 -5
  192. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +6 -5
  193. package/src/components/manage/Contents/ContentsItem.jsx +1 -1
  194. package/src/components/manage/Contents/ContentsPropertiesModal.test.jsx +1 -5
  195. package/src/components/manage/Contents/ContentsRenameModal.test.jsx +1 -5
  196. package/src/components/manage/Contents/ContentsTagsModal.test.jsx +1 -5
  197. package/src/components/manage/Contents/ContentsWorkflowModal.test.jsx +1 -5
  198. package/src/components/manage/Contents/DropZoneContent.jsx +338 -0
  199. package/src/components/manage/Contents/__mocks__/index.tsx +2 -18
  200. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  201. package/src/components/manage/Controlpanels/Aliases.test.jsx +1 -5
  202. package/src/components/manage/Controlpanels/BlockType.tsx +166 -0
  203. package/src/components/manage/Controlpanels/BlockTypes.tsx +145 -0
  204. package/src/components/manage/Controlpanels/ContentType.jsx +1 -1
  205. package/src/components/manage/Controlpanels/ContentType.test.jsx +1 -5
  206. package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -1
  207. package/src/components/manage/Controlpanels/Controlpanels.jsx +28 -5
  208. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +10 -0
  209. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  210. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +3 -2
  211. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  212. package/src/components/manage/Controlpanels/Relations/Relations.jsx +1 -1
  213. package/src/components/manage/Controlpanels/Rules/AddRule.test.jsx +1 -5
  214. package/src/components/manage/Controlpanels/Rules/EditRule.test.jsx +1 -5
  215. package/src/components/manage/Controlpanels/UndoControlpanel.test.jsx +1 -5
  216. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +156 -175
  217. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +3 -0
  218. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +575 -631
  219. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +58 -11
  220. package/src/components/manage/Diff/Diff.jsx +201 -298
  221. package/src/components/manage/Diff/Diff.test.jsx +1 -6
  222. package/src/components/manage/Diff/DiffField.test.jsx +1 -6
  223. package/src/components/manage/Display/Display.test.jsx +2 -11
  224. package/src/components/manage/Edit/Edit.test.jsx +1 -5
  225. package/src/components/manage/Form/BlockDataForm.test.jsx +1 -5
  226. package/src/components/manage/Form/Form.jsx +3 -3
  227. package/src/components/manage/Form/Form.test.jsx +1 -5
  228. package/src/components/manage/Form/InlineForm.jsx +2 -2
  229. package/src/components/manage/Form/InlineForm.test.jsx +1 -5
  230. package/src/components/manage/Form/ModalForm.jsx +12 -10
  231. package/src/components/manage/Form/ModalForm.test.jsx +27 -5
  232. package/src/components/manage/Form/__mocks__/index.tsx +9 -27
  233. package/src/components/manage/Multilingual/CompareLanguages.jsx +6 -6
  234. package/src/components/manage/Multilingual/CreateTranslation.jsx +16 -13
  235. package/src/components/manage/Multilingual/ManageTranslations.jsx +5 -5
  236. package/src/components/manage/Multilingual/TranslationObject.jsx +11 -8
  237. package/src/components/manage/Preferences/ChangePassword.test.jsx +1 -5
  238. package/src/components/manage/Preferences/PersonalPreferences.jsx +8 -5
  239. package/src/components/manage/Preferences/PersonalPreferences.test.jsx +1 -17
  240. package/src/components/manage/Sharing/Sharing.jsx +21 -15
  241. package/src/components/manage/Sidebar/ObjectBrowser.jsx +3 -0
  242. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +18 -2
  243. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
  244. package/src/components/manage/Sidebar/SidebarPortal.test.tsx +42 -0
  245. package/src/components/manage/Sidebar/SidebarPortal.tsx +48 -0
  246. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +2 -1
  247. package/src/components/manage/TemplateChooser/TemplateChooser.test.jsx +1 -0
  248. package/src/components/manage/Toast/Toast.jsx +32 -0
  249. package/src/components/manage/Toast/Toast.test.jsx +9 -5
  250. package/src/components/manage/Toolbar/PersonalTools.jsx +2 -1
  251. package/src/components/manage/Toolbar/PersonalTools.test.jsx +15 -0
  252. package/src/components/manage/Toolbar/Toolbar.jsx +14 -4
  253. package/src/components/manage/Toolbar/Types.crash.test.jsx +48 -0
  254. package/src/components/manage/Toolbar/Types.jsx +6 -4
  255. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +16 -0
  256. package/src/components/manage/UniversalLink/UniversalLink.tsx +2 -0
  257. package/src/components/manage/Widgets/AlignWidget.stories.jsx +9 -0
  258. package/src/components/manage/Widgets/AlignWidget.test.tsx +95 -0
  259. package/src/components/manage/Widgets/{AlignWidget.jsx → AlignWidget.tsx} +23 -7
  260. package/src/components/manage/Widgets/ArrayWidget.test.jsx +1 -6
  261. package/src/components/manage/Widgets/BlockAlignment.stories.tsx +104 -0
  262. package/src/components/manage/Widgets/BlockAlignment.test.tsx +104 -0
  263. package/src/components/manage/Widgets/BlockAlignment.tsx +88 -0
  264. package/src/components/manage/Widgets/BlockWidth.stories.tsx +69 -0
  265. package/src/components/manage/Widgets/BlockWidth.test.tsx +62 -0
  266. package/src/components/manage/Widgets/BlockWidth.tsx +101 -0
  267. package/src/components/manage/Widgets/ButtonsWidget.stories.jsx +61 -0
  268. package/src/components/manage/Widgets/ButtonsWidget.test.tsx +138 -0
  269. package/src/components/manage/Widgets/ButtonsWidget.tsx +195 -0
  270. package/src/components/manage/Widgets/CheckboxGroupWidget.test.jsx +1 -6
  271. package/src/components/manage/Widgets/DatetimeWidget.jsx +102 -53
  272. package/src/components/manage/Widgets/DatetimeWidget.test.jsx +56 -6
  273. package/src/components/manage/Widgets/FileWidget.jsx +21 -8
  274. package/src/components/manage/Widgets/FormFieldWrapper.jsx +146 -168
  275. package/src/components/manage/Widgets/ImageWidget.jsx +177 -38
  276. package/src/components/manage/Widgets/InternalUrlWidget.jsx +2 -0
  277. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +8 -0
  278. package/src/components/manage/Widgets/ObjectListWidget.test.jsx +2 -11
  279. package/src/components/manage/Widgets/ObjectWidget.test.jsx +1 -5
  280. package/src/components/manage/Widgets/QueryWidget.jsx +137 -9
  281. package/src/components/manage/Widgets/RadioGroupWidget.test.jsx +1 -6
  282. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.test.jsx +1 -6
  283. package/src/components/manage/Widgets/RegistryImageWidget.jsx +1 -1
  284. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +4 -2
  285. package/src/components/manage/Widgets/SchemaWidget.test.jsx +1 -6
  286. package/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx +1 -6
  287. package/src/components/manage/Widgets/SelectAutoComplete.jsx +29 -12
  288. package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +1 -6
  289. package/src/components/manage/Widgets/SelectWidget.test.jsx +1 -6
  290. package/src/components/manage/Widgets/Size.stories.tsx +69 -0
  291. package/src/components/manage/Widgets/Size.test.tsx +59 -0
  292. package/src/components/manage/Widgets/Size.tsx +78 -0
  293. package/src/components/manage/Widgets/TimeWidget.test.jsx +1 -6
  294. package/src/components/manage/Widgets/TokenWidget.test.jsx +1 -6
  295. package/src/components/manage/Widgets/UrlWidget.jsx +49 -18
  296. package/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx +2 -11
  297. package/src/components/manage/Widgets/__mocks__/index.tsx +33 -51
  298. package/src/components/manage/Widgets/index.tsx +21 -0
  299. package/src/components/manage/Workflow/Workflow.test.jsx +2 -11
  300. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -0
  301. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +30 -0
  302. package/src/components/theme/App/App.jsx +5 -1
  303. package/src/components/theme/Avatar/Avatar.jsx +2 -1
  304. package/src/components/theme/Comments/CommentEditModal.test.jsx +1 -5
  305. package/src/components/theme/Comments/Comments.test.jsx +2 -11
  306. package/src/components/theme/ConnectionRefused/ConnectionRefused.jsx +3 -2
  307. package/src/components/theme/ContactForm/ContactForm.test.jsx +1 -5
  308. package/src/components/theme/Image/Image.jsx +25 -13
  309. package/src/components/theme/Image/Image.test.jsx +247 -146
  310. package/src/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.tsx +7 -0
  311. package/src/components/theme/LanguageSelector/LanguageSelector.tsx +89 -0
  312. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +42 -12
  313. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  314. package/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx +1 -5
  315. package/src/components/theme/PreviewImage/PreviewImage.jsx +1 -1
  316. package/src/components/theme/Register/Register.test.jsx +1 -5
  317. package/src/components/theme/RequestTimeout/RequestTimeout.jsx +1 -1
  318. package/src/components/theme/Sitemap/Sitemap.stories.jsx +82 -0
  319. package/src/components/theme/SlotRenderer/SlotRenderer.tsx +12 -6
  320. package/src/components/theme/Unauthorized/Unauthorized.jsx +35 -25
  321. package/src/components/theme/Unauthorized/Unauthorized.test.jsx +28 -1
  322. package/src/components/theme/VideoEmbed/VideoEmbed.jsx +100 -0
  323. package/src/components/theme/View/EventDatesInfo.test.jsx +1 -6
  324. package/src/components/theme/View/EventView.stories.jsx +89 -0
  325. package/src/components/theme/View/EventView.test.jsx +1 -6
  326. package/src/components/theme/View/FileView.stories.jsx +50 -0
  327. package/src/components/theme/View/ImageView.jsx +2 -1
  328. package/src/components/theme/View/ImageView.test.jsx +3 -0
  329. package/src/components/theme/View/LinkView.stories.jsx +57 -0
  330. package/src/components/theme/View/ListingView.stories.jsx +70 -0
  331. package/src/components/theme/View/NewsItemView.stories.jsx +58 -0
  332. package/src/components/theme/View/RenderBlocks.jsx +8 -10
  333. package/src/components/theme/View/RenderBlocks.stories.jsx +112 -0
  334. package/src/components/theme/View/RenderBlocks.test.jsx +14 -4
  335. package/src/components/theme/View/SummaryView.stories.jsx +71 -0
  336. package/src/components/theme/View/TabularView.stories.jsx +66 -0
  337. package/src/components/theme/View/View.jsx +8 -1
  338. package/src/components/theme/Widgets/ImageWidget.jsx +2 -1
  339. package/src/components/theme/Widgets/ImageWidget.test.jsx +31 -11
  340. package/src/config/Blocks.jsx +3 -0
  341. package/src/config/ControlPanels.js +3 -0
  342. package/src/config/Widgets.jsx +7 -0
  343. package/src/config/index.js +19 -12
  344. package/src/config/server.js +0 -2
  345. package/src/config/slots.js +19 -0
  346. package/src/config/validation.ts +8 -0
  347. package/src/constants/ActionTypes.js +1 -0
  348. package/src/express-middleware/devproxy.js +22 -5
  349. package/src/express-middleware/files.js +1 -0
  350. package/src/express-middleware/files.test.js +59 -0
  351. package/src/express-middleware/images.js +1 -0
  352. package/src/express-middleware/images.test.js +50 -0
  353. package/src/helpers/Api/APIResourceWithAuth.js +8 -3
  354. package/src/helpers/Api/Api.js +7 -4
  355. package/src/helpers/AsyncConnect/ssr.js +4 -1
  356. package/src/helpers/AuthToken/AuthToken.js +1 -6
  357. package/src/helpers/Blocks/Blocks.js +113 -28
  358. package/src/helpers/Blocks/Blocks.test.js +100 -0
  359. package/src/helpers/Content/Content.js +23 -0
  360. package/src/helpers/Content/Content.test.js +39 -0
  361. package/src/helpers/Content/withClientSideContent.jsx +35 -0
  362. package/src/helpers/Extensions/withBlockSchemaEnhancer.jsx +4 -1
  363. package/src/helpers/FormValidation/FormValidation.test.js +31 -0
  364. package/src/helpers/FormValidation/validators.ts +52 -6
  365. package/src/helpers/Html/Html.jsx +13 -4
  366. package/src/helpers/Loadable/__mocks__/Loadable.jsx +7 -22
  367. package/src/helpers/MessageLabels/MessageLabels.js +10 -0
  368. package/src/helpers/Sitemap/Sitemap.js +4 -4
  369. package/src/helpers/Url/Url.js +33 -2
  370. package/src/helpers/Url/Url.test.js +62 -0
  371. package/src/helpers/Utils/Utils.jsx +17 -0
  372. package/src/helpers/Utils/Utils.test.jsx +39 -0
  373. package/src/hooks/user/useUser.js +1 -1
  374. package/src/internalChecks.test.ts +11 -0
  375. package/src/middleware/api.js +17 -8
  376. package/src/middleware/storeProtectLoadUtils.test.js +3 -3
  377. package/src/reducers/blockTypes/blockTypes.js +38 -0
  378. package/src/reducers/content/content.js +3 -18
  379. package/src/reducers/diff/diff.js +5 -1
  380. package/src/reducers/diff/diff.test.js +60 -4
  381. package/src/reducers/index.js +2 -0
  382. package/src/reducers/querystring/querystring.js +8 -1
  383. package/src/reducers/users/users.js +1 -1
  384. package/src/routes.js +13 -1
  385. package/src/server.jsx +47 -13
  386. package/src/start-client.jsx +9 -2
  387. package/src/start-server.js +9 -3
  388. package/test-addons-loader.js +3 -0
  389. package/test-setup-globals.js +56 -2
  390. package/theme/themes/default/elements/segment.variables +9 -16
  391. package/theme/themes/pastanaga/collections/form.overrides +1 -1
  392. package/theme/themes/pastanaga/elements/segment.variables +1 -4
  393. package/theme/themes/pastanaga/extras/block-types.less +17 -0
  394. package/theme/themes/pastanaga/extras/blocks.less +19 -0
  395. package/theme/themes/pastanaga/extras/contents.less +75 -0
  396. package/theme/themes/pastanaga/extras/main.less +20 -4
  397. package/theme/themes/pastanaga/extras/toolbar.less +10 -5
  398. package/theme/themes/pastanaga/extras/videoembed.less +22 -0
  399. package/theme/themes/pastanaga/extras/widgets.less +79 -0
  400. package/tsconfig.declarations.json +1 -1
  401. package/tsconfig.json +4 -5
  402. package/types/actions/blockTypes/blockTypes.d.ts +7 -0
  403. package/types/components/index.d.ts +1 -1
  404. package/types/components/manage/Blocks/Block/Order/Item.test.d.ts +1 -0
  405. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  406. package/types/components/manage/Blocks/Grid/context.d.ts +1 -0
  407. package/types/components/manage/Blocks/Teaser/utils.d.ts +5 -0
  408. package/types/components/manage/Blocks/Video/Body.d.ts +4 -2
  409. package/types/components/manage/Blocks/Video/schema.d.ts +4 -0
  410. package/types/components/manage/Contents/DropZoneContent.d.ts +2 -0
  411. package/types/components/manage/Contents/__mocks__/index.d.ts +2 -2
  412. package/types/components/manage/Controlpanels/BlockType.d.ts +7 -0
  413. package/types/components/manage/Controlpanels/BlockTypes.d.ts +7 -0
  414. package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
  415. package/types/components/manage/Controlpanels/Users/RenderUsers.d.ts +18 -2
  416. package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +6 -2
  417. package/types/components/manage/Controlpanels/index.d.ts +2 -2
  418. package/types/components/manage/Diff/Diff.d.ts +7 -2
  419. package/types/components/manage/Form/__mocks__/index.d.ts +8 -8
  420. package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
  421. package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
  422. package/types/components/manage/Sidebar/SidebarPortal.d.ts +7 -15
  423. package/types/components/manage/Toolbar/Types.crash.test.d.ts +1 -0
  424. package/types/components/manage/Widgets/AlignWidget.d.ts +8 -10
  425. package/types/components/manage/Widgets/AlignWidget.stories.d.ts +1 -0
  426. package/types/components/manage/Widgets/BlockAlignment.d.ts +7 -0
  427. package/types/components/manage/Widgets/BlockAlignment.stories.d.ts +8 -0
  428. package/types/components/manage/Widgets/BlockWidth.d.ts +7 -0
  429. package/types/components/manage/Widgets/BlockWidth.stories.d.ts +6 -0
  430. package/types/components/manage/Widgets/ButtonsWidget.d.ts +48 -1
  431. package/types/components/manage/Widgets/ButtonsWidget.stories.d.ts +3 -0
  432. package/types/components/manage/Widgets/FormFieldWrapper.d.ts +28 -5
  433. package/types/components/manage/Widgets/ImageWidget.d.ts +41 -1
  434. package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
  435. package/types/components/manage/Widgets/ObjectBrowserWidget.d.ts +2 -0
  436. package/types/components/manage/Widgets/QueryWidget.d.ts +5 -2
  437. package/types/components/manage/Widgets/RecurrenceWidget/Utils.d.ts +12 -18
  438. package/types/components/manage/Widgets/Size.d.ts +7 -0
  439. package/types/components/manage/Widgets/Size.stories.d.ts +6 -0
  440. package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
  441. package/types/components/manage/Widgets/__mocks__/index.d.ts +33 -33
  442. package/types/components/manage/Widgets/index.d.ts +11 -6
  443. package/types/components/theme/ConnectionRefused/ConnectionRefused.d.ts +2 -2
  444. package/types/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.d.ts +3 -0
  445. package/types/components/theme/LanguageSelector/LanguageSelector.d.ts +3 -10
  446. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  447. package/types/components/theme/Sitemap/Sitemap.stories.d.ts +13 -0
  448. package/types/components/theme/SlotRenderer/SlotRenderer.d.ts +4 -5
  449. package/types/components/theme/Unauthorized/Unauthorized.d.ts +2 -2
  450. package/types/components/theme/VideoEmbed/VideoEmbed.d.ts +2 -0
  451. package/types/components/theme/View/EventView.stories.d.ts +19 -0
  452. package/types/components/theme/View/FileView.stories.d.ts +18 -0
  453. package/types/components/theme/View/LinkView.stories.d.ts +18 -0
  454. package/types/components/theme/View/ListingView.stories.d.ts +24 -0
  455. package/types/components/theme/View/NewsItemView.stories.d.ts +23 -0
  456. package/types/components/theme/View/RenderBlocks.stories.d.ts +23 -0
  457. package/types/components/theme/View/SummaryView.stories.d.ts +23 -0
  458. package/types/components/theme/View/TabularView.stories.d.ts +23 -0
  459. package/types/config/ControlPanels.d.ts +1 -0
  460. package/types/config/Views.d.ts +1 -1
  461. package/types/config/Widgets.d.ts +6 -0
  462. package/types/config/slots.d.ts +7 -0
  463. package/types/constants/ActionTypes.d.ts +1 -0
  464. package/types/helpers/Blocks/Blocks.d.ts +4 -0
  465. package/types/helpers/Content/Content.d.ts +7 -0
  466. package/types/helpers/Content/withClientSideContent.d.ts +1 -0
  467. package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +4 -5
  468. package/types/helpers/FormValidation/validators.d.ts +18 -1
  469. package/types/helpers/Helmet/Helmet.d.ts +1 -1
  470. package/types/helpers/Loadable/__mocks__/Loadable.d.ts +2 -2
  471. package/types/helpers/MessageLabels/MessageLabels.d.ts +105 -93
  472. package/types/helpers/Url/Url.d.ts +14 -0
  473. package/types/helpers/Url/bulkFlattenToAppURL.d.ts +5 -0
  474. package/types/helpers/Utils/Utils.d.ts +1 -0
  475. package/types/reducers/blockTypes/blockTypes.d.ts +16 -0
  476. package/types/reducers/index.d.ts +3 -0
  477. package/types/routes.d.ts +7 -5
  478. package/types/start-client.d.ts +0 -1
  479. package/vitest.config.mjs +84 -42
  480. package/webpack-plugins/webpack-less-plugin.js +1 -1
  481. package/webpack-plugins/webpack-scss-plugin.js +172 -0
  482. package/cypress/downloads/downloads.html +0 -0
  483. package/jest-addons-loader.js +0 -3
  484. package/jest-extender-plugin.js +0 -39
  485. package/jest-setup-afterenv.js +0 -2
  486. package/jest-svgsystem-transform.js +0 -10
  487. package/package-why.json +0 -34
  488. package/patches/patchit.sh +0 -2
  489. package/patches/razzle-jest.patch +0 -10
  490. package/src/components/manage/Contents/__mocks__/index.vitest.tsx +0 -5
  491. package/src/components/manage/Form/__mocks__/index.vitest.tsx +0 -73
  492. package/src/components/manage/Sidebar/SidebarPortal.jsx +0 -47
  493. package/src/components/manage/Sidebar/SidebarPortal.test.jsx +0 -26
  494. package/src/components/manage/Widgets/AlignWidget.test.jsx +0 -59
  495. package/src/components/manage/Widgets/ButtonsWidget.jsx +0 -41
  496. package/src/components/manage/Widgets/ButtonsWidget.test.jsx +0 -70
  497. package/src/components/manage/Widgets/__mocks__/index.vitest.tsx +0 -41
  498. package/src/components/theme/LanguageSelector/LanguageSelector.jsx +0 -79
  499. package/src/helpers/Loadable/__mocks__/Loadable.vitest.jsx +0 -39
  500. package/test-setup-globals-vitest.js +0 -46
  501. package/theme/themes/pastanaga/extras/utils.less +0 -63
  502. /package/src/components/theme/LanguageSelector/{LanguageSelector.test.jsx → LanguageSelector.test.tsx} +0 -0
@@ -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';
@@ -33,13 +34,13 @@ import find from 'lodash/find';
33
34
  import map from 'lodash/map';
34
35
  import pull from 'lodash/pull';
35
36
  import difference from 'lodash/difference';
36
- import PropTypes from 'prop-types';
37
- import React, { Component } from 'react';
38
- import { FormattedMessage, injectIntl } from 'react-intl';
37
+
38
+ import { useState, useEffect, useCallback } from 'react';
39
+ import { FormattedMessage, useIntl } from 'react-intl';
39
40
  import { createPortal } from 'react-dom';
40
- import { connect } from 'react-redux';
41
+ import { useSelector, useDispatch } from 'react-redux';
42
+ import { useLocation } from 'react-router-dom';
41
43
  import { toast } from 'react-toastify';
42
- import { bindActionCreators, compose } from 'redux';
43
44
  import {
44
45
  Confirm,
45
46
  Container,
@@ -53,151 +54,132 @@ import {
53
54
  } from 'semantic-ui-react';
54
55
 
55
56
  /**
56
- * UsersControlpanel class.
57
- * @class UsersControlpanel
58
- * @extends Component
57
+ * UsersControlpanel functional component.
58
+ * @function UsersControlpanel
59
59
  */
60
- class UsersControlpanel extends Component {
61
- /**
62
- * Property types.
63
- * @property {Object} propTypes Property types.
64
- * @static
65
- */
66
- static propTypes = {
67
- listRoles: PropTypes.func.isRequired,
68
- listUsers: PropTypes.func.isRequired,
69
- updateUser: PropTypes.func,
70
- listGroups: PropTypes.func.isRequired,
71
- pathname: PropTypes.string.isRequired,
72
- roles: PropTypes.arrayOf(
73
- PropTypes.shape({
74
- '@id': PropTypes.string,
75
- '@type': PropTypes.string,
76
- id: PropTypes.string,
77
- }),
78
- ).isRequired,
79
- users: PropTypes.arrayOf(
80
- PropTypes.shape({
81
- username: PropTypes.string,
82
- fullname: PropTypes.string,
83
- roles: PropTypes.arrayOf(PropTypes.string),
84
- }),
85
- ).isRequired,
86
- user: PropTypes.shape({
87
- '@id': PropTypes.string,
88
- id: PropTypes.string,
89
- description: PropTypes.string,
90
- email: PropTypes.string,
91
- fullname: PropTypes.string,
92
- groups: PropTypes.object,
93
- location: PropTypes.string,
94
- portrait: PropTypes.string,
95
- home_page: PropTypes.string,
96
- roles: PropTypes.arrayOf(PropTypes.string),
97
- username: PropTypes.string,
98
- }).isRequired,
99
- };
60
+ const UsersControlpanel = () => {
61
+ const intl = useIntl();
62
+ const dispatch = useDispatch();
100
63
 
101
- /**
102
- * Constructor
103
- * @method constructor
104
- * @param {Object} props Component properties
105
- * @constructs Sharing
106
- */
107
- constructor(props) {
108
- super(props);
109
- this.onChangeSearch = this.onChangeSearch.bind(this);
110
- this.onSearch = this.onSearch.bind(this);
111
- this.delete = this.delete.bind(this);
112
-
113
- this.onDeleteOk = this.onDeleteOk.bind(this);
114
- this.onDeleteCancel = this.onDeleteCancel.bind(this);
115
- this.onAddUserSubmit = this.onAddUserSubmit.bind(this);
116
- this.onAddUserError = this.onAddUserError.bind(this);
117
- this.onAddUserSuccess = this.onAddUserSuccess.bind(this);
118
- this.updateUserRole = this.updateUserRole.bind(this);
119
- this.state = {
120
- search: '',
121
- isLoading: false,
122
- showAddUser: false,
123
- showAddUserErrorConfirm: false,
124
- addUserError: '',
125
- showDelete: false,
126
- userToDelete: undefined,
127
- entries: [],
128
- isClient: false,
129
- currentPage: 0,
130
- pageSize: 10,
131
- loginUsingEmail: false,
132
- };
133
- }
64
+ // Redux state selectors
65
+ const roles = useSelector((state) => state.roles.roles);
66
+ const users = useSelector((state) => state.users.users);
67
+ const user = useSelector((state) => state.users.user);
68
+ const userId = useSelector((state) =>
69
+ state.userSession.token ? jwtDecode(state.userSession.token).sub : '',
70
+ );
71
+ const groups = useSelector((state) => state.groups.groups);
72
+ const many_users = useSelector(
73
+ (state) => state.controlpanels?.controlpanel?.data?.many_users,
74
+ );
134
75
 
135
- fetchData = async () => {
136
- await this.props.getControlpanel('usergroup');
137
- await this.props.listRoles();
138
- if (!this.props.many_users) {
139
- this.props.listGroups();
140
- await this.props.listUsers();
141
- this.setState({
142
- entries: this.props.users,
143
- });
144
- }
145
- await this.props.getUserSchema();
146
- await this.props.getUser(this.props.userId);
147
- };
76
+ const location = useLocation();
77
+ const pathname = location.pathname;
78
+ const deleteRequest = useSelector((state) => state.users.delete);
79
+ const createRequest = useSelector((state) => state.users.create);
80
+ const loadRolesRequest = useSelector((state) => state.roles);
81
+ const inheritedRole = useSelector(
82
+ (state) => state.authRole.authenticatedRole,
83
+ );
84
+ const userschema = useSelector((state) => state.userschema);
85
+ const controlPanelData = useSelector(
86
+ (state) => state.controlpanels?.controlpanel,
87
+ );
148
88
 
149
- // Because username field needs to be disabled if email login is enabled!
150
- checkLoginUsingEmailStatus = async () => {
151
- await this.props.getControlpanel('security');
152
- this.setState({
153
- loginUsingEmail: this.props.controlPanelData?.data.use_email_as_login,
154
- });
155
- };
89
+ // Action creators
90
+ const listRolesAction = useCallback(() => dispatch(listRoles()), [dispatch]);
91
+ const listUsersAction = useCallback(
92
+ (params) => dispatch(listUsers(params)),
93
+ [dispatch],
94
+ );
95
+ const listGroupsAction = useCallback(
96
+ () => dispatch(listGroups()),
97
+ [dispatch],
98
+ );
99
+ const getControlpanelAction = useCallback(
100
+ (panel) => dispatch(getControlpanel(panel)),
101
+ [dispatch],
102
+ );
103
+ const deleteUserAction = useCallback(
104
+ (userId) => dispatch(deleteUser(userId)),
105
+ [dispatch],
106
+ );
107
+ const updateUserAction = useCallback(
108
+ (userId, data) => dispatch(updateUser(userId, data)),
109
+ [dispatch],
110
+ );
111
+ const updateGroupAction = useCallback(
112
+ (groupId, data) => dispatch(updateGroup(groupId, data)),
113
+ [dispatch],
114
+ );
115
+ const getUserSchemaAction = useCallback(
116
+ () => dispatch(getUserSchema()),
117
+ [dispatch],
118
+ );
119
+ const getUserAction = useCallback(
120
+ (userId) => dispatch(getUser(userId)),
121
+ [dispatch],
122
+ );
123
+
124
+ const [search, setSearch] = useState('');
125
+ const [isLoading, setIsLoading] = useState(false);
126
+ const [showAddUser, setShowAddUser] = useState(false);
127
+ const [addUserError, setAddUserError] = useState('');
128
+ const [showDelete, setShowDelete] = useState(false);
129
+ const [userToDelete, setUserToDelete] = useState(undefined);
130
+ const [entries, setEntries] = useState([]);
131
+ const [isClient, setIsClient] = useState(false);
132
+ const [currentPage, setCurrentPage] = useState(0);
133
+ const [pageSize] = useState(10);
134
+ // eslint-disable-next-line no-unused-vars
135
+ const [loginUsingEmail, setLoginUsingEmail] = useState(false); // Reserved for future use to disable username field when email login is enabled
136
+ const [error, setError] = useState(null);
137
+
138
+ const fetchData = useCallback(async () => {
139
+ await getControlpanelAction('usergroup');
140
+ await listRolesAction();
141
+ if (!many_users) {
142
+ listGroupsAction();
143
+ await listUsersAction();
144
+ setEntries(users);
145
+ }
146
+ await getUserSchemaAction();
147
+ await getUserAction(userId);
148
+ }, [
149
+ getControlpanelAction,
150
+ listRolesAction,
151
+ many_users,
152
+ listGroupsAction,
153
+ listUsersAction,
154
+ users,
155
+ getUserSchemaAction,
156
+ getUserAction,
157
+ userId,
158
+ ]);
156
159
 
157
160
  /**
158
- * Component did mount
159
- * @method componentDidMount
161
+ * Check login using email status from security control panel
162
+ * @method checkLoginUsingEmailStatus
160
163
  * @returns {undefined}
161
164
  */
162
- componentDidMount() {
163
- this.setState({
164
- isClient: true,
165
- });
166
- this.fetchData();
167
- this.checkLoginUsingEmailStatus();
168
- }
169
-
170
- UNSAFE_componentWillReceiveProps(nextProps) {
171
- if (
172
- (this.props.deleteRequest.loading && nextProps.deleteRequest.loaded) ||
173
- (this.props.createRequest.loading && nextProps.createRequest.loaded)
174
- ) {
175
- this.props.listUsers({
176
- search: this.state.search,
177
- });
178
- }
179
- if (this.props.deleteRequest.loading && nextProps.deleteRequest.loaded) {
180
- this.onDeleteUserSuccess();
181
- }
182
- if (this.props.createRequest.loading && nextProps.createRequest.loaded) {
183
- this.onAddUserSuccess();
184
- }
185
- if (this.props.createRequest.loading && nextProps.createRequest.error) {
186
- this.onAddUserError(nextProps.createRequest.error);
187
- }
188
- if (
189
- this.props.loadRolesRequest.loading &&
190
- nextProps.loadRolesRequest.error
191
- ) {
192
- this.setState({
193
- error: nextProps.loadRolesRequest.error,
194
- });
165
+ const checkLoginUsingEmailStatus = useCallback(async () => {
166
+ try {
167
+ await getControlpanelAction('security');
168
+ if (controlPanelData?.data?.use_email_as_login) {
169
+ setLoginUsingEmail(controlPanelData.data.use_email_as_login);
170
+ }
171
+ } catch (error) {
172
+ // eslint-disable-next-line no-console
173
+ console.error('Error fetching security control panel', error);
195
174
  }
196
- }
175
+ }, [getControlpanelAction, controlPanelData]);
197
176
 
198
- getUserFromProps(value) {
199
- return find(this.props.users, ['@id', value]);
200
- }
177
+ const getUserFromProps = useCallback(
178
+ (value) => {
179
+ return find(users, ['@id', value]);
180
+ },
181
+ [users],
182
+ );
201
183
 
202
184
  /**
203
185
  * Search handler
@@ -205,22 +187,24 @@ class UsersControlpanel extends Component {
205
187
  * @param {object} event Event object.
206
188
  * @returns {undefined}
207
189
  */
208
- onSearch(event) {
209
- event.preventDefault();
210
- this.setState({ isLoading: true });
211
- this.props
212
- .listUsers({
213
- search: this.state.search,
214
- })
215
- .then(() => {
216
- this.setState({ isLoading: false });
190
+ const onSearch = useCallback(
191
+ (event) => {
192
+ event.preventDefault();
193
+ setIsLoading(true);
194
+ listUsersAction({
195
+ search: search,
217
196
  })
218
- .catch((error) => {
219
- this.setState({ isLoading: false });
220
- // eslint-disable-next-line no-console
221
- console.error('Error searching users', error);
222
- });
223
- }
197
+ .then(() => {
198
+ setIsLoading(false);
199
+ })
200
+ .catch((error) => {
201
+ setIsLoading(false);
202
+ // eslint-disable-next-line no-console
203
+ console.error('Error searching users', error);
204
+ });
205
+ },
206
+ [listUsersAction, search],
207
+ );
224
208
 
225
209
  /**
226
210
  * On change search handler
@@ -228,67 +212,94 @@ class UsersControlpanel extends Component {
228
212
  * @param {object} event Event object.
229
213
  * @returns {undefined}
230
214
  */
231
- onChangeSearch(event) {
232
- this.setState({
233
- search: event.target.value,
234
- });
235
- }
215
+ const onChangeSearch = (event) => {
216
+ setSearch(event.target.value);
217
+ };
236
218
 
237
219
  /**
238
- * Delete a user
239
- * @method delete
220
+ * Handle delete user click
221
+ * @method handleDeleteUser
240
222
  * @param {object} event Event object.
241
223
  * @param {string} value username.
242
224
  * @returns {undefined}
243
225
  */
244
- delete(event, { value }) {
245
- if (value) {
246
- this.setState({
247
- showDelete: true,
248
- userToDelete: this.getUserFromProps(value),
249
- });
250
- }
251
- }
226
+ const handleDeleteUser = useCallback(
227
+ (event, data) => {
228
+ // Handle both formats: direct value from event target or object with value
229
+ const value =
230
+ data?.value || event?.target?.value || event?.currentTarget?.value;
231
+ if (value) {
232
+ setShowDelete(true);
233
+ setUserToDelete(getUserFromProps(value));
234
+ }
235
+ },
236
+ [getUserFromProps],
237
+ );
252
238
 
253
239
  /**
254
240
  * On delete ok
255
241
  * @method onDeleteOk
256
242
  * @returns {undefined}
257
243
  */
258
- onDeleteOk() {
259
- if (this.state.userToDelete) {
260
- this.props.deleteUser(this.state.userToDelete.id);
244
+ const onDeleteOk = useCallback(() => {
245
+ if (userToDelete) {
246
+ const deleteAction = deleteUserAction(userToDelete.id);
247
+ if (deleteAction && typeof deleteAction.then === 'function') {
248
+ deleteAction
249
+ .then(() => {
250
+ // Handle success
251
+ setUserToDelete(undefined);
252
+ setShowDelete(false);
253
+
254
+ // Refresh users list
255
+ listUsersAction({ search: search });
256
+
257
+ // Show success message
258
+ toast.success(
259
+ <Toast
260
+ success
261
+ title={intl.formatMessage(messages.success)}
262
+ content={intl.formatMessage(messages.userDeleted)}
263
+ />,
264
+ );
265
+ })
266
+ .catch((error) => {
267
+ // Handle error
268
+ // eslint-disable-next-line no-console
269
+ console.error('Error deleting user', error);
270
+ });
271
+ }
261
272
  }
262
- }
273
+ }, [userToDelete, deleteUserAction, listUsersAction, search, intl]);
263
274
 
264
275
  /**
265
276
  * On delete cancel
266
277
  * @method onDeleteCancel
267
278
  * @returns {undefined}
268
279
  */
269
- onDeleteCancel() {
270
- this.setState({
271
- showDelete: false,
272
- itemsToDelete: [],
273
- userToDelete: undefined,
274
- });
275
- }
280
+ const onDeleteCancel = () => {
281
+ setShowDelete(false);
282
+ setUserToDelete(undefined);
283
+ };
276
284
 
277
285
  /**
278
286
  *@param {object} user
279
287
  *@returns {undefined}
280
288
  *@memberof UsersControlpanel
281
289
  */
282
- addUserToGroup = (user) => {
283
- const { groups, username } = user;
284
- groups.forEach((group) => {
285
- this.props.updateGroup(group, {
286
- users: {
287
- [username]: true,
288
- },
290
+ const addUserToGroup = useCallback(
291
+ (user) => {
292
+ const { groups: userGroups, username } = user;
293
+ userGroups.forEach((group) => {
294
+ updateGroupAction(group, {
295
+ users: {
296
+ [username]: true,
297
+ },
298
+ });
289
299
  });
290
- });
291
- };
300
+ },
301
+ [updateGroupAction],
302
+ );
292
303
 
293
304
  /**
294
305
  * Callback to be called by the ModalForm when the form is submitted.
@@ -297,131 +308,120 @@ class UsersControlpanel extends Component {
297
308
  * @param {func} callback to set new form data in the ModalForm
298
309
  * @returns {undefined}
299
310
  */
300
- onAddUserSubmit(data, callback) {
301
- const { groups, sendPasswordReset, password } = data;
302
- if (
303
- sendPasswordReset !== undefined &&
304
- sendPasswordReset === true &&
305
- password !== undefined
306
- ) {
307
- toast.error(
308
- <Toast
309
- error
310
- title={this.props.intl.formatMessage(messages.error)}
311
- content={this.props.intl.formatMessage(
312
- messages.addUserFormPasswordAndSendPasswordTogetherNotAllowed,
313
- )}
314
- />,
315
- );
316
- } else {
317
- if (groups && groups.length > 0) this.addUserToGroup(data);
318
- this.props.createUser(data, sendPasswordReset);
319
- this.setState({
320
- addUserSetFormDataCallback: callback,
321
- });
322
- }
323
- }
311
+ const onAddUserSubmit = useCallback(
312
+ (data, callback) => {
313
+ const { groups: userGroups, sendPasswordReset, password } = data;
314
+ if (
315
+ sendPasswordReset !== undefined &&
316
+ sendPasswordReset === true &&
317
+ password !== undefined
318
+ ) {
319
+ toast.error(
320
+ <Toast
321
+ error
322
+ title={intl.formatMessage(messages.error)}
323
+ content={intl.formatMessage(
324
+ messages.addUserFormPasswordAndSendPasswordTogetherNotAllowed,
325
+ )}
326
+ />,
327
+ );
328
+ } else {
329
+ if (userGroups && userGroups.length > 0) addUserToGroup(data);
324
330
 
325
- /**
326
- * Handle Success after createUser()
327
- *
328
- * @returns {undefined}
329
- */
330
- onAddUserSuccess() {
331
- this.state.addUserSetFormDataCallback({});
332
- this.setState({
333
- showAddUser: false,
334
- addUserError: undefined,
335
- addUserSetFormDataCallback: undefined,
336
- });
337
- toast.success(
338
- <Toast
339
- success
340
- title={this.props.intl.formatMessage(messages.success)}
341
- content={this.props.intl.formatMessage(messages.userCreated)}
342
- />,
343
- );
344
- }
331
+ const createUserAction = createUser(data, sendPasswordReset);
332
+ dispatch(createUserAction)
333
+ .then(() => {
334
+ // Handle success
335
+ if (callback) {
336
+ callback({});
337
+ }
338
+ setShowAddUser(false);
339
+ setAddUserError(undefined);
340
+
341
+ // Refresh users list
342
+ listUsersAction({ search: search });
343
+
344
+ // Show success message
345
+ toast.success(
346
+ <Toast
347
+ success
348
+ title={intl.formatMessage(messages.success)}
349
+ content={intl.formatMessage(messages.userCreated)}
350
+ />,
351
+ );
352
+ })
353
+ .catch((error) => {
354
+ // Handle error
355
+ setAddUserError(getErrorMessage(error));
356
+ });
357
+ }
358
+ },
359
+ [intl, addUserToGroup, dispatch, search, listUsersAction],
360
+ );
345
361
 
346
362
  /**
347
- * Handle Success after deleteUser()
348
- *
349
- * @returns {undefined}
350
- */
351
- onDeleteUserSuccess() {
352
- this.setState({
353
- userToDelete: undefined,
354
- showDelete: false,
355
- });
356
- toast.success(
357
- <Toast
358
- success
359
- title={this.props.intl.formatMessage(messages.success)}
360
- content={this.props.intl.formatMessage(messages.userDeleted)}
361
- />,
362
- );
363
- }
364
- /**
365
- *
366
- *
367
- * @param {*} data
368
- * @param {*} callback
369
- * @memberof UsersControlpanel
363
+ * Update user role
364
+ * @param {*} name
365
+ * @param {*} value
370
366
  */
371
- updateUserRole(name, value) {
372
- this.setState({
373
- entries: map(this.state.entries, (entry) => ({
374
- ...entry,
375
- roles:
376
- entry.id === name && !entry.roles.includes(value)
377
- ? [...entry.roles, value]
378
- : entry.id !== name
379
- ? entry.roles
380
- : pull(entry.roles, value),
381
- })),
382
- });
383
- }
367
+ const updateUserRole = useCallback(
368
+ (name, value) => {
369
+ setEntries(
370
+ map(entries, (entry) => ({
371
+ ...entry,
372
+ roles:
373
+ entry.id === name && !entry.roles.includes(value)
374
+ ? [...entry.roles, value]
375
+ : entry.id !== name
376
+ ? entry.roles
377
+ : pull(entry.roles, value),
378
+ })),
379
+ );
380
+ },
381
+ [entries],
382
+ );
383
+
384
384
  /**
385
- *
385
+ * Update user role submit
386
386
  * @param {*} event
387
- * @memberof UsersControlpanel
388
387
  */
389
- updateUserRoleSubmit = (e) => {
390
- e.stopPropagation();
388
+ const updateUserRoleSubmit = useCallback(
389
+ (e) => {
390
+ e.stopPropagation();
391
391
 
392
- const roles = this.props.roles.map((item) => item.id);
393
- this.state.entries.forEach((item) => {
394
- const userData = { roles: {} };
395
- const removedRoles = difference(roles, item.roles);
392
+ const roleIds = roles.map((item) => item.id);
393
+ entries.forEach((item) => {
394
+ const userData = { roles: {} };
395
+ const removedRoles = difference(roleIds, item.roles);
396
396
 
397
- removedRoles.forEach((role) => {
398
- userData.roles[role] = false;
399
- });
400
- item.roles.forEach((role) => {
401
- userData.roles[role] = true;
397
+ removedRoles.forEach((role) => {
398
+ userData.roles[role] = false;
399
+ });
400
+ item.roles.forEach((role) => {
401
+ userData.roles[role] = true;
402
+ });
403
+ updateUserAction(item.id, userData);
402
404
  });
403
- this.props.updateUser(item.id, userData);
404
- });
405
- toast.success(
406
- <Toast
407
- success
408
- title={this.props.intl.formatMessage(messages.success)}
409
- content={this.props.intl.formatMessage(messages.updateRoles)}
410
- />,
411
- );
412
- };
405
+ toast.success(
406
+ <Toast
407
+ success
408
+ title={intl.formatMessage(messages.success)}
409
+ content={intl.formatMessage(messages.updateRoles)}
410
+ />,
411
+ );
412
+ },
413
+ [roles, entries, updateUserAction, intl],
414
+ );
413
415
 
414
416
  /**
415
417
  * Handle Errors after createUser()
416
418
  *
417
- * @param {object} error object. Requires the property .message
419
+ * @param {object} error object
418
420
  * @returns {undefined}
419
421
  */
420
- onAddUserError(error) {
421
- this.setState({
422
- addUserError: error.response.body.error.message,
423
- });
424
- }
422
+ const onAddUserError = useCallback((error) => {
423
+ setAddUserError(getErrorMessage(error));
424
+ }, []);
425
425
 
426
426
  /**
427
427
  * On change page
@@ -430,359 +430,303 @@ class UsersControlpanel extends Component {
430
430
  * @param {string} value Page value.
431
431
  * @returns {undefined}
432
432
  */
433
- onChangePage = (event, { value }) => {
434
- this.setState({
435
- currentPage: value,
436
- });
433
+ const onChangePage = (event, { value }) => {
434
+ setCurrentPage(value);
437
435
  };
438
436
 
439
- componentDidUpdate(prevProps, prevState) {
440
- if (this.props.users !== prevProps.users) {
441
- this.setState({
442
- entries: this.props.users,
443
- });
444
- }
445
- }
446
-
447
437
  /**
448
438
  * Filters the roles a user can assign when adding a user.
449
439
  * @method canAssignAdd
450
440
  * @returns {arry}
451
441
  */
452
- canAssignAdd(isManager) {
453
- if (isManager) return this.props.roles;
454
- return this.props.user?.roles
455
- ? this.props.roles.filter((role) =>
456
- this.props.user.roles.includes(role.id),
457
- )
458
- : [];
459
- }
442
+ const canAssignAdd = useCallback(
443
+ (isManager) => {
444
+ if (isManager) return roles;
445
+ return user?.roles
446
+ ? roles.filter((role) => user.roles.includes(role.id))
447
+ : [];
448
+ },
449
+ [roles, user],
450
+ );
460
451
 
461
- /**
462
- * Render method.
463
- * @method render
464
- * @returns {string} Markup for the component.
465
- */
466
- render() {
467
- if (this.state.error) {
468
- return <Error error={this.state.error} />;
452
+ useEffect(() => {
453
+ setIsClient(true);
454
+ fetchData();
455
+ checkLoginUsingEmailStatus();
456
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
457
+
458
+ useEffect(() => {
459
+ setEntries(users);
460
+ }, [users]);
461
+
462
+ useEffect(() => {
463
+ if (createRequest?.error && !createRequest?.loading) {
464
+ onAddUserError(createRequest.error);
469
465
  }
470
- /*let fullnameToDelete = this.state.userToDelete
471
- ? this.state.userToDelete.fullname
472
- : '';*/
473
- let usernameToDelete = this.state.userToDelete
474
- ? this.state.userToDelete.username
475
- : '';
476
- // Copy the userschema using JSON serialization/deserialization
477
- // this is really ugly, but if we don't do this the original value
478
- // of the userschema is changed and it is used like that through
479
- // the lifecycle of the application
480
- let adduserschema = {};
481
- let isUserManager = false;
482
- if (this.props?.userschema?.loaded) {
483
- isUserManager = isManager(this.props.user);
484
- adduserschema = JSON.parse(
485
- JSON.stringify(this.props?.userschema?.userschema),
486
- );
487
- adduserschema.properties['username'] = {
488
- title: this.props.intl.formatMessage(messages.addUserFormUsernameTitle),
489
- type: 'string',
490
- description: this.props.intl.formatMessage(
491
- messages.addUserFormUsernameDescription,
492
- ),
493
- };
494
- adduserschema.properties['password'] = {
495
- title: this.props.intl.formatMessage(messages.addUserFormPasswordTitle),
496
- type: 'password',
497
- description: this.props.intl.formatMessage(
498
- messages.addUserFormPasswordDescription,
499
- ),
500
- widget: 'password',
501
- };
502
- adduserschema.properties['sendPasswordReset'] = {
503
- title: this.props.intl.formatMessage(
504
- messages.addUserFormSendPasswordResetTitle,
505
- ),
506
- type: 'boolean',
507
- };
508
- adduserschema.properties['roles'] = {
509
- title: this.props.intl.formatMessage(messages.addUserFormRolesTitle),
510
- type: 'array',
511
- choices: this.canAssignAdd(isUserManager).map((role) => [
512
- role.id,
513
- role.title,
514
- ]),
515
- noValueOption: false,
516
- };
517
- adduserschema.properties['groups'] = {
518
- title: this.props.intl.formatMessage(messages.addUserGroupNameTitle),
519
- type: 'array',
520
- choices: this.props.groups
521
- .filter((group) => canAssignGroup(isUserManager, group))
522
- .map((group) => [group.id, group.id]),
523
- noValueOption: false,
524
- };
525
- if (
526
- adduserschema.fieldsets &&
527
- adduserschema.fieldsets.length > 0 &&
528
- !adduserschema.fieldsets[0]['fields'].includes('username')
529
- ) {
530
- adduserschema.fieldsets[0]['fields'] = adduserschema.fieldsets[0][
531
- 'fields'
532
- ].concat([
466
+ }, [createRequest?.error, createRequest?.loading, onAddUserError]);
467
+
468
+ useEffect(() => {
469
+ if (loadRolesRequest?.error && !loadRolesRequest?.loading) {
470
+ setError(loadRolesRequest.error);
471
+ }
472
+ }, [loadRolesRequest?.error, loadRolesRequest?.loading]);
473
+
474
+ if (error) {
475
+ return <Error error={error} />;
476
+ }
477
+
478
+ const usernameToDelete = userToDelete ? userToDelete.username : '';
479
+
480
+ // Copy the userschema using JSON serialization/deserialization
481
+ // this is really ugly, but if we don't do this the original value
482
+ // of the userschema is changed and it is used like that through
483
+ // the lifecycle of the application
484
+ let adduserschema = {};
485
+ let isUserManager = false;
486
+ if (userschema?.loaded) {
487
+ isUserManager = isManager(user);
488
+ adduserschema = JSON.parse(JSON.stringify(userschema?.userschema));
489
+
490
+ // Add custom form fields to the schema
491
+ adduserschema.properties.username = {
492
+ title: intl.formatMessage(messages.addUserFormUsernameTitle),
493
+ type: 'string',
494
+ description: intl.formatMessage(messages.addUserFormUsernameDescription),
495
+ };
496
+
497
+ adduserschema.properties.password = {
498
+ title: intl.formatMessage(messages.addUserFormPasswordTitle),
499
+ type: 'password',
500
+ description: intl.formatMessage(messages.addUserFormPasswordDescription),
501
+ widget: 'password',
502
+ };
503
+
504
+ adduserschema.properties.sendPasswordReset = {
505
+ title: intl.formatMessage(messages.addUserFormSendPasswordResetTitle),
506
+ type: 'boolean',
507
+ };
508
+
509
+ adduserschema.properties.roles = {
510
+ title: intl.formatMessage(messages.addUserFormRolesTitle),
511
+ type: 'array',
512
+ choices: canAssignAdd(isUserManager).map((role) => [role.id, role.title]),
513
+ noValueOption: false,
514
+ };
515
+
516
+ adduserschema.properties.groups = {
517
+ title: intl.formatMessage(messages.addUserGroupNameTitle),
518
+ type: 'array',
519
+ choices: groups
520
+ .filter((group) => canAssignGroup(isUserManager, group))
521
+ .map((group) => [group.id, group.id]),
522
+ noValueOption: false,
523
+ };
524
+ // Add custom fields to the first fieldset if they don't already exist
525
+ if (
526
+ adduserschema.fieldsets &&
527
+ adduserschema.fieldsets.length > 0 &&
528
+ !adduserschema.fieldsets[0].fields.includes('username')
529
+ ) {
530
+ adduserschema.fieldsets[0].fields =
531
+ adduserschema.fieldsets[0].fields.concat([
533
532
  'username',
534
533
  'password',
535
534
  'sendPasswordReset',
536
535
  'roles',
537
536
  'groups',
538
537
  ]);
539
- }
540
538
  }
539
+ }
541
540
 
542
- return (
543
- <Container className="users-control-panel">
544
- <Helmet title={this.props.intl.formatMessage(messages.users)} />
545
- <div className="container">
546
- <Confirm
547
- open={this.state.showDelete}
548
- header={this.props.intl.formatMessage(
549
- messages.deleteUserConfirmTitle,
550
- )}
551
- content={
552
- <div className="content">
553
- <Dimmer active={this.props?.deleteRequest?.loading}>
554
- <Loader>
555
- <FormattedMessage id="Loading" defaultMessage="Loading." />
556
- </Loader>
557
- </Dimmer>
558
-
559
- <ul className="content">
560
- <FormattedMessage
561
- id="Do you really want to delete the user {username}?"
562
- defaultMessage="Do you really want to delete the user {username}?"
563
- values={{
564
- username: <b>{usernameToDelete}</b>,
565
- }}
566
- />
567
- </ul>
568
- </div>
569
- }
570
- onCancel={this.onDeleteCancel}
571
- onConfirm={this.onDeleteOk}
572
- size={null}
573
- />
574
- {this.props?.userschema?.loaded && this.state.showAddUser ? (
575
- <ModalForm
576
- open={this.state.showAddUser}
577
- className="modal"
578
- onSubmit={this.onAddUserSubmit}
579
- submitError={this.state.addUserError}
580
- onCancel={() =>
581
- this.setState({ showAddUser: false, addUserError: undefined })
582
- }
583
- title={this.props.intl.formatMessage(messages.addUserFormTitle)}
584
- loading={this.props.createRequest.loading}
585
- schema={adduserschema}
586
- />
587
- ) : null}
588
- </div>
589
- <Segment.Group raised>
590
- <Segment className="primary">
591
- <FormattedMessage id="Users" defaultMessage="Users" />
592
- </Segment>
593
- <Segment secondary>
594
- <FormattedMessage
595
- id="Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group."
596
- defaultMessage="Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group."
597
- values={{
598
- plone_svg: (
599
- <Icon
600
- name={ploneSVG}
601
- size="20px"
602
- color="#007EB1"
603
- title={'plone-svg'}
604
- />
605
- ),
606
- }}
607
- />
608
- </Segment>
609
- <Segment>
610
- <Form onSubmit={this.onSearch}>
611
- <Form.Field>
612
- <Input
613
- name="SearchableText"
614
- action={{
615
- icon: 'search',
616
- loading: this.state.isLoading,
617
- disabled: this.state.isLoading,
541
+ return (
542
+ <Container className="users-control-panel">
543
+ <Helmet title={intl.formatMessage(messages.users)} />
544
+ <div className="container">
545
+ <Confirm
546
+ open={showDelete}
547
+ header={intl.formatMessage(messages.deleteUserConfirmTitle)}
548
+ content={
549
+ <div className="content">
550
+ <Dimmer active={deleteRequest?.loading}>
551
+ <Loader>
552
+ <FormattedMessage id="Loading" defaultMessage="Loading." />
553
+ </Loader>
554
+ </Dimmer>
555
+
556
+ <ul className="content">
557
+ <FormattedMessage
558
+ id="Do you really want to delete the user {username}?"
559
+ defaultMessage="Do you really want to delete the user {username}?"
560
+ values={{
561
+ username: <b>{usernameToDelete}</b>,
618
562
  }}
619
- placeholder={this.props.intl.formatMessage(
620
- messages.searchUsers,
621
- )}
622
- onChange={this.onChangeSearch}
623
- id="user-search-input"
624
563
  />
625
- </Form.Field>
626
- </Form>
627
- </Segment>
628
- <Form>
629
- {((this.props.many_users && this.state.entries.length > 0) ||
630
- !this.props.many_users) && (
631
- <Table padded striped attached unstackable>
632
- <Table.Header>
633
- <Table.Row>
634
- <Table.HeaderCell>
635
- <FormattedMessage
636
- id="User name"
637
- defaultMessage="User name"
638
- />
639
- </Table.HeaderCell>
640
- {this.props.roles.map((role) => (
641
- <Table.HeaderCell key={role.id}>
642
- {role.title}
643
- </Table.HeaderCell>
644
- ))}
645
- <Table.HeaderCell>
646
- <FormattedMessage id="Actions" defaultMessage="Actions" />
647
- </Table.HeaderCell>
648
- </Table.Row>
649
- </Table.Header>
650
- <Table.Body data-user="users">
651
- {this.state.entries
652
- .slice(
653
- this.state.currentPage * 10,
654
- this.state.pageSize * (this.state.currentPage + 1),
655
- )
656
- .map((user) => (
657
- <RenderUsers
658
- key={user.id}
659
- onDelete={this.delete}
660
- roles={this.props.roles}
661
- user={user}
662
- updateUser={this.updateUserRole}
663
- inheritedRole={this.props.inheritedRole}
664
- userschema={this.props.userschema}
665
- listUsers={this.props.listUsers}
666
- isUserManager={isUserManager}
667
- />
668
- ))}
669
- </Table.Body>
670
- </Table>
671
- )}
672
- {this.state.entries.length === 0 && this.state.search && (
673
- <Segment>
674
- {this.props.intl.formatMessage(messages.userSearchNoResults)}
675
- </Segment>
676
- )}
677
- <div className="contents-pagination">
678
- <Pagination
679
- current={this.state.currentPage}
680
- total={Math.ceil(
681
- this.state.entries?.length / this.state.pageSize,
682
- )}
683
- onChangePage={this.onChangePage}
684
- />
564
+ </ul>
685
565
  </div>
566
+ }
567
+ onCancel={onDeleteCancel}
568
+ onConfirm={onDeleteOk}
569
+ size={null}
570
+ />
571
+ {userschema?.loaded && showAddUser ? (
572
+ <ModalForm
573
+ open={showAddUser}
574
+ className="modal"
575
+ onSubmit={onAddUserSubmit}
576
+ submitError={addUserError}
577
+ onCancel={() => {
578
+ setShowAddUser(false);
579
+ setAddUserError(undefined);
580
+ }}
581
+ title={intl.formatMessage(messages.addUserFormTitle)}
582
+ loading={createRequest?.loading}
583
+ schema={adduserschema}
584
+ />
585
+ ) : null}
586
+ </div>
587
+ <Segment.Group raised>
588
+ <Segment className="primary">
589
+ <FormattedMessage id="Users" defaultMessage="Users" />
590
+ </Segment>
591
+ <Segment secondary>
592
+ <FormattedMessage
593
+ id="Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group."
594
+ defaultMessage="Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group."
595
+ values={{
596
+ plone_svg: (
597
+ <Icon
598
+ name={ploneSVG}
599
+ size="20px"
600
+ color="#007EB1"
601
+ title={'plone-svg'}
602
+ />
603
+ ),
604
+ }}
605
+ />
606
+ </Segment>
607
+ <Segment>
608
+ <Form onSubmit={onSearch}>
609
+ <Form.Field>
610
+ <Input
611
+ name="SearchableText"
612
+ action={{
613
+ icon: 'search',
614
+ loading: isLoading,
615
+ disabled: isLoading,
616
+ }}
617
+ placeholder={intl.formatMessage(messages.searchUsers)}
618
+ onChange={onChangeSearch}
619
+ id="user-search-input"
620
+ />
621
+ </Form.Field>
686
622
  </Form>
687
- </Segment.Group>
688
- {this.state.isClient &&
689
- createPortal(
690
- <Toolbar
691
- pathname={this.props.pathname}
692
- hideDefaultViewButtons
693
- inner={
694
- <>
695
- <Button
696
- id="toolbar-save"
697
- className="save"
698
- aria-label={this.props.intl.formatMessage(messages.save)}
699
- onClick={this.updateUserRoleSubmit}
700
- loading={this.props.createRequest.loading}
701
- >
702
- <Icon
703
- name={saveSVG}
704
- className="circled"
705
- size="30px"
706
- title={this.props.intl.formatMessage(messages.save)}
707
- />
708
- </Button>
709
- <Link to="/controlpanel" className="cancel">
710
- <Icon
711
- name={clearSVG}
712
- className="circled"
713
- aria-label={this.props.intl.formatMessage(
714
- messages.cancel,
715
- )}
716
- size="30px"
717
- title={this.props.intl.formatMessage(messages.cancel)}
623
+ </Segment>
624
+ <Form>
625
+ {((many_users && entries.length > 0) || !many_users) && (
626
+ <Table padded striped attached unstackable>
627
+ <Table.Header>
628
+ <Table.Row>
629
+ <Table.HeaderCell>
630
+ <FormattedMessage
631
+ id="User name"
632
+ defaultMessage="User name"
718
633
  />
719
- </Link>
720
- <Button
721
- id="toolbar-add"
722
- aria-label={this.props.intl.formatMessage(
723
- messages.addUserButtonTitle,
724
- )}
725
- onClick={() => {
726
- this.setState({ showAddUser: true });
727
- }}
728
- loading={this.props.createRequest.loading}
729
- >
730
- <Icon
731
- name={addUserSvg}
732
- size="45px"
733
- color="#826A6A"
734
- title={this.props.intl.formatMessage(
735
- messages.addUserButtonTitle,
736
- )}
634
+ </Table.HeaderCell>
635
+ {roles.map((role) => (
636
+ <Table.HeaderCell key={role.id}>
637
+ {role.title}
638
+ </Table.HeaderCell>
639
+ ))}
640
+ <Table.HeaderCell>
641
+ <FormattedMessage id="Actions" defaultMessage="Actions" />
642
+ </Table.HeaderCell>
643
+ </Table.Row>
644
+ </Table.Header>
645
+ <Table.Body data-user="users">
646
+ {entries
647
+ .slice(currentPage * 10, pageSize * (currentPage + 1))
648
+ .map((userItem) => (
649
+ <RenderUsers
650
+ key={userItem.id}
651
+ onDelete={handleDeleteUser}
652
+ roles={roles}
653
+ user={userItem}
654
+ updateUser={updateUserRole}
655
+ inheritedRole={inheritedRole}
656
+ userschema={userschema}
657
+ listUsers={listUsersAction}
658
+ isUserManager={isUserManager}
737
659
  />
738
- </Button>
739
- </>
740
- }
741
- />,
742
- document.getElementById('toolbar'),
660
+ ))}
661
+ </Table.Body>
662
+ </Table>
743
663
  )}
744
- </Container>
745
- );
746
- }
747
- }
748
-
749
- export default compose(
750
- injectIntl,
751
- connect(
752
- (state, props) => ({
753
- roles: state.roles.roles,
754
- users: state.users.users,
755
- user: state.users.user,
756
- userId: state.userSession.token
757
- ? jwtDecode(state.userSession.token).sub
758
- : '',
759
- groups: state.groups.groups,
760
- many_users: state.controlpanels?.controlpanel?.data?.many_users,
761
- many_groups: state.controlpanels?.controlpanel?.data?.many_groups,
762
- description: state.description,
763
- pathname: props.location.pathname,
764
- deleteRequest: state.users.delete,
765
- createRequest: state.users.create,
766
- loadRolesRequest: state.roles,
767
- inheritedRole: state.authRole.authenticatedRole,
768
- userschema: state.userschema,
769
- controlPanelData: state.controlpanels?.controlpanel,
770
- }),
771
- (dispatch) =>
772
- bindActionCreators(
773
- {
774
- listRoles,
775
- listUsers,
776
- listGroups,
777
- getControlpanel,
778
- deleteUser,
779
- createUser,
780
- updateUser,
781
- updateGroup,
782
- getUserSchema,
783
- getUser,
784
- },
785
- dispatch,
786
- ),
787
- ),
788
- )(UsersControlpanel);
664
+ {entries.length === 0 && search && (
665
+ <Segment>
666
+ {intl.formatMessage(messages.userSearchNoResults)}
667
+ </Segment>
668
+ )}
669
+ <div className="contents-pagination">
670
+ <Pagination
671
+ current={currentPage}
672
+ total={Math.ceil(entries?.length / pageSize)}
673
+ onChangePage={onChangePage}
674
+ />
675
+ </div>
676
+ </Form>
677
+ </Segment.Group>
678
+ {isClient &&
679
+ createPortal(
680
+ <Toolbar
681
+ pathname={pathname}
682
+ hideDefaultViewButtons
683
+ inner={
684
+ <>
685
+ <Button
686
+ id="toolbar-save"
687
+ className="save"
688
+ aria-label={intl.formatMessage(messages.save)}
689
+ onClick={updateUserRoleSubmit}
690
+ loading={createRequest?.loading}
691
+ >
692
+ <Icon
693
+ name={saveSVG}
694
+ className="circled"
695
+ size="30px"
696
+ title={intl.formatMessage(messages.save)}
697
+ />
698
+ </Button>
699
+ <Link to="/controlpanel" className="cancel">
700
+ <Icon
701
+ name={clearSVG}
702
+ className="circled"
703
+ aria-label={intl.formatMessage(messages.cancel)}
704
+ size="30px"
705
+ title={intl.formatMessage(messages.cancel)}
706
+ />
707
+ </Link>
708
+ <Button
709
+ id="toolbar-add"
710
+ aria-label={intl.formatMessage(messages.addUserButtonTitle)}
711
+ onClick={() => {
712
+ setShowAddUser(true);
713
+ }}
714
+ loading={createRequest?.loading}
715
+ >
716
+ <Icon
717
+ name={addUserSvg}
718
+ size="45px"
719
+ color="#826A6A"
720
+ title={intl.formatMessage(messages.addUserButtonTitle)}
721
+ />
722
+ </Button>
723
+ </>
724
+ }
725
+ />,
726
+ document.getElementById('toolbar'),
727
+ )}
728
+ </Container>
729
+ );
730
+ };
731
+
732
+ export default UsersControlpanel;