dropdown-button.tsx 8.0 KB

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