invite.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { useCallback } from 'react'
  2. import { useShareProjectContext } from './share-project-modal'
  3. import { useTranslation } from 'react-i18next'
  4. import MemberPrivileges from './member-privileges'
  5. import { resendInvite, revokeInvite } from '../utils/api'
  6. import { useProjectContext } from '@/shared/context/project-context'
  7. import { sendMB } from '@/infrastructure/event-tracking'
  8. import OLRow from '@/features/ui/components/ol/ol-row'
  9. import OLCol from '@/features/ui/components/ol/ol-col'
  10. import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
  11. import OLButton from '@/features/ui/components/ol/ol-button'
  12. import MaterialIcon from '@/shared/components/material-icon'
  13. import { ProjectMember } from '@/shared/context/types/project-metadata'
  14. export default function Invite({
  15. invite,
  16. isProjectOwner,
  17. }: {
  18. invite: ProjectMember
  19. isProjectOwner: boolean
  20. }) {
  21. const { t } = useTranslation()
  22. return (
  23. <OLRow className="project-invite">
  24. <OLCol xs={8}>
  25. <div>{invite.email}</div>
  26. <div className="small">
  27. {t('invite_not_accepted')}
  28. .&nbsp;
  29. {isProjectOwner && <ResendInvite invite={invite} />}
  30. </div>
  31. </OLCol>
  32. <OLCol xs={3} className="text-end">
  33. <MemberPrivileges privileges={invite.privileges} />
  34. </OLCol>
  35. {isProjectOwner && (
  36. <OLCol xs={1} className="text-center">
  37. <RevokeInvite invite={invite} />
  38. </OLCol>
  39. )}
  40. </OLRow>
  41. )
  42. }
  43. function ResendInvite({ invite }: { invite: ProjectMember }) {
  44. const { t } = useTranslation()
  45. const { monitorRequest, setError, inFlight } = useShareProjectContext()
  46. const { projectId } = useProjectContext()
  47. // const buttonRef = useRef(null)
  48. //
  49. const handleClick = useCallback(
  50. () =>
  51. monitorRequest(() => resendInvite(projectId, invite))
  52. .catch(error => {
  53. if (error?.response?.status === 404) {
  54. setError('invite_expired')
  55. }
  56. if (error?.response?.status === 429) {
  57. setError('invite_resend_limit_hit')
  58. }
  59. })
  60. .finally(() => {
  61. // NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
  62. // if (buttonRef.current) {
  63. // buttonRef.current.blur()
  64. // }
  65. if (document.activeElement) {
  66. ;(document.activeElement as HTMLElement).blur()
  67. }
  68. }),
  69. [invite, monitorRequest, projectId, setError]
  70. )
  71. return (
  72. <OLButton
  73. variant="link"
  74. className="btn-inline-link"
  75. onClick={handleClick}
  76. disabled={inFlight}
  77. // ref={buttonRef}
  78. >
  79. {t('resend')}
  80. </OLButton>
  81. )
  82. }
  83. function RevokeInvite({ invite }: { invite: ProjectMember }) {
  84. const { t } = useTranslation()
  85. const { monitorRequest } = useShareProjectContext()
  86. const { projectId, project, updateProject } = useProjectContext()
  87. const { invites, members } = project || {}
  88. function handleClick(event: React.MouseEvent) {
  89. event.preventDefault()
  90. monitorRequest(() => revokeInvite(projectId, invite)).then(() => {
  91. const updatedInvites =
  92. invites?.filter(existing => existing !== invite) || []
  93. updateProject({
  94. invites: updatedInvites,
  95. })
  96. sendMB('collaborator-invite-revoked', {
  97. project_id: projectId,
  98. current_invites_amount: updatedInvites.length,
  99. current_collaborators_amount: members?.length || 0,
  100. })
  101. })
  102. }
  103. return (
  104. <OLTooltip
  105. id="revoke-invite"
  106. description={t('revoke_invite')}
  107. overlayProps={{ placement: 'bottom' }}
  108. >
  109. <OLButton
  110. variant="link"
  111. onClick={handleClick}
  112. aria-label={t('revoke')}
  113. className="btn-inline-link text-decoration-none"
  114. >
  115. <MaterialIcon type="clear" />
  116. </OLButton>
  117. </OLTooltip>
  118. )
  119. }