mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
init: make /Projects work in app router
This commit is contained in:
1 parent
0ba764c13f
commit
6f7acafeb7
21 files changed
+627
-83
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useContext } from 'react'
|
||||
|
||||
import FlagContext from 'components/ui/Flag/FlagContext'
|
||||
|
||||
@@ -83,6 +83,8 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
isSuccess,
|
||||
])
|
||||
|
||||
// return <>{children}</>
|
||||
|
||||
return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -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.
|
||||
@@ -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
@@ -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.
Generated
+89
@@ -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 ?? ' '} />
|
||||
{/* 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
|
||||
Reference in new issue
Block a user