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