project-access.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import { useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import ShareProjectModalRow from '@/features/share-project-modal/components/share-project-modal-row'
  4. import MaterialIcon from '@/shared/components/material-icon'
  5. import OLButton from '@/shared/components/ol/ol-button'
  6. import OLTooltip from '@/shared/components/ol/ol-tooltip'
  7. import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
  8. import {
  9. Dropdown,
  10. DropdownDivider,
  11. DropdownItem,
  12. DropdownMenu,
  13. DropdownToggle,
  14. } from '@/shared/components/dropdown/dropdown-menu'
  15. import DropdownListItem from '@/shared/components/dropdown/dropdown-list-item'
  16. import LinkSharing from '@/features/share-project-modal/components/link-sharing'
  17. import { useEditorContext } from '@/shared/context/editor-context'
  18. import { PermissionsLevel } from '@/features/ide-react/types/permissions'
  19. import MemberPrivileges from '@/features/share-project-modal/components/member-privileges'
  20. import RemoveSharingLinksModal from '@/features/share-project-modal/components/remove-sharing-links-modal'
  21. import {
  22. ProjectAccessType,
  23. useShareProjectContext,
  24. } from '@/features/share-project-modal/components/share-project-modal'
  25. type ProjectAccessProps = {
  26. setIsInvitedPeopleScreen: React.Dispatch<React.SetStateAction<boolean>>
  27. invitedPeopleCount: number
  28. }
  29. export type PendingAccessType = Exclude<ProjectAccessType, 'linkSharing'>
  30. function ProjectAccess({
  31. setIsInvitedPeopleScreen,
  32. invitedPeopleCount,
  33. }: ProjectAccessProps) {
  34. const { t } = useTranslation()
  35. const [pendingAccess, setPendingAccess] = useState<PendingAccessType | null>(
  36. null
  37. )
  38. // TODO set initial state
  39. const [privileges, setPrivileges] =
  40. useState<Exclude<PermissionsLevel, 'owner'>>('readOnly')
  41. const { isProjectOwner } = useEditorContext()
  42. // TODO set company name
  43. const companyName = 'XYZ'
  44. const {
  45. monitorRequest,
  46. setSuccessActionMessage,
  47. projectAccess,
  48. setProjectAccess,
  49. } = useShareProjectContext()
  50. const handleAccessChange = (newAccess: PendingAccessType) => {
  51. setPendingAccess(null)
  52. monitorRequest(
  53. () =>
  54. // TODO: replace with real API call
  55. new Promise(resolve => setTimeout(resolve, 1000))
  56. ).then(() => {
  57. setProjectAccess(newAccess)
  58. setSuccessActionMessage(t('access_updated'))
  59. })
  60. }
  61. const onAccessSelect = (eventKey: ProjectAccessType) => {
  62. if (projectAccess === 'linkSharing' && eventKey !== 'linkSharing') {
  63. // Legacy link sharing: show confirmation first
  64. setPendingAccess(eventKey as PendingAccessType)
  65. } else {
  66. // Non-legacy: fire request directly
  67. handleAccessChange(eventKey as PendingAccessType)
  68. }
  69. }
  70. const onPrivilegesChange = (eventKey: Exclude<PermissionsLevel, 'owner'>) => {
  71. monitorRequest(
  72. () =>
  73. // TODO: replace with real API call
  74. new Promise(resolve => setTimeout(resolve, 1000))
  75. ).then(() => {
  76. setPrivileges(eventKey)
  77. setSuccessActionMessage(t('access_updated'))
  78. })
  79. }
  80. const getProjectAccessDropdownToggleText = () => {
  81. switch (projectAccess) {
  82. case 'linkSharing':
  83. return t('via_sharing_links_legacy')
  84. case 'onlyInvitedPeople':
  85. return t('only_invited_people')
  86. case 'anyoneInXyzWithTheLink':
  87. return t('anyone_in_x_with_the_link', { companyName })
  88. case 'anyoneWithTheLink':
  89. return t('anyone_with_the_link')
  90. default:
  91. return ''
  92. }
  93. }
  94. return (
  95. <>
  96. <h3 className="h4 fw-normal mt-3 mb-2 pt-1">{t('project_access')}</h3>
  97. <ShareProjectModalRow>
  98. <div className="d-inline-flex align-items-center h5 m-0 gap-2">
  99. <MaterialIcon type="group" unfilled />
  100. <div className="px-2 fw-normal">
  101. {t('x_people_invited', { count: invitedPeopleCount })}
  102. </div>
  103. </div>
  104. <OLButton
  105. variant="ghost"
  106. trailingIcon="chevron_right"
  107. onClick={() => setIsInvitedPeopleScreen(true)}
  108. >
  109. {t('manage_access')}
  110. </OLButton>
  111. </ShareProjectModalRow>
  112. {projectAccess && (
  113. <ShareProjectModalRow>
  114. <div className="d-inline-flex align-items-center h5 m-0">
  115. {projectAccess === 'linkSharing' && <MaterialIcon type="link" />}
  116. {projectAccess === 'onlyInvitedPeople' && (
  117. <MaterialIcon type="lock" unfilled />
  118. )}
  119. {projectAccess === 'anyoneInXyzWithTheLink' && (
  120. <MaterialIcon type="domain" unfilled />
  121. )}
  122. {projectAccess === 'anyoneWithTheLink' && (
  123. <MaterialIcon type="globe" unfilled />
  124. )}
  125. <Dropdown onSelect={onAccessSelect}>
  126. <DropdownToggle
  127. variant="ghost"
  128. className="d-flex align-items-center gap-2 no-default-caret"
  129. >
  130. {getProjectAccessDropdownToggleText()}
  131. <MaterialIcon type="keyboard_arrow_down" />
  132. </DropdownToggle>
  133. <DropdownMenu>
  134. {projectAccess === 'linkSharing' && (
  135. <>
  136. <DropdownListItem className="d-flex align-items-center">
  137. <DropdownItem
  138. as="button"
  139. eventKey="linkSharing"
  140. leadingIcon={<MaterialIcon type="link" />}
  141. trailingIcon={
  142. projectAccess === 'linkSharing' ? 'check' : undefined
  143. }
  144. active={projectAccess === 'linkSharing'}
  145. >
  146. {t('via_sharing_links_legacy')}
  147. </DropdownItem>
  148. </DropdownListItem>
  149. <DropdownDivider />
  150. </>
  151. )}
  152. <DropdownListItem className="d-flex align-items-center">
  153. <DropdownItem
  154. as="button"
  155. eventKey="onlyInvitedPeople"
  156. leadingIcon={<MaterialIcon type="lock" unfilled />}
  157. trailingIcon={
  158. projectAccess === 'onlyInvitedPeople'
  159. ? 'check'
  160. : undefined
  161. }
  162. active={projectAccess === 'onlyInvitedPeople'}
  163. >
  164. {t('only_invited_people')}
  165. </DropdownItem>
  166. </DropdownListItem>
  167. <DropdownListItem className="d-flex align-items-center">
  168. <DropdownItem
  169. as="button"
  170. eventKey="anyoneInXyzWithTheLink"
  171. leadingIcon={<MaterialIcon type="domain" unfilled />}
  172. trailingIcon={
  173. projectAccess === 'anyoneInXyzWithTheLink'
  174. ? 'check'
  175. : undefined
  176. }
  177. active={projectAccess === 'anyoneInXyzWithTheLink'}
  178. >
  179. {t('anyone_in_x_with_the_link', { companyName })}
  180. </DropdownItem>
  181. </DropdownListItem>
  182. <DropdownListItem className="d-flex align-items-center gap-2">
  183. <DropdownItem
  184. as="button"
  185. eventKey="anyoneWithTheLink"
  186. leadingIcon={<MaterialIcon type="globe" unfilled />}
  187. trailingIcon={
  188. projectAccess === 'anyoneWithTheLink'
  189. ? 'check'
  190. : undefined
  191. }
  192. active={projectAccess === 'anyoneWithTheLink'}
  193. >
  194. {t('anyone_with_the_link')}
  195. </DropdownItem>
  196. <OLTooltip
  197. id="tooltip-anyone-with-link"
  198. description={t('not_permitted_by_your_organization')}
  199. overlayProps={{ placement: 'left' }}
  200. >
  201. <span style={{ cursor: 'default' }}>
  202. <MaterialIcon
  203. type="info"
  204. unfilled
  205. className="align-middle px-2"
  206. />
  207. </span>
  208. </OLTooltip>
  209. </DropdownListItem>
  210. </DropdownMenu>
  211. </Dropdown>
  212. {pendingAccess && (
  213. <RemoveSharingLinksModal
  214. pendingAccess={pendingAccess}
  215. onCancel={() => setPendingAccess(null)}
  216. onConfirm={() => handleAccessChange(pendingAccess)}
  217. />
  218. )}
  219. </div>
  220. {projectAccess !== 'linkSharing' && (
  221. <Dropdown align="end" onSelect={onPrivilegesChange}>
  222. <DropdownToggle
  223. variant="ghost"
  224. className="d-flex align-items-center gap-2 no-default-caret"
  225. >
  226. <MemberPrivileges privileges={privileges} />
  227. <MaterialIcon type="keyboard_arrow_down" />
  228. </DropdownToggle>
  229. <DropdownMenu>
  230. <OLDropdownMenuItem
  231. as="button"
  232. eventKey="readAndWrite"
  233. leadingIcon={<MaterialIcon type="edit" unfilled />}
  234. active={privileges === 'readAndWrite'}
  235. trailingIcon={
  236. privileges === 'readAndWrite' ? 'check' : undefined
  237. }
  238. >
  239. {t('editor')}
  240. </OLDropdownMenuItem>
  241. <OLDropdownMenuItem
  242. as="button"
  243. eventKey="review"
  244. leadingIcon={<MaterialIcon type="mode_comment" unfilled />}
  245. active={privileges === 'review'}
  246. trailingIcon={privileges === 'review' ? 'check' : undefined}
  247. >
  248. {t('reviewer')}
  249. </OLDropdownMenuItem>
  250. <OLDropdownMenuItem
  251. as="button"
  252. eventKey="readOnly"
  253. leadingIcon={<MaterialIcon type="visibility" unfilled />}
  254. active={privileges === 'readOnly'}
  255. trailingIcon={privileges === 'readOnly' ? 'check' : undefined}
  256. >
  257. {t('viewer')}
  258. </OLDropdownMenuItem>
  259. </DropdownMenu>
  260. </Dropdown>
  261. )}
  262. </ShareProjectModalRow>
  263. )}
  264. {isProjectOwner && <LinkSharing />}
  265. </>
  266. )
  267. }
  268. export default ProjectAccess