MainLayout.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import loadable from '@loadable/component'
  2. import { FC, useEffect, useRef, useState } from 'react'
  3. import { Route, Routes, useLocation, useNavigationType, useSearchParams } from 'react-router-dom'
  4. import { StudioLoading } from '@/components/_loaders/StudioLoading'
  5. import { CookiePopover } from '@/components/_overlays/CookiePopover'
  6. import { atlasConfig } from '@/config'
  7. import { BASE_PATHS, absoluteRoutes } from '@/config/routes'
  8. import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
  9. import { transitions } from '@/styles'
  10. import { RoutingState } from '@/types/routing'
  11. import { isBrowserOutdated } from '@/utils/browser'
  12. import { SentryLogger } from '@/utils/logs'
  13. import { AppLogo } from './components/AppLogo'
  14. import { TopbarBase } from './components/_navigation/TopbarBase'
  15. import { useConfirmationModal } from './providers/confirmationModal'
  16. import { useOverlayManager } from './providers/overlayManager'
  17. import { LegalLayout } from './views/legal/LegalLayout'
  18. import { ViewerLayout } from './views/viewer/ViewerLayout'
  19. history.scrollRestoration = 'manual'
  20. const ROUTING_ANIMATION_OFFSET = 100
  21. const LoadableStudioLayout = loadable(() => import('./views/studio/StudioLayout'), {
  22. fallback: (
  23. <>
  24. <TopbarBase
  25. fullLogoNode={<AppLogo variant="studio" height={32} width={undefined} />}
  26. logoLinkUrl={absoluteRoutes.studio.index()}
  27. />
  28. <StudioLoading />
  29. </>
  30. ),
  31. })
  32. const LoadablePlaygroundLayout = loadable(() => import('./views/playground/PlaygroundLayout'), {
  33. fallback: <h1>Loading Playground...</h1>,
  34. })
  35. export const MainLayout: FC = () => {
  36. const scrollPosition = useRef<number>(0)
  37. const location = useLocation()
  38. const [searchParams] = useSearchParams()
  39. const navigationType = useNavigationType()
  40. const [cachedLocation, setCachedLocation] = useState(location)
  41. const locationState = location.state as RoutingState
  42. const [openDialog, closeDialog] = useConfirmationModal({
  43. title: 'Outdated browser detected',
  44. description:
  45. 'It seems the browser version you are using is not fully supported by Joystream. Some of the features may be broken or not accessible. For the best experience, please upgrade your browser to the latest version.',
  46. type: 'warning',
  47. primaryButton: {
  48. text: 'Click here to see instructions',
  49. onClick: () => window.open('https://www.whatismybrowser.com/guides/how-to-update-your-browser/auto'),
  50. },
  51. onExitClick: () => closeDialog(),
  52. })
  53. const { trackPageView } = useSegmentAnalytics()
  54. useEffect(() => {
  55. // had to include this timeout to make sure the page title is updated
  56. const trackRequestTimeout = setTimeout(
  57. () =>
  58. trackPageView(
  59. document.title,
  60. 'viewer',
  61. (location.pathname === absoluteRoutes.viewer.ypp() && searchParams.get('referrer')) || undefined
  62. ),
  63. 1000
  64. )
  65. if (!atlasConfig.analytics.sentry?.dsn) {
  66. return
  67. }
  68. const stopReplay = async () => await SentryLogger.replay?.stop()
  69. if (location.pathname === absoluteRoutes.viewer.ypp()) {
  70. if (SentryLogger.initialized && !SentryLogger.replay?.getReplayId()) {
  71. SentryLogger.replay?.start()
  72. }
  73. } else {
  74. if (SentryLogger.replay?.getReplayId()) {
  75. stopReplay()
  76. }
  77. }
  78. return () => {
  79. clearTimeout(trackRequestTimeout)
  80. }
  81. }, [location.pathname, trackPageView, searchParams])
  82. const { clearOverlays } = useOverlayManager()
  83. useEffect(() => {
  84. if (isBrowserOutdated) {
  85. openDialog()
  86. }
  87. }, [openDialog])
  88. useEffect(() => {
  89. if (location.pathname === cachedLocation.pathname) {
  90. return
  91. }
  92. clearOverlays()
  93. setCachedLocation(location)
  94. if (locationState?.overlaidLocation?.pathname === location.pathname) {
  95. // if exiting routing overlay, skip scroll to top
  96. return
  97. }
  98. if (navigationType !== 'POP') {
  99. scrollPosition.current = window.scrollY
  100. }
  101. // delay scroll to allow transition to finish first
  102. setTimeout(() => {
  103. window.scrollTo(0, navigationType !== 'POP' ? 0 : scrollPosition.current)
  104. }, parseInt(transitions.timings.routing) + ROUTING_ANIMATION_OFFSET)
  105. }, [location, cachedLocation, locationState, navigationType, clearOverlays])
  106. return (
  107. <>
  108. <CookiePopover />
  109. <Routes>
  110. <Route path={BASE_PATHS.viewer + '/*'} element={<ViewerLayout />} />
  111. <Route path={BASE_PATHS.legal + '/*'} element={<LegalLayout />} />
  112. <Route path={BASE_PATHS.studio + '/*'} element={<LoadableStudioLayout />} />
  113. <Route path={BASE_PATHS.playground + '/*'} element={<LoadablePlaygroundLayout />} />
  114. </Routes>
  115. </>
  116. )
  117. }