dropdown-button.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import {
  2. useState,
  3. type ComponentProps,
  4. useCallback,
  5. type Dispatch,
  6. type SetStateAction,
  7. } from 'react'
  8. import { useTranslation } from 'react-i18next'
  9. import { Dropdown, MenuItem } from 'react-bootstrap'
  10. import { User } from '../../../../../../types/group-management/user'
  11. import useAsync from '@/shared/hooks/use-async'
  12. import { type FetchError, postJSON } from '@/infrastructure/fetch-json'
  13. import Icon from '@/shared/components/icon'
  14. import { GroupUserAlert } from '../../utils/types'
  15. import { useGroupMembersContext } from '../../context/group-members-context'
  16. import getMeta from '@/utils/meta'
  17. type resendInviteResponse = {
  18. success: boolean
  19. }
  20. type ManagedUserDropdownButtonProps = {
  21. user: User
  22. openOffboardingModalForUser: (user: User) => void
  23. openUnlinkUserModal: (user: User) => void
  24. groupId: string
  25. setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
  26. }
  27. export default function DropdownButton({
  28. user,
  29. openOffboardingModalForUser,
  30. openUnlinkUserModal,
  31. groupId,
  32. setGroupUserAlert,
  33. }: ManagedUserDropdownButtonProps) {
  34. const { t } = useTranslation()
  35. const { removeMember } = useGroupMembersContext()
  36. const [isOpened, setIsOpened] = useState(false)
  37. const {
  38. runAsync: runResendManagedUserInviteAsync,
  39. isLoading: isResendingManagedUserInvite,
  40. } = useAsync<resendInviteResponse>()
  41. const {
  42. runAsync: runResendLinkSSOInviteAsync,
  43. isLoading: isResendingSSOLinkInvite,
  44. } = useAsync<resendInviteResponse>()
  45. const {
  46. runAsync: runResendGroupInviteAsync,
  47. isLoading: isResendingGroupInvite,
  48. } = useAsync<resendInviteResponse>()
  49. const managedUsersActive = getMeta('ol-managedUsersActive')
  50. const groupSSOActive = getMeta('ol-groupSSOActive')
  51. const userPending = user.invite
  52. const isGroupSSOLinked =
  53. !userPending && user.enrollment?.sso?.some(sso => sso.groupId === groupId)
  54. const isUserManaged = !userPending && user.enrollment?.managedBy === groupId
  55. const handleResendManagedUserInvite = useCallback(
  56. async user => {
  57. try {
  58. const result = await runResendManagedUserInviteAsync(
  59. postJSON(
  60. `/manage/groups/${groupId}/resendManagedUserInvite/${user._id}`
  61. )
  62. )
  63. if (result.success) {
  64. setGroupUserAlert({
  65. variant: 'resendManagedUserInviteSuccess',
  66. email: user.email,
  67. })
  68. setIsOpened(false)
  69. }
  70. } catch (err) {
  71. if ((err as FetchError)?.response?.status === 429) {
  72. setGroupUserAlert({
  73. variant: 'resendInviteTooManyRequests',
  74. email: user.email,
  75. })
  76. } else {
  77. setGroupUserAlert({
  78. variant: 'resendManagedUserInviteFailed',
  79. email: user.email,
  80. })
  81. }
  82. setIsOpened(false)
  83. }
  84. },
  85. [setGroupUserAlert, groupId, runResendManagedUserInviteAsync]
  86. )
  87. const handleResendLinkSSOInviteAsync = useCallback(
  88. async user => {
  89. try {
  90. const result = await runResendLinkSSOInviteAsync(
  91. postJSON(`/manage/groups/${groupId}/resendSSOLinkInvite/${user._id}`)
  92. )
  93. if (result.success) {
  94. setGroupUserAlert({
  95. variant: 'resendSSOLinkInviteSuccess',
  96. email: user.email,
  97. })
  98. setIsOpened(false)
  99. }
  100. } catch (err) {
  101. if ((err as FetchError)?.response?.status === 429) {
  102. setGroupUserAlert({
  103. variant: 'resendInviteTooManyRequests',
  104. email: user.email,
  105. })
  106. } else {
  107. setGroupUserAlert({
  108. variant: 'resendSSOLinkInviteFailed',
  109. email: user.email,
  110. })
  111. }
  112. setIsOpened(false)
  113. }
  114. },
  115. [setGroupUserAlert, groupId, runResendLinkSSOInviteAsync]
  116. )
  117. const handleResendGroupInvite = useCallback(
  118. async user => {
  119. try {
  120. await runResendGroupInviteAsync(
  121. postJSON(`/manage/groups/${groupId}/resendInvite/`, {
  122. body: {
  123. email: user.email,
  124. },
  125. })
  126. )
  127. setGroupUserAlert({
  128. variant: 'resendGroupInviteSuccess',
  129. email: user.email,
  130. })
  131. setIsOpened(false)
  132. } catch (err) {
  133. if ((err as FetchError)?.response?.status === 429) {
  134. setGroupUserAlert({
  135. variant: 'resendInviteTooManyRequests',
  136. email: user.email,
  137. })
  138. } else {
  139. setGroupUserAlert({
  140. variant: 'resendGroupInviteFailed',
  141. email: user.email,
  142. })
  143. }
  144. setIsOpened(false)
  145. }
  146. },
  147. [setGroupUserAlert, groupId, runResendGroupInviteAsync]
  148. )
  149. const onResendManagedUserInviteClick = () => {
  150. handleResendManagedUserInvite(user)
  151. }
  152. const onResendSSOLinkInviteClick = () => {
  153. handleResendLinkSSOInviteAsync(user)
  154. }
  155. const onResendGroupInviteClick = () => {
  156. handleResendGroupInvite(user)
  157. }
  158. const onDeleteUserClick = () => {
  159. openOffboardingModalForUser(user)
  160. }
  161. const onRemoveFromGroup = () => {
  162. removeMember(user)
  163. }
  164. const onUnlinkUserClick = () => {
  165. openUnlinkUserModal(user)
  166. }
  167. const buttons = []
  168. if (userPending) {
  169. buttons.push(
  170. <MenuItemButton
  171. onClick={onResendGroupInviteClick}
  172. key="resend-group-invite-action"
  173. data-testid="resend-group-invite-action"
  174. >
  175. {t('resend_group_invite')}
  176. {isResendingGroupInvite ? (
  177. <Icon type="spinner" spin style={{ marginLeft: '5px' }} />
  178. ) : null}
  179. </MenuItemButton>
  180. )
  181. }
  182. if (managedUsersActive && !isUserManaged && !userPending) {
  183. buttons.push(
  184. <MenuItemButton
  185. onClick={onResendManagedUserInviteClick}
  186. key="resend-managed-user-invite-action"
  187. data-testid="resend-managed-user-invite-action"
  188. >
  189. {t('resend_managed_user_invite')}
  190. {isResendingManagedUserInvite ? (
  191. <Icon type="spinner" spin style={{ marginLeft: '5px' }} />
  192. ) : null}
  193. </MenuItemButton>
  194. )
  195. }
  196. if (groupSSOActive && isGroupSSOLinked) {
  197. buttons.push(
  198. <MenuItemButton
  199. onClick={onUnlinkUserClick}
  200. key="unlink-user-action"
  201. data-testid="unlink-user-action"
  202. >
  203. {t('unlink_user')}
  204. </MenuItemButton>
  205. )
  206. }
  207. if (groupSSOActive && !isGroupSSOLinked && !userPending) {
  208. buttons.push(
  209. <MenuItemButton
  210. onClick={onResendSSOLinkInviteClick}
  211. key="resend-sso-link-invite-action"
  212. data-testid="resend-sso-link-invite-action"
  213. >
  214. {t('resend_link_sso')}
  215. {isResendingSSOLinkInvite ? (
  216. <Icon type="spinner" spin style={{ marginLeft: '5px' }} />
  217. ) : null}
  218. </MenuItemButton>
  219. )
  220. }
  221. if (isUserManaged && !user.isEntityAdmin) {
  222. buttons.push(
  223. <MenuItemButton
  224. className="delete-user-action"
  225. key="delete-user-action"
  226. data-testid="delete-user-action"
  227. onClick={onDeleteUserClick}
  228. >
  229. {t('delete_user')}
  230. </MenuItemButton>
  231. )
  232. } else if (!isUserManaged) {
  233. buttons.push(
  234. <MenuItemButton
  235. key="remove-user-action"
  236. data-testid="remove-user-action"
  237. onClick={onRemoveFromGroup}
  238. className="delete-user-action"
  239. >
  240. {t('remove_from_group')}
  241. </MenuItemButton>
  242. )
  243. }
  244. if (buttons.length === 0) {
  245. buttons.push(
  246. <MenuItem key="no-actions-available" data-testid="no-actions-available">
  247. <span className="text-muted">{t('no_actions')}</span>
  248. </MenuItem>
  249. )
  250. }
  251. return (
  252. <span className="managed-user-actions">
  253. <Dropdown
  254. id={`managed-user-dropdown-${user.email}`}
  255. open={isOpened}
  256. onToggle={open => setIsOpened(open)}
  257. >
  258. <Dropdown.Toggle
  259. bsStyle={null}
  260. className="btn btn-link action-btn"
  261. noCaret
  262. >
  263. <i
  264. className="fa fa-ellipsis-v"
  265. aria-hidden="true"
  266. aria-label={t('actions')}
  267. />
  268. </Dropdown.Toggle>
  269. <Dropdown.Menu className="dropdown-menu-right managed-user-dropdown-menu">
  270. {buttons}
  271. </Dropdown.Menu>
  272. </Dropdown>
  273. </span>
  274. )
  275. }
  276. function MenuItemButton({
  277. children,
  278. onClick,
  279. className,
  280. ...buttonProps
  281. }: ComponentProps<'button'>) {
  282. return (
  283. <li role="presentation" className={className}>
  284. <button
  285. className="managed-user-menu-item-button"
  286. role="menuitem"
  287. onClick={onClick}
  288. {...buttonProps}
  289. >
  290. {children}
  291. </button>
  292. </li>
  293. )
  294. }