PageDeleteRightsSettings.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. import type React from 'react';
  2. import { useCallback } from 'react';
  3. import { Collapse } from 'reactstrap';
  4. import type AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
  5. import {
  6. type IPageDeleteConfigValue,
  7. type IPageDeleteConfigValueToProcessValidation,
  8. PageDeleteConfigValue,
  9. } from '~/interfaces/page-delete-config';
  10. import {
  11. prepareDeleteConfigValuesForCalc,
  12. validateDeleteConfigs,
  13. } from '~/utils/page-delete-config';
  14. import {
  15. DeletionType,
  16. type DeletionTypeValue,
  17. getDeleteConfigValueForT,
  18. getDeletionTypeForT,
  19. isRecursiveDeletion,
  20. isTypeDeletion,
  21. } from './types';
  22. type Props = {
  23. adminGeneralSecurityContainer: AdminGeneralSecurityContainer;
  24. t: (key: string) => string;
  25. };
  26. export const PageDeleteRightsSettings: React.FC<Props> = ({
  27. adminGeneralSecurityContainer,
  28. t,
  29. }) => {
  30. const {
  31. currentPageDeletionAuthority,
  32. currentPageCompleteDeletionAuthority,
  33. currentPageRecursiveDeletionAuthority,
  34. currentPageRecursiveCompleteDeletionAuthority,
  35. } = adminGeneralSecurityContainer.state;
  36. const getRecursiveDeletionConfigState = useCallback(
  37. (deletionType: DeletionTypeValue) => {
  38. if (isTypeDeletion(deletionType)) {
  39. return [
  40. adminGeneralSecurityContainer.state
  41. .currentPageRecursiveDeletionAuthority,
  42. adminGeneralSecurityContainer.changePageRecursiveDeletionAuthority,
  43. ] as const;
  44. }
  45. return [
  46. adminGeneralSecurityContainer.state
  47. .currentPageRecursiveCompleteDeletionAuthority,
  48. adminGeneralSecurityContainer.changePageRecursiveCompleteDeletionAuthority,
  49. ] as const;
  50. },
  51. [adminGeneralSecurityContainer],
  52. );
  53. const previousPageRecursiveAuthorityState = useCallback(
  54. (deletionType: DeletionTypeValue) => {
  55. return isTypeDeletion(deletionType)
  56. ? adminGeneralSecurityContainer.state
  57. .previousPageRecursiveDeletionAuthority
  58. : adminGeneralSecurityContainer.state
  59. .previousPageRecursiveCompleteDeletionAuthority;
  60. },
  61. [adminGeneralSecurityContainer],
  62. );
  63. const setPagePreviousRecursiveAuthorityState = useCallback(
  64. (
  65. deletionType: DeletionTypeValue,
  66. previousState: IPageDeleteConfigValue | null,
  67. ) => {
  68. if (isTypeDeletion(deletionType)) {
  69. adminGeneralSecurityContainer.changePreviousPageRecursiveDeletionAuthority(
  70. previousState,
  71. );
  72. return;
  73. }
  74. adminGeneralSecurityContainer.changePreviousPageRecursiveCompleteDeletionAuthority(
  75. previousState,
  76. );
  77. },
  78. [adminGeneralSecurityContainer],
  79. );
  80. const expandDeleteOptionsState = useCallback(
  81. (deletionType: DeletionTypeValue) => {
  82. return isTypeDeletion(deletionType)
  83. ? adminGeneralSecurityContainer.state.expandOtherOptionsForDeletion
  84. : adminGeneralSecurityContainer.state
  85. .expandOtherOptionsForCompleteDeletion;
  86. },
  87. [adminGeneralSecurityContainer],
  88. );
  89. const setExpandOtherDeleteOptionsState = useCallback(
  90. (deletionType: DeletionTypeValue, bool: boolean) => {
  91. if (isTypeDeletion(deletionType)) {
  92. adminGeneralSecurityContainer.switchExpandOtherOptionsForDeletion(bool);
  93. return;
  94. }
  95. adminGeneralSecurityContainer.switchExpandOtherOptionsForCompleteDeletion(
  96. bool,
  97. );
  98. },
  99. [adminGeneralSecurityContainer],
  100. );
  101. const setDeletionConfigState = useCallback(
  102. (
  103. newState: IPageDeleteConfigValue,
  104. setState: (value: IPageDeleteConfigValue) => void,
  105. deletionType: DeletionTypeValue,
  106. ) => {
  107. setState(newState);
  108. if (previousPageRecursiveAuthorityState(deletionType) !== null) {
  109. setPagePreviousRecursiveAuthorityState(deletionType, null);
  110. }
  111. if (isRecursiveDeletion(deletionType)) {
  112. return;
  113. }
  114. const [recursiveState, setRecursiveState] =
  115. getRecursiveDeletionConfigState(deletionType);
  116. const calculableValue = prepareDeleteConfigValuesForCalc(
  117. newState as IPageDeleteConfigValueToProcessValidation,
  118. recursiveState as IPageDeleteConfigValueToProcessValidation,
  119. );
  120. const shouldForceUpdate = !validateDeleteConfigs(
  121. calculableValue[0],
  122. calculableValue[1],
  123. );
  124. if (shouldForceUpdate) {
  125. setRecursiveState(newState);
  126. setPagePreviousRecursiveAuthorityState(deletionType, recursiveState);
  127. setExpandOtherDeleteOptionsState(deletionType, true);
  128. }
  129. },
  130. [
  131. getRecursiveDeletionConfigState,
  132. previousPageRecursiveAuthorityState,
  133. setPagePreviousRecursiveAuthorityState,
  134. setExpandOtherDeleteOptionsState,
  135. ],
  136. );
  137. const renderPageDeletePermissionDropdown = useCallback(
  138. (
  139. currentState: IPageDeleteConfigValue,
  140. setState: (value: IPageDeleteConfigValue) => void,
  141. deletionType: DeletionTypeValue,
  142. isButtonDisabled: boolean,
  143. ) => {
  144. return (
  145. <div className="dropdown">
  146. <button
  147. className="btn btn-outline-secondary dropdown-toggle text-end"
  148. type="button"
  149. id="dropdownMenuButton"
  150. data-bs-toggle="dropdown"
  151. aria-haspopup="true"
  152. aria-expanded="true"
  153. >
  154. <span className="float-start">
  155. {t(getDeleteConfigValueForT(currentState))}
  156. </span>
  157. </button>
  158. <div className="dropdown-menu">
  159. {isRecursiveDeletion(deletionType) ? (
  160. <button
  161. className="dropdown-item"
  162. type="button"
  163. onClick={() => {
  164. setDeletionConfigState(
  165. PageDeleteConfigValue.Inherit,
  166. setState,
  167. deletionType,
  168. );
  169. }}
  170. >
  171. {t('security_settings.inherit')}
  172. </button>
  173. ) : (
  174. <button
  175. className="dropdown-item"
  176. type="button"
  177. onClick={() => {
  178. setDeletionConfigState(
  179. PageDeleteConfigValue.Anyone,
  180. setState,
  181. deletionType,
  182. );
  183. }}
  184. >
  185. {t('security_settings.anyone')}
  186. </button>
  187. )}
  188. <button
  189. className={`dropdown-item ${isButtonDisabled ? 'disabled' : ''}`}
  190. type="button"
  191. onClick={() => {
  192. setDeletionConfigState(
  193. PageDeleteConfigValue.AdminAndAuthor,
  194. setState,
  195. deletionType,
  196. );
  197. }}
  198. >
  199. {t('security_settings.admin_and_author')}
  200. </button>
  201. <button
  202. className="dropdown-item"
  203. type="button"
  204. onClick={() => {
  205. setDeletionConfigState(
  206. PageDeleteConfigValue.AdminOnly,
  207. setState,
  208. deletionType,
  209. );
  210. }}
  211. >
  212. {t('security_settings.admin_only')}
  213. </button>
  214. </div>
  215. <p className="form-text text-muted small">
  216. {t(
  217. `security_settings.${getDeletionTypeForT(deletionType)}_explanation`,
  218. )}
  219. </p>
  220. </div>
  221. );
  222. },
  223. [t, setDeletionConfigState],
  224. );
  225. const renderPageDeletePermission = useCallback(
  226. (
  227. currentState: IPageDeleteConfigValue,
  228. setState: (value: IPageDeleteConfigValue) => void,
  229. deletionType: DeletionTypeValue,
  230. isButtonDisabled: boolean,
  231. ) => {
  232. const expandDeleteOptions = expandDeleteOptionsState(deletionType);
  233. return (
  234. <div
  235. key={`page-delete-permission-dropdown-${deletionType}`}
  236. className="row"
  237. >
  238. <div className="col-md-4 text-md-end">
  239. {!isRecursiveDeletion(deletionType) &&
  240. isTypeDeletion(deletionType) && (
  241. <strong>{t('security_settings.page_delete')}</strong>
  242. )}
  243. {!isRecursiveDeletion(deletionType) &&
  244. !isTypeDeletion(deletionType) && (
  245. <strong>{t('security_settings.page_delete_completely')}</strong>
  246. )}
  247. </div>
  248. <div className="col-md-8">
  249. {!isRecursiveDeletion(deletionType) ? (
  250. <>
  251. {renderPageDeletePermissionDropdown(
  252. currentState,
  253. setState,
  254. deletionType,
  255. isButtonDisabled,
  256. )}
  257. {currentState === PageDeleteConfigValue.Anyone &&
  258. deletionType === DeletionType.CompleteDeletion && (
  259. <>
  260. <input
  261. id="isAllGroupMembershipRequiredForPageCompleteDeletionCheckbox"
  262. className="form-check-input"
  263. type="checkbox"
  264. checked={
  265. adminGeneralSecurityContainer.state
  266. .isAllGroupMembershipRequiredForPageCompleteDeletion
  267. }
  268. onChange={() => {
  269. adminGeneralSecurityContainer.switchIsAllGroupMembershipRequiredForPageCompleteDeletion();
  270. }}
  271. />
  272. <label
  273. className="form-check-label"
  274. htmlFor="isAllGroupMembershipRequiredForPageCompleteDeletionCheckbox"
  275. >
  276. {t(
  277. 'security_settings.is_all_group_membership_required_for_page_complete_deletion',
  278. )}
  279. </label>
  280. <p className="form-text text-muted small mt-2">
  281. {t(
  282. 'security_settings.is_all_group_membership_required_for_page_complete_deletion_explanation',
  283. )}
  284. </p>
  285. </>
  286. )}
  287. </>
  288. ) : (
  289. <>
  290. <button
  291. type="button"
  292. className="btn btn-link p-0 mb-4"
  293. aria-expanded="false"
  294. onClick={() =>
  295. setExpandOtherDeleteOptionsState(
  296. deletionType,
  297. !expandDeleteOptions,
  298. )
  299. }
  300. >
  301. <span
  302. className={`material-symbols-outlined me-1 ${expandDeleteOptions ? 'rotate-90' : ''}`}
  303. >
  304. navigate_next
  305. </span>
  306. {t('security_settings.other_options')}
  307. </button>
  308. <Collapse isOpen={expandDeleteOptions}>
  309. <div className="pb-4">
  310. <p className="card custom-card bg-warning-sublte">
  311. <span className="text-warning">
  312. <span className="material-symbols-outlined">info</span>
  313. <span
  314. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  315. dangerouslySetInnerHTML={{
  316. __html: t(
  317. 'security_settings.page_delete_rights_caution',
  318. ),
  319. }}
  320. />
  321. </span>
  322. </p>
  323. {previousPageRecursiveAuthorityState(deletionType) !==
  324. null && (
  325. <div className="mb-3">
  326. <strong>
  327. {t('security_settings.forced_update_desc')}
  328. </strong>
  329. <code>
  330. {t(
  331. getDeleteConfigValueForT(
  332. previousPageRecursiveAuthorityState(deletionType),
  333. ),
  334. )}
  335. </code>
  336. </div>
  337. )}
  338. {renderPageDeletePermissionDropdown(
  339. currentState,
  340. setState,
  341. deletionType,
  342. isButtonDisabled,
  343. )}
  344. </div>
  345. </Collapse>
  346. </>
  347. )}
  348. </div>
  349. </div>
  350. );
  351. },
  352. [
  353. adminGeneralSecurityContainer,
  354. expandDeleteOptionsState,
  355. previousPageRecursiveAuthorityState,
  356. renderPageDeletePermissionDropdown,
  357. setExpandOtherDeleteOptionsState,
  358. t,
  359. ],
  360. );
  361. const isButtonDisabledForDeletion = !validateDeleteConfigs(
  362. currentPageDeletionAuthority,
  363. PageDeleteConfigValue.AdminAndAuthor,
  364. );
  365. const isButtonDisabledForCompleteDeletion = !validateDeleteConfigs(
  366. currentPageCompleteDeletionAuthority,
  367. PageDeleteConfigValue.AdminAndAuthor,
  368. );
  369. return (
  370. <>
  371. <h4 className="mb-3">{t('security_settings.page_delete_rights')}</h4>
  372. {[
  373. [
  374. currentPageDeletionAuthority,
  375. adminGeneralSecurityContainer.changePageDeletionAuthority,
  376. DeletionType.Deletion,
  377. false,
  378. ],
  379. [
  380. currentPageRecursiveDeletionAuthority,
  381. adminGeneralSecurityContainer.changePageRecursiveDeletionAuthority,
  382. DeletionType.RecursiveDeletion,
  383. isButtonDisabledForDeletion,
  384. ],
  385. ].map((arr) =>
  386. renderPageDeletePermission(
  387. arr[0] as IPageDeleteConfigValue,
  388. arr[1] as (value: IPageDeleteConfigValue) => void,
  389. arr[2] as DeletionTypeValue,
  390. arr[3] as boolean,
  391. ),
  392. )}
  393. {[
  394. [
  395. currentPageCompleteDeletionAuthority,
  396. adminGeneralSecurityContainer.changePageCompleteDeletionAuthority,
  397. DeletionType.CompleteDeletion,
  398. false,
  399. ],
  400. [
  401. currentPageRecursiveCompleteDeletionAuthority,
  402. adminGeneralSecurityContainer.changePageRecursiveCompleteDeletionAuthority,
  403. DeletionType.RecursiveCompleteDeletion,
  404. isButtonDisabledForCompleteDeletion,
  405. ],
  406. ].map((arr) =>
  407. renderPageDeletePermission(
  408. arr[0] as IPageDeleteConfigValue,
  409. arr[1] as (value: IPageDeleteConfigValue) => void,
  410. arr[2] as DeletionTypeValue,
  411. arr[3] as boolean,
  412. ),
  413. )}
  414. </>
  415. );
  416. };