_app.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import '@/styles/code.css'
  2. import '@/styles/editor.css'
  3. import '@/styles/focus.css'
  4. import '@/styles/globals.css'
  5. import '@/styles/graphiql-base.css'
  6. import '@/styles/grid.css'
  7. import '@/styles/markdown-preview.css'
  8. import '@/styles/monaco.css'
  9. import '@/styles/react-data-grid-logs.css'
  10. import '@/styles/reactflow.css'
  11. import '@/styles/storage.css'
  12. import '@/styles/stripe.css'
  13. import '@/styles/ui.css'
  14. import 'react-data-grid/lib/styles.css'
  15. import 'ui-patterns/ShimmeringLoader/index.css'
  16. import { loader } from '@monaco-editor/react'
  17. import * as Sentry from '@sentry/nextjs'
  18. import { HydrationBoundary, QueryClientProvider } from '@tanstack/react-query'
  19. import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
  20. import {
  21. FeatureFlagProvider,
  22. getFlags,
  23. TelemetryTagManager,
  24. ThemeProvider,
  25. useThemeSandbox,
  26. } from 'common'
  27. import MetaFaviconsPagesRouter from 'common/MetaFavicons/pages-router'
  28. import dayjs from 'dayjs'
  29. import customParseFormat from 'dayjs/plugin/customParseFormat'
  30. import duration from 'dayjs/plugin/duration'
  31. import relativeTime from 'dayjs/plugin/relativeTime'
  32. import timezone from 'dayjs/plugin/timezone'
  33. import utc from 'dayjs/plugin/utc'
  34. import { DevToolbar, DevToolbarProvider, DevToolbarTrigger, type ExtraTab } from 'dev-tools'
  35. import dynamic from 'next/dynamic'
  36. import Head from 'next/head'
  37. import { NuqsAdapter } from 'nuqs/adapters/next/pages'
  38. import { ErrorInfo, useCallback, useEffect, useState, type ComponentProps } from 'react'
  39. import { ErrorBoundary } from 'react-error-boundary'
  40. import { TooltipProvider } from 'ui'
  41. import { TimestampInfoProvider } from 'ui-patterns'
  42. import { StudioCommandMenu } from '@/components/interfaces/App/CommandMenu'
  43. import { StudioCommandProvider as CommandProvider } from '@/components/interfaces/App/CommandMenu/StudioCommandProvider'
  44. import { FeaturePreviewContextProvider } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
  45. import { FeaturePreviewModal } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewModal'
  46. import { MonacoThemeProvider } from '@/components/interfaces/App/MonacoThemeProvider'
  47. import { RouteValidationWrapper } from '@/components/interfaces/App/RouteValidationWrapper'
  48. import { MainScrollContainerProvider } from '@/components/layouts/MainScrollContainerContext'
  49. import { BannerStackProvider } from '@/components/ui/BannerStack/BannerStackProvider'
  50. import { GlobalErrorBoundaryState } from '@/components/ui/ErrorBoundary/GlobalErrorBoundaryState'
  51. import { GlobalShortcuts } from '@/components/ui/GlobalShortcuts/GlobalShortcuts'
  52. import { getCLIReleaseVersion } from '@/data/misc/cli-release-version-query'
  53. import { useRootQueryClient } from '@/data/query-client'
  54. import { customFont, sourceCodePro } from '@/fonts'
  55. import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
  56. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  57. import { AuthProvider } from '@/lib/auth'
  58. import { API_URL, BASE_PATH, IS_PLATFORM, useDefaultProvider } from '@/lib/constants'
  59. import { TimezoneProvider, useTimezone } from '@/lib/datetime'
  60. import { ProfileProvider } from '@/lib/profile'
  61. import { Telemetry } from '@/lib/telemetry'
  62. import { Toaster } from '@/lib/toaster'
  63. import { AiAssistantStateContextProvider } from '@/state/ai-assistant-state'
  64. import type { AppPropsWithLayout } from '@/types'
  65. dayjs.extend(customParseFormat)
  66. dayjs.extend(utc)
  67. dayjs.extend(timezone)
  68. dayjs.extend(relativeTime)
  69. dayjs.extend(duration)
  70. // Keep dev-only components out of the production bundle
  71. const env = process.env.NEXT_PUBLIC_ENVIRONMENT
  72. const IS_DEV_TOOLBAR_ENABLED = env === 'local' || env === 'staging'
  73. const ResourceWarningsTab = IS_DEV_TOOLBAR_ENABLED
  74. ? dynamic(() =>
  75. import('@/components/ui/DevToolbar/ResourceWarningsTab').then((m) => m.ResourceWarningsTab)
  76. )
  77. : () => null
  78. const devToolbarExtraTabs: ExtraTab[] = IS_DEV_TOOLBAR_ENABLED
  79. ? [{ id: 'warnings', label: 'Warnings', content: <ResourceWarningsTab /> }]
  80. : []
  81. const FeatureFlagProviderWithOrgContext = ({
  82. children,
  83. ...props
  84. }: ComponentProps<typeof FeatureFlagProvider>) => {
  85. const { data: selectedOrganization } = useSelectedOrganizationQuery({ enabled: IS_PLATFORM })
  86. const cloudProvider = useDefaultProvider()
  87. const getConfigCatFlags = useCallback(
  88. (userEmail?: string) => {
  89. const customAttributes: Record<string, string> = {}
  90. if (cloudProvider) customAttributes.cloud_provider = cloudProvider
  91. if (selectedOrganization?.plan?.id) customAttributes.plan = selectedOrganization.plan.id
  92. return getFlags(userEmail, customAttributes)
  93. },
  94. [cloudProvider, selectedOrganization?.plan?.id]
  95. )
  96. return (
  97. <FeatureFlagProvider
  98. {...props}
  99. getConfigCatFlags={getConfigCatFlags}
  100. organizationSlug={selectedOrganization?.slug ?? undefined}
  101. >
  102. {children}
  103. </FeatureFlagProvider>
  104. )
  105. }
  106. const TimestampInfoTimezoneBridge = ({ children }: { children: React.ReactNode }) => {
  107. const { timezone } = useTimezone()
  108. return <TimestampInfoProvider timezone={timezone}>{children}</TimestampInfoProvider>
  109. }
  110. loader.config({
  111. // [Joshen] Attempt for offline support/bypass ISP issues is to store the assets required for monaco
  112. // locally. We're however, only storing the assets which we need (based on what the network tab loads
  113. // while using monaco). If we end up facing more effort trying to maintain this, probably to either
  114. // use cloudflare or find some way to pull all the files from a CDN via a CLI, rather than tracking individual files
  115. // The alternative was to import * as monaco from 'monaco-editor' but i couldn't get it working
  116. paths: {
  117. vs: IS_PLATFORM
  118. ? 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs'
  119. : `${BASE_PATH}/monaco-editor`,
  120. },
  121. })
  122. // [Joshen TODO] Once we settle on the new nav layout - we'll need a lot of clean up in terms of our layout components
  123. // a lot of them are unnecessary and introduce way too many cluttered CSS especially with the height styles that make
  124. // debugging way too difficult. Ideal scenario is we just have one AppLayout to control the height and scroll areas of
  125. // the dashboard, all other layout components should not be doing that
  126. function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
  127. const queryClient = useRootQueryClient()
  128. const { appTitle } = useCustomContent(['app:title'])
  129. const [isCLI, setIsCLI] = useState(false)
  130. const getLayout = Component.getLayout ?? ((page) => page)
  131. const errorBoundaryHandler = (error: Error, _info: ErrorInfo) => {
  132. Sentry.withScope(function (scope) {
  133. scope.setTag('globalErrorBoundary', true)
  134. const eventId = Sentry.captureException(error)
  135. // Attach the Sentry event ID to the error object so it can be accessed by the error boundary
  136. if (eventId && error && typeof error === 'object') {
  137. ;(error as any).sentryId = eventId
  138. }
  139. })
  140. console.error(error.stack)
  141. }
  142. useThemeSandbox()
  143. const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test'
  144. // [Joshen] Should target hosted staging, local dev, and local CLI only
  145. const isNonProdEnv = (IS_PLATFORM && process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod') || isCLI
  146. const checkCliEnvironment = async () => {
  147. const data = await getCLIReleaseVersion()
  148. if (!!data.current) setIsCLI(true)
  149. }
  150. useEffect(() => {
  151. if (!IS_PLATFORM) checkCliEnvironment()
  152. }, [])
  153. return (
  154. <QueryClientProvider client={queryClient}>
  155. <ErrorBoundary FallbackComponent={GlobalErrorBoundaryState} onError={errorBoundaryHandler}>
  156. <NuqsAdapter>
  157. <HydrationBoundary state={pageProps.dehydratedState}>
  158. <AuthProvider>
  159. <FeatureFlagProviderWithOrgContext API_URL={API_URL} enabled={IS_PLATFORM}>
  160. <ProfileProvider>
  161. <TimezoneProvider>
  162. <TimestampInfoTimezoneBridge>
  163. <Head>
  164. <title>{appTitle ?? 'Briven'}</title>
  165. <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  166. <meta property="og:image" content={`${BASE_PATH}/img/briven-logo.png`} />
  167. <meta name="googlebot" content="notranslate" />
  168. {/* [Alaister]: This has to be an inline style tag here and not a separate component due to next/font */}
  169. <style
  170. dangerouslySetInnerHTML={{
  171. __html: `:root{--font-custom:${customFont.style.fontFamily};--font-source-code-pro:${sourceCodePro.style.fontFamily};}`,
  172. }}
  173. />
  174. {/* Speed up initial API loading times by pre-connecting to the API domain */}
  175. {IS_PLATFORM && (
  176. <link
  177. rel="preconnect"
  178. href={new URL(API_URL).origin}
  179. crossOrigin="use-credentials"
  180. />
  181. )}
  182. </Head>
  183. <MetaFaviconsPagesRouter
  184. includeManifest
  185. applicationName="Briven Studio"
  186. route={isNonProdEnv ? '/favicon/staging' : '/favicon'}
  187. />
  188. <TooltipProvider delayDuration={0}>
  189. <RouteValidationWrapper>
  190. <ThemeProvider>
  191. <DevToolbarProvider apiUrl={API_URL}>
  192. <AiAssistantStateContextProvider>
  193. <CommandProvider>
  194. <BannerStackProvider>
  195. <FeaturePreviewContextProvider>
  196. <MainScrollContainerProvider>
  197. {getLayout(<Component {...pageProps} />)}
  198. </MainScrollContainerProvider>
  199. <GlobalShortcuts />
  200. <StudioCommandMenu />
  201. <FeaturePreviewModal />
  202. </FeaturePreviewContextProvider>
  203. </BannerStackProvider>
  204. <Toaster />
  205. <MonacoThemeProvider />
  206. </CommandProvider>
  207. </AiAssistantStateContextProvider>
  208. <DevToolbar extraTabs={devToolbarExtraTabs} />
  209. <DevToolbarTrigger />
  210. </DevToolbarProvider>
  211. </ThemeProvider>
  212. </RouteValidationWrapper>
  213. </TooltipProvider>
  214. <Telemetry />
  215. {!isTestEnv && (
  216. <ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-left" />
  217. )}
  218. </TimestampInfoTimezoneBridge>
  219. </TimezoneProvider>
  220. </ProfileProvider>
  221. </FeatureFlagProviderWithOrgContext>
  222. </AuthProvider>
  223. </HydrationBoundary>
  224. </NuqsAdapter>
  225. <TelemetryTagManager />
  226. </ErrorBoundary>
  227. </QueryClientProvider>
  228. )
  229. }
  230. export default CustomApp