init: make /Projects work in app router

This commit is contained in:
Jonathan Summers-Muir committed 2024-10-11 17:52:29 +08:00
1 parent 0ba764c13f
commit 6f7acafeb7
21 files changed
+627 -83

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
'use client'
import 'react-data-grid/lib/styles.css'
import 'styles/code.scss'
import 'styles/contextMenu.scss'
import 'styles/date-picker.scss'
import 'styles/editor.scss'
import 'styles/graphiql-base.scss'
import 'styles/grid.scss'
import 'styles/main.scss'
import 'styles/markdown-preview.scss'
import 'styles/monaco.scss'
import 'styles/react-data-grid-logs.scss'
import 'styles/reactflow.scss'
import 'styles/storage.scss'
import 'styles/stripe.scss'
import 'styles/toast.scss'
import 'styles/ui.scss'
import 'ui/build/css/themes/dark.css'
import 'ui/build/css/themes/light.css'
import { loader } from '@monaco-editor/react'
import { TooltipProvider } from '@radix-ui/react-tooltip'
import * as Sentry from '@sentry/nextjs'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
import { createClient } from '@supabase/supabase-js'
// import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { ThemeProvider, useThemeSandbox } from 'common'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import Head from 'next/head'
import { ErrorInfo, useEffect, useMemo, useRef, useState } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { toast } from 'sonner'
import MetaFaviconsAppRouter from 'common/MetaFavicons/app-router-head'
import { AppBannerWrapper, RouteValidationWrapper } from 'components/interfaces/App'
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
import { StudioCommandMenu } from 'components/interfaces/App/CommandMenu'
import { FeaturePreviewContextProvider } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import FeaturePreviewModal from 'components/interfaces/App/FeaturePreview/FeaturePreviewModal'
import { GenerateSql } from 'components/interfaces/SqlGenerator/SqlGenerator'
import { ErrorBoundaryState } from 'components/ui/ErrorBoundaryState'
import FlagProvider from 'components/ui/Flag/FlagProvider'
import PageTelemetry from 'components/ui/PageTelemetry'
import { useRootQueryClient } from 'data/query-client'
import { AuthProvider } from 'lib/auth'
import { BASE_PATH, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
import { ProfileProvider } from 'lib/profile'
import { useAppStateSnapshot } from 'state/app-state'
import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store'
import { AppPropsWithLayout } from 'types'
import { SonnerToaster } from 'ui'
import { CommandProvider } from 'ui-patterns/CommandMenu'
import { ConsentToast } from 'ui-patterns/ConsentToast'
import { ReactQueryProvider } from './react-query-provider'
import { NextIntlClientProvider } from 'next-intl'
dayjs.extend(customParseFormat)
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(relativeTime)
loader.config({
// [Joshen] Attempt for offline support/bypass ISP issues is to store the assets required for monaco
// locally. We're however, only storing the assets which we need (based on what the network tab loads
// while using monaco). If we end up facing more effort trying to maintain this, probably to either
// use cloudflare or find some way to pull all the files from a CDN via a CLI, rather than tracking individual files
// The alternative was to import * as monaco from 'monaco-editor' but i couldn't get it working
paths: {
vs: IS_PLATFORM
? 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.0/min/vs'
: `${BASE_PATH}/monaco-editor`,
},
})
// [Joshen TODO] Once we settle on the new nav layout - we'll need a lot of clean up in terms of our layout components
// a lot of them are unnecessary and introduce way too many cluttered CSS especially with the height styles that make
// debugging way too difficult. Ideal scenario is we just have one AppLayout to control the height and scroll areas of
// the dashboard, all other layout components should not be doing that
function RootLayout({ children }: { children: React.ReactNode }) {
// const [queryClient] = useState(() => new QueryClient())
const snap = useAppStateSnapshot()
const consentToastId = useRef<string | number>()
// [Joshen] Some issues with using createBrowserSupabaseClient
const [supabase] = useState(() =>
IS_PLATFORM
? createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL as string,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string
)
: undefined
)
const AuthContainer = useMemo(
// eslint-disable-next-line react/display-name
() => (props: any) => {
return IS_PLATFORM ? (
<SessionContextProvider supabaseClient={supabase as any}>
<AuthProvider>{props.children}</AuthProvider>
</SessionContextProvider>
) : (
<AuthProvider>{props.children}</AuthProvider>
)
},
[supabase]
)
const errorBoundaryHandler = (error: Error, info: ErrorInfo) => {
Sentry.withScope(function (scope) {
scope.setTag('globalErrorBoundary', true)
Sentry.captureException(error)
})
console.error(error.stack)
}
useEffect(() => {
// Check for telemetry consent
if (typeof window !== 'undefined') {
const onAcceptConsent = () => {
snap.setIsOptedInTelemetry(true)
if (consentToastId.current) toast.dismiss(consentToastId.current)
}
const onOptOut = () => {
snap.setIsOptedInTelemetry(false)
if (consentToastId.current) toast.dismiss(consentToastId.current)
}
const hasAcknowledgedConsent = localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
if (IS_PLATFORM && hasAcknowledgedConsent === null) {
setTimeout(() => {
consentToastId.current = toast(
<ConsentToast onAccept={onAcceptConsent} onOptOut={onOptOut} />,
{
id: 'consent-toast',
position: 'bottom-right',
duration: Infinity,
}
)
}, 300)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useThemeSandbox()
const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test'
return (
<html lang="en">
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<body>
<ErrorBoundary FallbackComponent={ErrorBoundaryState} onError={errorBoundaryHandler}>
<ReactQueryProvider>
<AuthContainer>
<FlagProvider>
<NextIntlClientProvider locale="en">
<ProfileProvider>
<MetaFaviconsAppRouter applicationName="Supabase Studio" />
<PageTelemetry>
<TooltipProvider>
<RouteValidationWrapper>
<ThemeProvider
defaultTheme="system"
themes={['dark', 'light', 'classic-dark']}
enableSystem
disableTransitionOnChange
>
<AppBannerContextProvider>
<CommandProvider>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{children}
<StudioCommandMenu />
<GenerateSql />
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
<SonnerToaster position="top-right" />
</CommandProvider>
</AppBannerContextProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
</PageTelemetry>
{!isTestEnv && <HCaptchaLoadedStore />}
{!isTestEnv && (
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
)}
</ProfileProvider>
</NextIntlClientProvider>
</FlagProvider>
</AuthContainer>
</ReactQueryProvider>
</ErrorBoundary>
</body>
</html>
)
}
export default RootLayout
+108
View File
@@ -0,0 +1,108 @@
'use client'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { ProjectList } from 'components/interfaces/Home/ProjectList'
import HomePageActions from 'components/interfaces/HomePageActions'
import AccountLayout from 'components/layouts/AccountLayout/AccountLayout'
import AlertError from 'components/ui/AlertError'
import { Loading } from 'components/ui/Loading'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useAutoProjectsPrefetch } from 'data/projects/projects-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, PROJECT_STATUS } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
const ProjectsPage = () => {
// const router = useRouter()
const [search, setSearch] = useState('')
const [filterStatus, setFilterStatus] = useState<string[]>([
PROJECT_STATUS.ACTIVE_HEALTHY,
PROJECT_STATUS.INACTIVE,
])
const { data: organizations, isError, isSuccess } = useOrganizationsQuery()
useAutoProjectsPrefetch()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
const navLayoutV2 = useFlag('navigationLayoutV2')
const hasWindowLoaded = typeof window !== 'undefined'
useEffect(() => {
if (navLayoutV2 && isSuccess && hasWindowLoaded) {
const localStorageSlug = localStorage.getItem(
LOCAL_STORAGE_KEYS.RECENTLY_VISITED_ORGANIZATION
)
const verifiedSlug = organizations.some((org) => org.slug === localStorageSlug)
// if (organizations.length === 0) router.push('/new')
// else if (localStorageSlug && verifiedSlug) router.push(`/org/${localStorageSlug}`)
// else router.push(`/org/${organizations[0].slug}`)
}
if (!navLayoutV2 && isSuccess && hasWindowLoaded) {
// navigate to new page exactly once
const hasNoOrg = organizations.length === 0
const hasShownNewPage = localStorage.getItem(LOCAL_STORAGE_KEYS.UI_ONBOARDING_NEW_PAGE_SHOWN)
if (hasNoOrg && !hasShownNewPage) {
localStorage.setItem(LOCAL_STORAGE_KEYS.UI_ONBOARDING_NEW_PAGE_SHOWN, 'true')
// router.push('/new')
}
}
}, [navLayoutV2, isSuccess, hasWindowLoaded])
return (
<>
{isError && (
<div
className={`py-4 px-5 ${navLayoutV2 ? 'h-full flex items-center justify-center' : ''}`}
>
<AlertError subject="Failed to retrieve organizations" />
</div>
)}
{navLayoutV2 && (
<div className={`flex items-center justify-center h-full`}>
<Loading />
</div>
)}
{!navLayoutV2 && (
<div className="p-5">
{IS_PLATFORM && projectCreationEnabled && isSuccess && (
<HomePageActions
search={search}
filterStatus={filterStatus}
setSearch={setSearch}
setFilterStatus={setFilterStatus}
organizations={organizations}
/>
)}
<div className="my-6 space-y-8">
<ProjectList
search={search}
filterStatus={filterStatus}
resetFilterStatus={() => setFilterStatus(['ACTIVE_HEALTHY', 'INACTIVE'])}
/>
</div>
</div>
)}
</>
)
}
// ProjectsPage.getLayout = (page) =>
// // <AccountLayout
// // title="Dashboard"
// // breadcrumbs={[
// // {
// // key: `supabase-projects`,
// // label: 'Projects',
// // },
// // ]}
// // >
// ({ page })
// // </AccountLayout>
export default ProjectsPage
+11
View File
@@ -0,0 +1,11 @@
// app/providers.jsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export function ReactQueryProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient())
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
+7
View File
@@ -0,0 +1,7 @@
// make a normal page in nextjs app router
import { Button } from 'ui'
export default function Something() {
return <Button>Click me</Button>
}
@@ -1,4 +1,3 @@
import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect } from 'react'
import { toast } from 'sonner'
@@ -9,10 +8,12 @@ import useLatest from 'hooks/misc/useLatest'
import { useFlag } from 'hooks/ui/useFlag'
import { DEFAULT_HOME, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import { usePathname, useRouter } from 'next/navigation'
// Ideally these could all be within a _middleware when we use Next 12
const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const pathName = usePathname()
const { ref, slug, id } = useParams()
const navLayoutV2 = useFlag('navigationLayoutV2')
@@ -37,7 +38,7 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
* @returns a boolean
*/
function isExceptUrl() {
return excemptUrls.includes(router?.pathname)
return excemptUrls.includes(pathName ?? '')
}
const { data: organizations, isSuccess: orgsInitialized } = useOrganizationsQuery({
@@ -57,6 +58,7 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
if (!isValidOrg) {
toast.error('This organization does not exist')
router.push(navLayoutV2 ? `/org/${organizations[0].slug}` : DEFAULT_HOME)
return
}
}
@@ -113,10 +115,10 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
useEffect(() => {
if (ref !== undefined && id !== undefined) {
if (router.pathname.endsWith('/sql/[id]') && id !== 'new') {
if (pathName?.endsWith('/sql/[id]') && id !== 'new') {
snap.setDashboardHistory(ref, 'sql', id)
}
if (router.pathname.endsWith('/editor/[id]')) {
if (pathName?.endsWith('/editor/[id]')) {
snap.setDashboardHistory(ref, 'editor', id)
}
}
@@ -1,6 +1,6 @@
import { Filter, Search } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useRouter } from 'next/navigation'
import { IS_PLATFORM } from 'common'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
@@ -1,4 +1,4 @@
import { useRouter } from 'next/router'
import { useRouter } from 'next/navigation'
import { MouseEvent, useState } from 'react'
import { components } from 'api-types'
@@ -1,14 +1,14 @@
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { usePathname } from 'next/navigation'
import type { FallbackProps } from 'react-error-boundary'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
import { WarningIcon } from 'ui'
export const ErrorBoundaryState = ({ error, resetErrorBoundary }: FallbackProps) => {
const router = useRouter()
const message = `Path name: ${router.pathname}\n\n${error.stack}`
const pathname = usePathname()
const message = `Path name: ${pathname}\n\n${error.stack}`
const isRemoveChildError = error.message.includes("Failed to execute 'removeChild' on 'Node'")
return (
@@ -61,7 +61,10 @@ export const ErrorBoundaryState = ({ error, resetErrorBoundary }: FallbackProps)
Return to dashboard
</Button>
) : (
<Button type="outline" onClick={() => router.reload()}>
<Button
type="outline"
// onClick={() => router.reload()}
>
Reload dashboard
</Button>
)}
+23 -20
View File
@@ -1,6 +1,6 @@
import { Sha256 } from '@aws-crypto/sha256-browser'
import * as Sentry from '@sentry/nextjs'
import { useRouter } from 'next/router'
import { useRouter, usePathname } from 'next/navigation'
import { PropsWithChildren, useEffect, useState } from 'react'
import { useParams, useUser } from 'common'
@@ -24,13 +24,14 @@ const getAnonId = async (id: string) => {
}
const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
const pathname = usePathname()
const user = useUser()
const router = useRouter()
const { ref, slug } = useParams()
const snap = useAppStateSnapshot()
const organization = useSelectedOrganization()
const previousPathname = usePrevious(router.pathname)
const previousPathname = usePrevious(pathname)
const enablePostHogTelemetry = useFlag('enablePosthogChanges')
const consent =
typeof window !== 'undefined'
@@ -54,26 +55,30 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
useEffect(() => {
function handleRouteChange() {
if (snap.isOptedInTelemetry) handlePageTelemetry(window.location.href)
// Check telemetry consent and send page telemetry if opted in
if (snap.isOptedInTelemetry) {
handlePageTelemetry(window.location.href)
}
}
// Listen for page changes after a navigation or when the query changes
router.events.on('routeChangeComplete', handleRouteChange)
// Send telemetry on route change or when query changes
handleRouteChange()
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
// No need for event cleanup here in App Router
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router, snap.isOptedInTelemetry])
}, [pathname, snap.isOptedInTelemetry])
useEffect(() => {
// Send page telemetry on first page load
// Waiting for router ready before sending page_view
// if not the path will be dynamic route instead of the browser url
if (router.isReady && snap.isOptedInTelemetry) {
// Waiting for pathname to be available before sending page_view
// If not, the path might be a dynamic route instead of the browser URL
if (pathname && snap.isOptedInTelemetry) {
handlePageTelemetry(window.location.href)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady, snap.isOptedInTelemetry])
}, [pathname, snap.isOptedInTelemetry])
useEffect(() => {
// don't set the sentry user id if the user hasn't logged in (so that Sentry errors show null user id instead of anonymous id)
@@ -95,19 +100,17 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
}, [user?.id])
useEffect(() => {
const isLandingOnProjectRoute =
router.pathname.includes('[ref]') && previousPathname === router.pathname
const isLandingOnProjectRoute = pathname?.includes('[ref]') && previousPathname === pathname
const isEnteringProjectRoute =
!(previousPathname ?? '').includes('[ref]') && router.pathname.includes('[ref]')
!(previousPathname ?? '').includes('[ref]') && pathname?.includes('[ref]')
const isLeavingProjectRoute =
(previousPathname ?? '').includes('[ref]') && !router.pathname.includes('[ref]')
(previousPathname ?? '').includes('[ref]') && !pathname?.includes('[ref]')
const isLandingOnOrgRoute =
router.pathname.includes('[slug]') && previousPathname === router.pathname
const isLandingOnOrgRoute = pathname?.includes('[slug]') && previousPathname === pathname
const isEnteringOrgRoute =
!(previousPathname ?? '').includes('[slug]') && router.pathname.includes('[slug]')
!(previousPathname ?? '').includes('[slug]') && pathname?.includes('[slug]')
const isLeavingOrgRoute =
(previousPathname ?? '').includes('[slug]') && !router.pathname.includes('[slug]')
(previousPathname ?? '').includes('[slug]') && !pathname?.includes('[slug]')
if (trackTelemetryPH) {
if (ref && (isLandingOnProjectRoute || isEnteringProjectRoute)) {
@@ -123,7 +126,7 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [trackTelemetryPH, slug, ref, router.pathname])
}, [trackTelemetryPH, slug, ref, pathname])
useEffect(() => {
const handleBeforeUnload = async () => {
@@ -5,7 +5,8 @@ import { isBrowser } from 'common'
import { handleError, post } from 'data/fetchers'
import { useFlag } from 'hooks/ui/useFlag'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import { useRouter } from 'next/router'
import { usePathname } from 'next/navigation'
import { useLocale } from 'next-intl'
import type { ResponseError } from 'types'
type SendEventGA = components['schemas']['TelemetryEventBody']
@@ -47,7 +48,9 @@ export const useSendEventMutation = ({
UseMutationOptions<SendEventData, ResponseError, SendEventVariables>,
'mutationFn'
> = {}) => {
const router = useRouter()
const pathName = usePathname()
const locale = useLocale()
const usePostHogParameters = useFlag('enablePosthogChanges')
const title = typeof document !== 'undefined' ? document?.title : ''
@@ -57,10 +60,10 @@ export const useSendEventMutation = ({
? ({
page_url: window.location.href,
page_title: title,
pathname: router.pathname,
pathname: pathName,
ph: {
referrer,
language: router?.locale ?? 'en-US',
language: locale ?? 'en-US',
user_agent: navigator.userAgent,
search: window.location.search,
viewport_height: isBrowser ? window.innerHeight : 0,
@@ -71,10 +74,10 @@ export const useSendEventMutation = ({
: ({
page_referrer: referrer,
page_title: title,
page_location: router.asPath.split('#')[0],
page_location: pathName?.split('#')[0],
ga: {
screen_resolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,
language: router?.locale ?? 'en-US',
language: locale ?? 'en-US',
},
} as SendEventGA)
@@ -6,7 +6,7 @@ import { handleError, post } from 'data/fetchers'
import { Profile } from 'data/profile/types'
import { useFlag } from 'hooks/ui/useFlag'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import { useRouter } from 'next/router'
import { useLocale } from 'next-intl'
import type { ResponseError } from 'types'
type SendIdentifyGA = components['schemas']['TelemetryIdentifyBody']
@@ -47,7 +47,7 @@ export const useSendIdentifyMutation = ({
UseMutationOptions<SendIdentifyData, ResponseError, SendIdentifyVariables>,
'mutationFn'
> = {}) => {
const router = useRouter()
const locale = useLocale()
const usePostHogParameters = useFlag('enablePosthogChanges')
const payload = usePostHogParameters
@@ -55,7 +55,7 @@ export const useSendIdentifyMutation = ({
: ({
ga: {
screen_resolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,
language: router?.locale ?? 'en-US',
language: locale ?? 'en-US',
},
} as SendIdentifyGA)
@@ -2,7 +2,7 @@ import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { components } from 'api-types'
import { handleError, post } from 'data/fetchers'
import { useRouter } from 'next/router'
import { usePathname } from 'next/navigation'
import type { ResponseError } from 'types'
type SendPageLeaveBody = components['schemas']['TelemetryPageLeaveBody']
@@ -23,14 +23,14 @@ export const useSendPageLeaveMutation = ({
onError,
...options
}: Omit<UseMutationOptions<SendPageLeaveData, ResponseError>, 'mutationFn'> = {}) => {
const router = useRouter()
const pathname = usePathname()
const url = typeof window !== 'undefined' ? window.location.href : ''
const title = typeof document !== 'undefined' ? document?.title : ''
const body = {
page_url: url,
page_title: title,
pathname: router.pathname,
pathname: pathname,
} as SendPageLeaveBody
return useMutation<SendPageLeaveData, ResponseError>((vars) => sendPageLeave(body), {
@@ -4,7 +4,8 @@ import { components } from 'api-types'
import { isBrowser } from 'common'
import { handleError, post } from 'data/fetchers'
import { useFlag } from 'hooks/ui/useFlag'
import { useRouter } from 'next/router'
import { useLocale } from 'next-intl'
import { usePathname } from 'next/navigation'
import type { ResponseError } from 'types'
type SendPageGA = components['schemas']['TelemetryPageBody']
@@ -34,7 +35,8 @@ export const useSendPageMutation = ({
onError,
...options
}: Omit<UseMutationOptions<SendPageData, ResponseError, SendPageVariables>, 'mutationFn'> = {}) => {
const router = useRouter()
const pathname = usePathname()
const locale = useLocale()
const usePostHogParameters = useFlag('enablePosthogChanges')
const title = typeof document !== 'undefined' ? document?.title : ''
@@ -43,10 +45,10 @@ export const useSendPageMutation = ({
const payload = usePostHogParameters
? ({
page_title: title,
pathname: router.pathname,
pathname: pathname,
ph: {
referrer,
language: router?.locale ?? 'en-US',
language: locale ?? 'en-US',
user_agent: navigator.userAgent,
search: window.location.search,
viewport_height: isBrowser ? window.innerHeight : 0,
@@ -58,7 +60,7 @@ export const useSendPageMutation = ({
referrer,
ga: {
screen_resolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,
language: router?.locale ?? 'en-US',
language: locale ?? 'en-US',
},
} as SendPageGA)
+2
View File
@@ -1,3 +1,5 @@
'use client'
import { useContext } from 'react'
import FlagContext from 'components/ui/Flag/FlagContext'
+2
View File
@@ -83,6 +83,8 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
isSuccess,
])
// return <>{children}</>
return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
}
+2 -1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
+1
View File
@@ -77,6 +77,7 @@
"monaco-editor": "0.33.0",
"motion-number": "^0.1.7",
"next": "^14.2.10",
"next-intl": "^3.21.1",
"next-themes": "^0.3.0",
"nuqs": "^1.17.8",
"openai": "^4.20.1",
+40 -35
View File
@@ -55,6 +55,7 @@ import { AppPropsWithLayout } from 'types'
import { SonnerToaster } from 'ui'
import { CommandProvider } from 'ui-patterns/CommandMenu'
import { ConsentToast } from 'ui-patterns/ConsentToast'
import { NextIntlClientProvider } from 'next-intl'
dayjs.extend(customParseFormat)
dayjs.extend(utc)
@@ -159,42 +160,46 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
<Hydrate state={pageProps.dehydratedState}>
<AuthContainer>
<FlagProvider>
<ProfileProvider>
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<MetaFaviconsPagesRouter applicationName="Supabase Studio" />
<PageTelemetry>
<TooltipProvider>
<RouteValidationWrapper>
<ThemeProvider
defaultTheme="system"
themes={['dark', 'light', 'classic-dark']}
enableSystem
disableTransitionOnChange
>
<AppBannerContextProvider>
<CommandProvider>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<StudioCommandMenu />
<GenerateSql />
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
<SonnerToaster position="top-right" />
</CommandProvider>
</AppBannerContextProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
</PageTelemetry>
<NextIntlClientProvider locale="en">
<ProfileProvider>
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<MetaFaviconsPagesRouter applicationName="Supabase Studio" />
<PageTelemetry>
<TooltipProvider>
<RouteValidationWrapper>
<ThemeProvider
defaultTheme="system"
themes={['dark', 'light', 'classic-dark']}
enableSystem
disableTransitionOnChange
>
<AppBannerContextProvider>
<CommandProvider>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<StudioCommandMenu />
<GenerateSql />
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
<SonnerToaster position="top-right" />
</CommandProvider>
</AppBannerContextProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
</PageTelemetry>
{!isTestEnv && <HCaptchaLoadedStore />}
{!isTestEnv && <ReactQueryDevtools initialIsOpen={false} position="bottom-right" />}
</ProfileProvider>
{!isTestEnv && <HCaptchaLoadedStore />}
{!isTestEnv && (
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
)}
</ProfileProvider>
</NextIntlClientProvider>
</FlagProvider>
</AuthContainer>
</Hydrate>
File renamed without changes.
+89
View File
@@ -1863,6 +1863,7 @@
"monaco-editor": "0.33.0",
"motion-number": "^0.1.7",
"next": "^14.2.10",
"next-intl": "^3.21.1",
"next-themes": "^0.3.0",
"nuqs": "^1.17.8",
"openai": "^4.20.1",
@@ -6903,6 +6904,51 @@
"version": "0.1.6",
"license": "MIT"
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz",
"integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==",
"dependencies": {
"@formatjs/fast-memoize": "2.2.0",
"@formatjs/intl-localematcher": "0.5.4",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
"integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.7.9",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz",
"integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.1.0",
"@formatjs/icu-skeleton-parser": "1.8.3",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz",
"integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -24327,6 +24373,17 @@
"node": ">=12"
}
},
"node_modules/intl-messageformat": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.6.0.tgz",
"integrity": "sha512-AYKl/DY1nl75pJU8EK681JOVL40uQTNJe3yEMXKfydDFoz+5hNrM/PqjchueSMKGKCZKBVgeexqZwy3uC2B36Q==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.1.0",
"@formatjs/fast-memoize": "2.2.0",
"@formatjs/icu-messageformat-parser": "2.7.9",
"tslib": "^2.4.0"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"license": "MIT",
@@ -32344,6 +32401,26 @@
}
}
},
"node_modules/next-intl": {
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.21.1.tgz",
"integrity": "sha512-hQm4Wgq5i1lfOHAWmXBVl5d2/XAeddcjsrUmjotXEESzPSvW5j2t0Pr8AV8WorTILgqU748aXuenBhz5P78tdw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"dependencies": {
"@formatjs/intl-localematcher": "^0.5.4",
"negotiator": "^0.6.3",
"use-intl": "^3.21.1"
},
"peerDependencies": {
"next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/next-mdx-remote": {
"version": "4.4.1",
"license": "MPL-2.0",
@@ -41509,6 +41586,18 @@
"react": ">=16.8.0"
}
},
"node_modules/use-intl": {
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.21.1.tgz",
"integrity": "sha512-52kYgcydYkG9SX0ZZGt7W6WD2Va01hwe15bDgkXuaTdSxrF9fDu6hHTV5DxIuSmSSf/FEcBo/nodpw3ZhY31Lw==",
"dependencies": {
"@formatjs/fast-memoize": "^2.2.0",
"intl-messageformat": "^10.5.14"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/use-memo-one": {
"version": "1.1.3",
"license": "MIT",
@@ -0,0 +1,90 @@
import Head from 'next/head'
import React from 'react'
export const DEFAULT_FAVICON_THEME_COLOR = '1E1E1E'
export const DEFAULT_FAVICON_ROUTE = '/favicon'
const MetaFaviconsAppRouter = ({
applicationName,
route = DEFAULT_FAVICON_ROUTE,
themeColor = DEFAULT_FAVICON_THEME_COLOR,
includeRssXmlFeed = false,
includeManifest = false,
includeMsApplicationConfig = false,
}: {
applicationName: string
// alternative route to use for the favicons
route?: string
// theme color for the browser
themeColor?: string
// include RSS feed
includeRssXmlFeed?: boolean
// include manifest.json
includeManifest?: boolean
// include browserconfig.xml
includeMsApplicationConfig?: boolean
}) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH
return (
<Head>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="57x57" href={`${basePath}${route}/apple-icon-57x57.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="60x60" href={`${basePath}${route}/apple-icon-60x60.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="72x72" href={`${basePath}${route}/apple-icon-72x72.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="76x76" href={`${basePath}${route}/apple-icon-76x76.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="114x114" href={`${basePath}${route}/apple-icon-114x114.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="120x120" href={`${basePath}${route}/apple-icon-120x120.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="144x144" href={`${basePath}${route}/apple-icon-144x144.png`}/>
{/* prettier-ignore */}
<link rel="apple-touch-icon-precomposed" sizes="152x152" href={`${basePath}${route}/apple-icon-152x152.png`}/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-16x16.png`} sizes="16x16"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-32x32.png`} sizes="32x32"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-48x48.png`} sizes="48x48"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-96x96.png`} sizes="96x96"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-128.png`} sizes="128x128"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-180x180.png`} sizes="180x180"/>
{/* prettier-ignore */}
<link rel="icon" type="image/png" href={`${basePath}${route}/favicon-196x196.png`} sizes="196x196"/>
{/* prettier-ignore */}
<meta name="application-name" content={applicationName ?? '&nbsp;'} />
{/* prettier-ignore */}
<meta name="msapplication-TileColor" content={`#${themeColor}`} />
{/* prettier-ignore */}
<meta name="msapplication-TileImage" content={`${basePath}${route}/mstile-144x144.png`} />
{/* prettier-ignore */}
<meta name="msapplication-square70x70logo" content={`${basePath}${route}/mstile-70x70.png`} />
{/* prettier-ignore */}
<meta name="msapplication-square150x150logo" content={`${basePath}${route}/mstile-150x150.png`} />
{/* prettier-ignore */}
<meta name="msapplication-wide310x150logo" content={`${basePath}${route}/mstile-310x150.png`} />
{/* prettier-ignore */}
<meta name="msapplication-square310x310logo" content={`${basePath}${route}/mstile-310x310.png`} />
<meta name="theme-color" content={`#${themeColor}`} />
<link rel="shortcut icon" href={`${basePath}${route}/favicon.ico`} />
<link rel="icon" type="image/x-icon" href={`${basePath}${route}/favicon.ico`} />
<link rel="apple-touch-icon" href={`${basePath}${route}/favicon.ico`} />
{includeRssXmlFeed && (
<link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
)}
{includeManifest && <link rel="manifest" href={`${basePath}${route}/manifest.json`} />}
{includeMsApplicationConfig && (
<meta name="msapplication-config" content={`${basePath}${route}/browserconfig.xml`} />
)}
</Head>
)
}
export default MetaFaviconsAppRouter