GrantSelector.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import React, { useCallback, useState } from 'react';
  2. import { isPopulated, GroupType, type IGrantedGroup } from '@growi/core';
  3. import { useTranslation } from 'next-i18next';
  4. import {
  5. UncontrolledDropdown,
  6. DropdownToggle, DropdownMenu, DropdownItem,
  7. Modal, ModalHeader, ModalBody,
  8. } from 'reactstrap';
  9. import type { IPageGrantData } from '~/interfaces/page';
  10. import { useCurrentUser } from '~/stores/context';
  11. import { useMyUserGroups } from './use-my-user-groups';
  12. const AVAILABLE_GRANTS = [
  13. {
  14. grant: 1, iconClass: 'icon-people', btnStyleClass: 'outline-info', label: 'Public',
  15. },
  16. {
  17. grant: 2, iconClass: 'icon-link', btnStyleClass: 'outline-teal', label: 'Anyone with the link',
  18. },
  19. // { grant: 3, iconClass: '', label: 'Specified users only' },
  20. {
  21. grant: 4, iconClass: 'icon-lock', btnStyleClass: 'outline-danger', label: 'Only me',
  22. },
  23. {
  24. grant: 5, iconClass: 'icon-options', btnStyleClass: 'outline-purple', label: 'Only inside the group', reselectLabel: 'Reselect the group',
  25. },
  26. ];
  27. type Props = {
  28. disabled?: boolean,
  29. grant: number,
  30. grantedGroups?: {
  31. id: string,
  32. name: string,
  33. type: GroupType,
  34. }[]
  35. onUpdateGrant?: (grantData: IPageGrantData) => void,
  36. }
  37. /**
  38. * Page grant select component
  39. */
  40. export const GrantSelector = (props: Props): JSX.Element => {
  41. const { t } = useTranslation();
  42. const {
  43. disabled,
  44. grantedGroups,
  45. onUpdateGrant,
  46. grant: currentGrant,
  47. } = props;
  48. const [isSelectGroupModalShown, setIsSelectGroupModalShown] = useState(false);
  49. const { data: currentUser } = useCurrentUser();
  50. const shouldFetch = isSelectGroupModalShown;
  51. const { data: myUserGroups, update: updateMyUserGroups } = useMyUserGroups(shouldFetch);
  52. const showSelectGroupModal = useCallback(() => {
  53. updateMyUserGroups();
  54. setIsSelectGroupModalShown(true);
  55. }, [updateMyUserGroups]);
  56. /**
  57. * change event handler for grant selector
  58. */
  59. const changeGrantHandler = useCallback((grant: number) => {
  60. // select group
  61. if (grant === 5) {
  62. showSelectGroupModal();
  63. return;
  64. }
  65. if (onUpdateGrant != null) {
  66. onUpdateGrant({ grant, grantedGroups: undefined });
  67. }
  68. }, [onUpdateGrant, showSelectGroupModal]);
  69. const groupListItemClickHandler = useCallback((grantGroup: IGrantedGroup) => {
  70. if (onUpdateGrant != null && isPopulated(grantGroup.item)) {
  71. let grantedGroupsCopy = grantedGroups != null ? [...grantedGroups] : [];
  72. const grantGroupInfo = { id: grantGroup.item._id, name: grantGroup.item.name, type: grantGroup.type };
  73. if (grantedGroupsCopy.find(group => group.id === grantGroupInfo.id) == null) {
  74. grantedGroupsCopy.push(grantGroupInfo);
  75. }
  76. else {
  77. grantedGroupsCopy = grantedGroupsCopy.filter(group => group.id !== grantGroupInfo.id);
  78. }
  79. onUpdateGrant({ grant: 5, grantedGroups: grantedGroupsCopy });
  80. }
  81. }, [onUpdateGrant, grantedGroups]);
  82. /**
  83. * Render grant selector DOM.
  84. */
  85. const renderGrantSelector = useCallback(() => {
  86. let dropdownToggleBtnColor;
  87. let dropdownToggleLabelElm;
  88. const dropdownMenuElems = AVAILABLE_GRANTS.map((opt) => {
  89. const label = ((opt.grant === 5 && opt.reselectLabel != null) && grantedGroups != null && grantedGroups.length > 0)
  90. ? opt.reselectLabel // when grantGroup is selected
  91. : opt.label;
  92. const labelElm = (
  93. <span>
  94. <i className={`icon icon-fw ${opt.iconClass}`}></i>
  95. <span className="label">{t(label)}</span>
  96. </span>
  97. );
  98. // set dropdownToggleBtnColor, dropdownToggleLabelElm
  99. if (opt.grant === 1 || opt.grant === currentGrant) {
  100. dropdownToggleBtnColor = opt.btnStyleClass;
  101. dropdownToggleLabelElm = labelElm;
  102. }
  103. return <DropdownItem key={opt.grant} onClick={() => changeGrantHandler(opt.grant)}>{labelElm}</DropdownItem>;
  104. });
  105. // add specified group option
  106. if (grantedGroups != null && grantedGroups.length > 0) {
  107. const labelElm = (
  108. <span>
  109. <i className="icon icon-fw icon-organization"></i>
  110. <span className="label">
  111. {grantedGroups.length > 1
  112. ? (
  113. <span>
  114. {`${grantedGroups[0].name}... `}
  115. <span className="badge badge-purple">+{grantedGroups.length - 1}</span>
  116. </span>
  117. ) : grantedGroups[0].name}
  118. </span>
  119. </span>
  120. );
  121. // set dropdownToggleLabelElm
  122. dropdownToggleLabelElm = labelElm;
  123. dropdownMenuElems.push(<DropdownItem key="groupSelected">{labelElm}</DropdownItem>);
  124. }
  125. return (
  126. <div className="grw-grant-selector mb-0" data-testid="grw-grant-selector">
  127. <UncontrolledDropdown direction="up">
  128. <DropdownToggle color={dropdownToggleBtnColor} caret className="d-flex justify-content-between align-items-center" disabled={disabled}>
  129. {dropdownToggleLabelElm}
  130. </DropdownToggle>
  131. <DropdownMenu>
  132. {dropdownMenuElems}
  133. </DropdownMenu>
  134. </UncontrolledDropdown>
  135. </div>
  136. );
  137. }, [changeGrantHandler, currentGrant, disabled, grantedGroups, t]);
  138. /**
  139. * Render select grantgroup modal.
  140. */
  141. const renderSelectGroupModalContent = useCallback(() => {
  142. if (!shouldFetch) {
  143. return <></>;
  144. }
  145. // show spinner
  146. if (myUserGroups == null) {
  147. return (
  148. <div className="my-3 text-center">
  149. <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
  150. </div>
  151. );
  152. }
  153. if (myUserGroups.length === 0) {
  154. return (
  155. <div>
  156. <h4>{t('user_group.belonging_to_no_group')}</h4>
  157. { currentUser?.admin && (
  158. <p><a href="/admin/user-groups"><i className="icon icon-fw icon-login"></i>{t('user_group.manage_user_groups')}</a></p>
  159. ) }
  160. </div>
  161. );
  162. }
  163. return (
  164. <>
  165. { myUserGroups.map((group) => {
  166. const groupIsGranted = grantedGroups?.find(g => g.id === group.item._id) != null;
  167. const activeClass = groupIsGranted ? 'active' : '';
  168. return (
  169. <button
  170. className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
  171. type="button"
  172. key={group.item._id}
  173. onClick={() => groupListItemClickHandler(group)}
  174. >
  175. <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
  176. <h5 className="d-inline-block ml-3">{group.item.name}</h5>
  177. {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">{group.item.provider}</span>}
  178. {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
  179. </button>
  180. );
  181. }) }
  182. <button type="button" className="btn btn-primary mt-2 float-right" onClick={() => setIsSelectGroupModalShown(false)}>{t('Done')}</button>
  183. </>
  184. );
  185. }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t, grantedGroups]);
  186. return (
  187. <>
  188. { renderGrantSelector() }
  189. {/* render modal */}
  190. { !disabled && currentUser != null && (
  191. <Modal
  192. isOpen={isSelectGroupModalShown}
  193. toggle={() => setIsSelectGroupModalShown(false)}
  194. >
  195. <ModalHeader tag="h4" toggle={() => setIsSelectGroupModalShown(false)} className="bg-purple text-light">
  196. {t('user_group.select_group')}
  197. </ModalHeader>
  198. <ModalBody>
  199. {renderSelectGroupModalContent()}
  200. </ModalBody>
  201. </Modal>
  202. ) }
  203. </>
  204. );
  205. };