@plone/volto 19.0.0-alpha.8 → 19.0.0

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