share-project-modal-content.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { useTranslation } from 'react-i18next'
  2. import { useEditorContext } from '@/shared/context/editor-context'
  3. import { lazy, Suspense, useState } from 'react'
  4. import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
  5. import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
  6. import {
  7. OLModal,
  8. OLModalBody,
  9. OLModalFooter,
  10. OLModalHeader,
  11. OLModalTitle,
  12. } from '@/shared/components/ol/ol-modal'
  13. import OLNotification from '@/shared/components/ol/ol-notification'
  14. import OLButton from '@/shared/components/ol/ol-button'
  15. import OLSpinner from '@/shared/components/ol/ol-spinner'
  16. import MaterialIcon from '@/shared/components/material-icon'
  17. import ErrorMessage from '@/features/share-project-modal/components/error-message'
  18. import GiveFeedbackLink from '@/features/share-project-modal/components/give-feedback-link'
  19. import classNames from 'classnames'
  20. import { useFeatureFlag } from '@/shared/context/split-test-context'
  21. import { useShareProjectContext } from '@/features/share-project-modal/components/share-project-modal'
  22. import { ErrorBoundaryFallback } from '@/shared/components/error-boundary-fallback'
  23. import withErrorBoundary from '@/infrastructure/error-boundary'
  24. const ReadOnlyTokenLink = lazy(() =>
  25. import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
  26. // re-export as default -- lazy can only handle default exports.
  27. default: ReadOnlyTokenLink,
  28. }))
  29. )
  30. const ShareModalBody = lazy(() => import('./share-modal-body'))
  31. type ShareProjectModalContentProps = {
  32. cancel: () => void
  33. show: boolean
  34. animation: boolean
  35. inFlight: boolean
  36. error: string | undefined
  37. projectName?: string
  38. }
  39. export default function ShareProjectModalContent({
  40. show,
  41. cancel,
  42. animation,
  43. inFlight,
  44. error,
  45. projectName,
  46. }: ShareProjectModalContentProps) {
  47. return (
  48. <OLModal show={show} onHide={cancel} animation={animation}>
  49. <ShareProjectModalContentInnerWithErrorBoundary
  50. inFlight={inFlight}
  51. error={error}
  52. projectName={projectName}
  53. cancel={cancel}
  54. />
  55. </OLModal>
  56. )
  57. }
  58. function ShareProjectModalContentInner({
  59. inFlight,
  60. error,
  61. projectName,
  62. cancel,
  63. }: Pick<
  64. ShareProjectModalContentProps,
  65. 'inFlight' | 'error' | 'projectName' | 'cancel'
  66. >) {
  67. const { t } = useTranslation()
  68. const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
  69. const [isInvitedPeopleScreen, setIsInvitedPeopleScreen] = useState(false)
  70. const { successActionMessage } = useShareProjectContext()
  71. const { isRestrictedTokenMember, isProjectOwner } = useEditorContext()
  72. return (
  73. <>
  74. <OLModalHeader>
  75. <div className="d-flex flex-grow-1 justify-content-between">
  76. {isSharingUpdatesEnabled && isInvitedPeopleScreen ? (
  77. <OLButton
  78. variant="ghost"
  79. onClick={() => setIsInvitedPeopleScreen(false)}
  80. leadingIcon="arrow_back_ios_new"
  81. >
  82. {t('back')}
  83. </OLButton>
  84. ) : (
  85. <OLModalTitle>
  86. {isSharingUpdatesEnabled && projectName
  87. ? t('share_project_name', { projectName })
  88. : t('share_project')}
  89. </OLModalTitle>
  90. )}
  91. {isSharingUpdatesEnabled && isProjectOwner && <GiveFeedbackLink />}
  92. </div>
  93. </OLModalHeader>
  94. <OLModalBody
  95. className={classNames('modal-body-share modal-link-share', {
  96. 'modal-redesign': isSharingUpdatesEnabled,
  97. })}
  98. >
  99. <div
  100. className={classNames({
  101. 'container-fluid': !isSharingUpdatesEnabled,
  102. })}
  103. >
  104. <Suspense fallback={<FullSizeLoadingSpinner minHeight="15rem" />}>
  105. {isRestrictedTokenMember ? (
  106. <ReadOnlyTokenLink />
  107. ) : (
  108. <ShareModalBody
  109. isInvitedPeopleScreen={isInvitedPeopleScreen}
  110. setIsInvitedPeopleScreen={setIsInvitedPeopleScreen}
  111. error={error}
  112. />
  113. )}
  114. </Suspense>
  115. {!isSharingUpdatesEnabled && error && (
  116. <OLNotification
  117. type="error"
  118. content={<ErrorMessage error={error} />}
  119. className="mb-0 mt-3"
  120. />
  121. )}
  122. </div>
  123. </OLModalBody>
  124. <OLModalFooter>
  125. <div className="d-flex flex-grow-1 flex-wrap gap-2">
  126. {isSharingUpdatesEnabled ? (
  127. <>
  128. {successActionMessage && (
  129. <div className="ms-auto px-3 align-self-center">
  130. <div
  131. className="d-flex gap-3 align-items-center"
  132. role="status"
  133. aria-live="polite"
  134. >
  135. <MaterialIcon
  136. unfilled
  137. type="check_circle"
  138. className="text-success"
  139. />
  140. <span>{successActionMessage}</span>
  141. </div>
  142. </div>
  143. )}
  144. </>
  145. ) : (
  146. inFlight && <OLSpinner size="sm" />
  147. )}
  148. </div>
  149. <ClickableElementEnhancer
  150. onClick={cancel}
  151. as={OLButton}
  152. variant={isSharingUpdatesEnabled ? 'ghost' : 'secondary'}
  153. isLoading={isSharingUpdatesEnabled && inFlight}
  154. disabled={inFlight}
  155. >
  156. {t('close')}
  157. </ClickableElementEnhancer>
  158. </OLModalFooter>
  159. </>
  160. )
  161. }
  162. const ShareProjectModalContentInnerFallback = () => {
  163. const { t } = useTranslation()
  164. return (
  165. <>
  166. <OLModalHeader>
  167. <OLModalTitle>{t('generic_something_went_wrong')}</OLModalTitle>
  168. </OLModalHeader>
  169. <OLModalBody>
  170. <ErrorBoundaryFallback />
  171. </OLModalBody>
  172. </>
  173. )
  174. }
  175. const ShareProjectModalContentInnerWithErrorBoundary = withErrorBoundary(
  176. ShareProjectModalContentInner,
  177. () => <ShareProjectModalContentInnerFallback />
  178. )