invite.jsx 3.6 KB

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