invite.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 { ProjectContextMember } from '@/shared/context/types/project-context'
  14. export default function Invite({
  15. invite,
  16. isProjectOwner,
  17. }: {
  18. invite: ProjectContextMember
  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: ProjectContextMember }) {
  44. const { t } = useTranslation()
  45. const { monitorRequest, setError, inFlight } = useShareProjectContext()
  46. const { _id: 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: ProjectContextMember }) {
  84. const { t } = useTranslation()
  85. const { updateProject, monitorRequest } = useShareProjectContext()
  86. const { _id: projectId, invites, members } = useProjectContext()
  87. function handleClick(event: React.MouseEvent) {
  88. event.preventDefault()
  89. monitorRequest(() => revokeInvite(projectId, invite)).then(() => {
  90. const updatedInvites = invites.filter(existing => existing !== invite)
  91. updateProject({
  92. invites: updatedInvites,
  93. })
  94. sendMB('collaborator-invite-revoked', {
  95. project_id: projectId,
  96. current_invites_amount: updatedInvites.length,
  97. current_collaborators_amount: members.length,
  98. })
  99. })
  100. }
  101. return (
  102. <OLTooltip
  103. id="revoke-invite"
  104. description={t('revoke_invite')}
  105. overlayProps={{ placement: 'bottom' }}
  106. >
  107. <OLButton
  108. variant="link"
  109. onClick={handleClick}
  110. aria-label={t('revoke')}
  111. className="btn-inline-link text-decoration-none"
  112. >
  113. <MaterialIcon type="clear" />
  114. </OLButton>
  115. </OLTooltip>
  116. )
  117. }