_layout-client.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useRef, useState } from 'react'
  4. import { usePathname, useRouter, useSelectedLayoutSegments } from 'next/navigation'
  5. import useSWR, { SWRConfig } from 'swr'
  6. import * as Sentry from '@sentry/react'
  7. import Header from '../components/header'
  8. import { fetchAppList } from '@/service/apps'
  9. import { fetchDatasets } from '@/service/datasets'
  10. import { fetchLanggeniusVersion, fetchUserProfile, logout } from '@/service/common'
  11. import Loading from '@/app/components/base/loading'
  12. import { AppContextProvider } from '@/context/app-context'
  13. import DatasetsContext from '@/context/datasets-context'
  14. import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
  15. const isDevelopment = process.env.NODE_ENV === 'development'
  16. export type ICommonLayoutProps = {
  17. children: React.ReactNode
  18. }
  19. const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
  20. useEffect(() => {
  21. const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn')
  22. if (!isDevelopment && SENTRY_DSN) {
  23. Sentry.init({
  24. dsn: SENTRY_DSN,
  25. integrations: [
  26. new Sentry.BrowserTracing({
  27. }),
  28. new Sentry.Replay(),
  29. ],
  30. tracesSampleRate: 0.1,
  31. replaysSessionSampleRate: 0.1,
  32. replaysOnErrorSampleRate: 1.0,
  33. })
  34. }
  35. }, [])
  36. const router = useRouter()
  37. const pathname = usePathname()
  38. const segments = useSelectedLayoutSegments()
  39. const pattern = pathname.replace(/.*\/app\//, '')
  40. const [idOrMethod] = pattern.split('/')
  41. const isNotDetailPage = idOrMethod === 'list'
  42. const pageContainerRef = useRef<HTMLDivElement>(null)
  43. const appId = isNotDetailPage ? '' : idOrMethod
  44. const { data: appList, mutate: mutateApps } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList)
  45. const { data: datasetList, mutate: mutateDatasets } = useSWR(segments[0] === 'datasets' ? { url: '/datasets', params: { page: 1 } } : null, fetchDatasets)
  46. const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile)
  47. const [userProfile, setUserProfile] = useState<UserProfileResponse>()
  48. const [langeniusVersionInfo, setLangeniusVersionInfo] = useState<LangGeniusVersionResponse>()
  49. const updateUserProfileAndVersion = async () => {
  50. if (userProfileResponse && !userProfileResponse.bodyUsed) {
  51. const result = await userProfileResponse.json()
  52. setUserProfile(result)
  53. const current_version = userProfileResponse.headers.get('x-version')
  54. const current_env = userProfileResponse.headers.get('x-env')
  55. const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
  56. setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
  57. }
  58. }
  59. useEffect(() => {
  60. updateUserProfileAndVersion()
  61. }, [userProfileResponse])
  62. if (!appList || !userProfile || !langeniusVersionInfo)
  63. return <Loading type='app' />
  64. const curAppId = segments[0] === 'app' && segments[2]
  65. const currentDatasetId = segments[0] === 'datasets' && segments[2]
  66. const currentDataset = datasetList?.data?.find(opt => opt.id === currentDatasetId)
  67. // if (!isNotDetailPage && !curApp) {
  68. // alert('app not found') // TODO: use toast. Now can not get toast context here.
  69. // // notify({ type: 'error', message: 'App not found' })
  70. // router.push('/apps')
  71. // }
  72. const onLogout = async () => {
  73. await logout({
  74. url: '/logout',
  75. params: {},
  76. })
  77. router.push('/signin')
  78. }
  79. return (
  80. <SWRConfig value={{
  81. shouldRetryOnError: false,
  82. }}>
  83. <AppContextProvider value={{ apps: appList.data, mutateApps, userProfile, mutateUserProfile, pageContainerRef }}>
  84. <DatasetsContext.Provider value={{ datasets: datasetList?.data || [], mutateDatasets, currentDataset }}>
  85. <div ref={pageContainerRef} className='relative flex flex-col h-full overflow-auto bg-gray-100'>
  86. <Header
  87. isBordered={['/apps', '/datasets'].includes(pathname)}
  88. curAppId={curAppId || ''}
  89. userProfile={userProfile}
  90. onLogout={onLogout}
  91. langeniusVersionInfo={langeniusVersionInfo}
  92. />
  93. {children}
  94. </div>
  95. </DatasetsContext.Provider>
  96. </AppContextProvider>
  97. </SWRConfig>
  98. )
  99. }
  100. export default React.memo(CommonLayout)