link-sharing.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { useCallback, useState } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { Button, Col, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'
  4. import { Trans } from 'react-i18next'
  5. import Icon from '../../../shared/components/icon'
  6. import { useShareProjectContext } from './share-project-modal'
  7. import { setProjectAccessLevel } from '../utils/api'
  8. import CopyLink from '../../../shared/components/copy-link'
  9. import { useProjectContext } from '../../../shared/context/project-context'
  10. import * as eventTracking from '../../../infrastructure/event-tracking'
  11. export default function LinkSharing() {
  12. const [inflight, setInflight] = useState(false)
  13. const { monitorRequest } = useShareProjectContext()
  14. const project = useProjectContext()
  15. // set the access level of a project
  16. const setAccessLevel = useCallback(
  17. publicAccesLevel => {
  18. setInflight(true)
  19. monitorRequest(() => setProjectAccessLevel(project, publicAccesLevel))
  20. .then(() => {
  21. // NOTE: not calling `updateProject` here as it receives data via
  22. // project:publicAccessLevel:changed and project:tokens:changed
  23. // over the websocket connection
  24. // TODO: eventTracking.sendMB('project-make-token-based') when publicAccesLevel is 'tokenBased'
  25. })
  26. .finally(() => {
  27. setInflight(false)
  28. })
  29. },
  30. [monitorRequest, project]
  31. )
  32. switch (project.publicAccesLevel) {
  33. // Private (with token-access available)
  34. case 'private':
  35. return (
  36. <PrivateSharing setAccessLevel={setAccessLevel} inflight={inflight} />
  37. )
  38. // Token-based access
  39. case 'tokenBased':
  40. return (
  41. <TokenBasedSharing
  42. setAccessLevel={setAccessLevel}
  43. inflight={inflight}
  44. />
  45. )
  46. // Legacy public-access
  47. case 'readAndWrite':
  48. case 'readOnly':
  49. return (
  50. <LegacySharing
  51. setAccessLevel={setAccessLevel}
  52. accessLevel={project.publicAccesLevel}
  53. inflight={inflight}
  54. />
  55. )
  56. default:
  57. return null
  58. }
  59. }
  60. function PrivateSharing({ setAccessLevel, inflight }) {
  61. return (
  62. <Row className="public-access-level">
  63. <Col xs={12} className="text-center">
  64. <Trans i18nKey="link_sharing_is_off" />
  65. <span>&nbsp;&nbsp;</span>
  66. <Button
  67. type="button"
  68. bsStyle="link"
  69. className="btn-inline-link"
  70. onClick={() => {
  71. setAccessLevel('tokenBased')
  72. eventTracking.sendMB('link-sharing-click')
  73. }}
  74. disabled={inflight}
  75. >
  76. <Trans i18nKey="turn_on_link_sharing" />
  77. </Button>
  78. <span>&nbsp;&nbsp;</span>
  79. <LinkSharingInfo />
  80. </Col>
  81. </Row>
  82. )
  83. }
  84. PrivateSharing.propTypes = {
  85. setAccessLevel: PropTypes.func.isRequired,
  86. inflight: PropTypes.bool,
  87. }
  88. function TokenBasedSharing({ setAccessLevel, inflight }) {
  89. const project = useProjectContext()
  90. return (
  91. <Row className="public-access-level">
  92. <Col xs={12} className="text-center">
  93. <strong>
  94. <Trans i18nKey="link_sharing_is_on" />
  95. </strong>
  96. <span>&nbsp;&nbsp;</span>
  97. <Button
  98. bsStyle="link"
  99. className="btn-inline-link"
  100. onClick={() => setAccessLevel('private')}
  101. disabled={inflight}
  102. >
  103. <Trans i18nKey="turn_off_link_sharing" />
  104. </Button>
  105. <span>&nbsp;&nbsp;</span>
  106. <LinkSharingInfo />
  107. </Col>
  108. <Col xs={12} className="access-token-display-area">
  109. <div className="access-token-wrapper">
  110. <strong>
  111. <Trans i18nKey="anyone_with_link_can_edit" />
  112. </strong>
  113. <AccessToken
  114. token={project?.tokens?.readAndWrite}
  115. path="/"
  116. tooltipId="tooltip-copy-link-rw"
  117. />
  118. </div>
  119. <div className="access-token-wrapper">
  120. <strong>
  121. <Trans i18nKey="anyone_with_link_can_view" />
  122. </strong>
  123. <AccessToken
  124. token={project?.tokens?.readOnly}
  125. path="/read/"
  126. tooltipId="tooltip-copy-link-ro"
  127. />
  128. </div>
  129. </Col>
  130. </Row>
  131. )
  132. }
  133. TokenBasedSharing.propTypes = {
  134. setAccessLevel: PropTypes.func.isRequired,
  135. inflight: PropTypes.bool,
  136. }
  137. function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
  138. return (
  139. <Row className="public-access-level">
  140. <Col xs={12} className="text-center">
  141. <strong>
  142. {accessLevel === 'readAndWrite' && (
  143. <Trans i18nKey="this_project_is_public" />
  144. )}
  145. {accessLevel === 'readOnly' && (
  146. <Trans i18nKey="this_project_is_public_read_only" />
  147. )}
  148. </strong>
  149. <span>&nbsp;&nbsp;</span>
  150. <Button
  151. type="button"
  152. bsStyle="link"
  153. className="btn-inline-link"
  154. onClick={() => setAccessLevel('private')}
  155. disabled={inflight}
  156. >
  157. <Trans i18nKey="make_private" />
  158. </Button>
  159. <span>&nbsp;&nbsp;</span>
  160. <LinkSharingInfo />
  161. </Col>
  162. </Row>
  163. )
  164. }
  165. LegacySharing.propTypes = {
  166. accessLevel: PropTypes.string.isRequired,
  167. setAccessLevel: PropTypes.func.isRequired,
  168. inflight: PropTypes.bool,
  169. }
  170. export function ReadOnlyTokenLink() {
  171. const project = useProjectContext()
  172. return (
  173. <Row className="public-access-level">
  174. <Col xs={12} className="access-token-display-area">
  175. <div className="access-token-wrapper">
  176. <strong>
  177. <Trans i18nKey="anyone_with_link_can_view" />
  178. </strong>
  179. <AccessToken
  180. token={project?.tokens?.readOnly}
  181. path="/read/"
  182. tooltipId="tooltip-copy-link-ro"
  183. />
  184. </div>
  185. </Col>
  186. </Row>
  187. )
  188. }
  189. function AccessToken({ token, path, tooltipId }) {
  190. if (!token) {
  191. return (
  192. <pre className="access-token">
  193. <span>
  194. <Trans i18nKey="loading" />…
  195. </span>
  196. </pre>
  197. )
  198. }
  199. const link = `${window.location.origin}${path}${token}`
  200. return (
  201. <pre className="access-token">
  202. <span>{link}</span>
  203. <CopyLink link={link} tooltipId={tooltipId} />
  204. </pre>
  205. )
  206. }
  207. AccessToken.propTypes = {
  208. token: PropTypes.string,
  209. tooltipId: PropTypes.string.isRequired,
  210. path: PropTypes.string.isRequired,
  211. }
  212. function LinkSharingInfo() {
  213. return (
  214. <OverlayTrigger
  215. placement="top"
  216. overlay={
  217. <Tooltip id="tooltip-link-sharing-info">
  218. <Trans i18nKey="learn_more_about_link_sharing" />
  219. </Tooltip>
  220. }
  221. >
  222. <a
  223. href="/learn/how-to/What_is_Link_Sharing%3F"
  224. target="_blank"
  225. rel="noopener"
  226. >
  227. <Icon type="question-circle" />
  228. </a>
  229. </OverlayTrigger>
  230. )
  231. }