share-project-modal.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import React, {
  2. createContext,
  3. useCallback,
  4. useContext,
  5. useEffect,
  6. useState,
  7. } from 'react'
  8. import PropTypes from 'prop-types'
  9. import ShareProjectModalContent from './share-project-modal-content'
  10. import {
  11. useProjectContext,
  12. projectShape,
  13. } from '../../../shared/context/project-context'
  14. import { useSplitTestContext } from '../../../shared/context/split-test-context'
  15. import { sendMB } from '../../../infrastructure/event-tracking'
  16. const ShareProjectContext = createContext()
  17. ShareProjectContext.Provider.propTypes = {
  18. value: PropTypes.shape({
  19. isAdmin: PropTypes.bool.isRequired,
  20. updateProject: PropTypes.func.isRequired,
  21. monitorRequest: PropTypes.func.isRequired,
  22. inFlight: PropTypes.bool,
  23. setInFlight: PropTypes.func,
  24. error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  25. setError: PropTypes.func,
  26. }),
  27. }
  28. export function useShareProjectContext() {
  29. const context = useContext(ShareProjectContext)
  30. if (!context) {
  31. throw new Error(
  32. 'useShareProjectContext is only available inside ShareProjectProvider'
  33. )
  34. }
  35. return context
  36. }
  37. const ShareProjectModal = React.memo(function ShareProjectModal({
  38. handleHide,
  39. show,
  40. animation = true,
  41. isAdmin,
  42. }) {
  43. const [inFlight, setInFlight] = useState(false)
  44. const [error, setError] = useState()
  45. const project = useProjectContext(projectShape)
  46. const { splitTestVariants } = useSplitTestContext({
  47. splitTestVariants: PropTypes.object,
  48. })
  49. // send tracking event when the modal is opened
  50. useEffect(() => {
  51. if (show) {
  52. sendMB('share-modal-opened', {
  53. splitTestVariant: splitTestVariants['null-test-share-modal'],
  54. })
  55. }
  56. }, [splitTestVariants, show])
  57. // reset error when the modal is opened
  58. useEffect(() => {
  59. if (show) {
  60. setError(undefined)
  61. }
  62. }, [show])
  63. // close the modal if not in flight
  64. const cancel = useCallback(() => {
  65. if (!inFlight) {
  66. handleHide()
  67. }
  68. }, [handleHide, inFlight])
  69. // update `error` and `inFlight` while sending a request
  70. const monitorRequest = useCallback(request => {
  71. setError(undefined)
  72. setInFlight(true)
  73. const promise = request()
  74. promise.catch(error => {
  75. setError(
  76. error.data?.errorReason ||
  77. error.data?.error ||
  78. 'generic_something_went_wrong'
  79. )
  80. })
  81. promise.finally(() => {
  82. setInFlight(false)
  83. })
  84. return promise
  85. }, [])
  86. // merge the new data with the old project data
  87. const updateProject = useCallback(
  88. data => Object.assign(project, data),
  89. [project]
  90. )
  91. if (!project) {
  92. return null
  93. }
  94. return (
  95. <ShareProjectContext.Provider
  96. value={{
  97. isAdmin,
  98. updateProject,
  99. monitorRequest,
  100. inFlight,
  101. setInFlight,
  102. error,
  103. setError,
  104. }}
  105. >
  106. <ShareProjectModalContent
  107. animation={animation}
  108. cancel={cancel}
  109. error={error}
  110. inFlight={inFlight}
  111. show={show}
  112. />
  113. </ShareProjectContext.Provider>
  114. )
  115. })
  116. ShareProjectModal.propTypes = {
  117. animation: PropTypes.bool,
  118. handleHide: PropTypes.func.isRequired,
  119. isAdmin: PropTypes.bool.isRequired,
  120. show: PropTypes.bool.isRequired,
  121. }
  122. export default ShareProjectModal