share-project-modal.tsx 3.3 KB

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