invite.jsx 4.1 KB

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