GrantSelector.tsx 6.6 KB

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