edit-member.jsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { useState, useEffect } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { useTranslation } from 'react-i18next'
  4. import { useShareProjectContext } from './share-project-modal'
  5. import TransferOwnershipModal from './transfer-ownership-modal'
  6. import { removeMemberFromProject, updateMember } from '../utils/api'
  7. import Icon from '../../../shared/components/icon'
  8. import { useProjectContext } from '../../../shared/context/project-context'
  9. import { sendMB } from '../../../infrastructure/event-tracking'
  10. import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
  11. import OLCol from '@/features/ui/components/ol/ol-col'
  12. import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
  13. import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
  14. import OLButton from '@/features/ui/components/ol/ol-button'
  15. import MaterialIcon from '@/shared/components/material-icon'
  16. import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
  17. import { bsVersion } from '@/features/utils/bootstrap-5'
  18. export default function EditMember({ member }) {
  19. const [privileges, setPrivileges] = useState(member.privileges)
  20. const [confirmingOwnershipTransfer, setConfirmingOwnershipTransfer] =
  21. useState(false)
  22. // update the local state if the member's privileges change externally
  23. useEffect(() => {
  24. setPrivileges(member.privileges)
  25. }, [member.privileges])
  26. const { updateProject, monitorRequest } = useShareProjectContext()
  27. const { _id: projectId, members } = useProjectContext()
  28. function handleSubmit(event) {
  29. event.preventDefault()
  30. if (privileges === 'owner') {
  31. setConfirmingOwnershipTransfer(true)
  32. } else {
  33. monitorRequest(() =>
  34. updateMember(projectId, member, {
  35. privilegeLevel: privileges,
  36. })
  37. ).then(() => {
  38. updateProject({
  39. members: members.map(item =>
  40. item._id === member._id ? { ...item, privileges } : item
  41. ),
  42. })
  43. })
  44. }
  45. }
  46. if (confirmingOwnershipTransfer) {
  47. return (
  48. <TransferOwnershipModal
  49. member={member}
  50. cancel={() => setConfirmingOwnershipTransfer(false)}
  51. />
  52. )
  53. }
  54. return (
  55. <form
  56. id="share-project-form"
  57. className={bsVersion({ bs3: 'form-horizontal' })}
  58. onSubmit={handleSubmit}
  59. >
  60. <OLFormGroup className="project-member row">
  61. <OLCol xs={7} className={bsVersion({ bs3: 'pt-1', bs5: 'pt-2' })}>
  62. {member.email}
  63. </OLCol>
  64. <OLCol xs={3}>
  65. <SelectPrivilege
  66. value={privileges}
  67. handleChange={event => setPrivileges(event.target.value)}
  68. />
  69. </OLCol>
  70. <OLCol xs={2}>
  71. {privileges === member.privileges ? (
  72. <RemoveMemberAction member={member} />
  73. ) : (
  74. <ChangePrivilegesActions
  75. handleReset={() => setPrivileges(member.privileges)}
  76. />
  77. )}
  78. </OLCol>
  79. </OLFormGroup>
  80. </form>
  81. )
  82. }
  83. EditMember.propTypes = {
  84. member: PropTypes.shape({
  85. _id: PropTypes.string.isRequired,
  86. email: PropTypes.string.isRequired,
  87. privileges: PropTypes.string.isRequired,
  88. }),
  89. }
  90. function SelectPrivilege({ value, handleChange }) {
  91. const { t } = useTranslation()
  92. return (
  93. <OLFormSelect
  94. className="privileges"
  95. value={value}
  96. onChange={handleChange}
  97. bs3Props={{
  98. bsSize: 'sm',
  99. }}
  100. >
  101. <option value="owner">{t('owner')}</option>
  102. <option value="readAndWrite">{t('can_edit')}</option>
  103. <option value="readOnly">{t('read_only')}</option>
  104. </OLFormSelect>
  105. )
  106. }
  107. SelectPrivilege.propTypes = {
  108. value: PropTypes.string.isRequired,
  109. handleChange: PropTypes.func.isRequired,
  110. }
  111. function RemoveMemberAction({ member }) {
  112. const { t } = useTranslation()
  113. const { updateProject, monitorRequest } = useShareProjectContext()
  114. const { _id: projectId, members, invites } = useProjectContext()
  115. function handleClick(event) {
  116. event.preventDefault()
  117. monitorRequest(() => removeMemberFromProject(projectId, member)).then(
  118. () => {
  119. const updatedMembers = members.filter(existing => existing !== member)
  120. updateProject({
  121. members: updatedMembers,
  122. })
  123. sendMB('collaborator-removed', {
  124. project_id: projectId,
  125. current_collaborators_amount: updatedMembers.length,
  126. current_invites_amount: invites.length,
  127. })
  128. }
  129. )
  130. }
  131. return (
  132. <div className="text-center">
  133. <OLTooltip
  134. id="remove-collaborator"
  135. description={t('remove_collaborator')}
  136. overlayProps={{ placement: 'bottom' }}
  137. >
  138. <OLButton
  139. variant="link"
  140. onClick={handleClick}
  141. className="remove-button"
  142. aria-label={t('remove_collaborator')}
  143. >
  144. <BootstrapVersionSwitcher
  145. bs3={<Icon type="times" />}
  146. bs5={<MaterialIcon type="clear" />}
  147. />
  148. </OLButton>
  149. </OLTooltip>
  150. </div>
  151. )
  152. }
  153. RemoveMemberAction.propTypes = {
  154. member: PropTypes.shape({
  155. _id: PropTypes.string.isRequired,
  156. email: PropTypes.string.isRequired,
  157. privileges: PropTypes.string.isRequired,
  158. }),
  159. }
  160. function ChangePrivilegesActions({ handleReset }) {
  161. const { t } = useTranslation()
  162. return (
  163. <div className="text-center">
  164. <OLButton type="submit" size="sm" variant="primary">
  165. {t('change_or_cancel-change')}
  166. </OLButton>
  167. <div className="text-sm">
  168. {t('change_or_cancel-or')}
  169. &nbsp;
  170. <OLButton
  171. variant="link"
  172. className="btn-inline-link"
  173. onClick={handleReset}
  174. >
  175. {t('change_or_cancel-cancel')}
  176. </OLButton>
  177. </div>
  178. </div>
  179. )
  180. }
  181. ChangePrivilegesActions.propTypes = {
  182. handleReset: PropTypes.func.isRequired,
  183. }