mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 14:05:05 +08:00
Merge remote-tracking branch 'origin/master' into feat/pricing-jsonld-faq-product
# Conflicts: # apps/www/app/pricing/page.tsx
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import { ThemeProvider } from 'common'
|
||||
import { Provider as JotaiProvider } from 'jotai'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
import { TooltipProvider } from 'ui'
|
||||
|
||||
import { MobileSidebarProvider } from '@/context/mobile-sidebar-context'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<ThemeProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<MobileSidebarProvider>{children}</MobileSidebarProvider>
|
||||
</TooltipProvider>
|
||||
</NextThemesProvider>
|
||||
</ThemeProvider>
|
||||
</JotaiProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import '@/styles/globals.css'
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
|
||||
import { customFont, sourceCodePro } from './fonts'
|
||||
import { ThemeProvider } from './Providers'
|
||||
import { Providers } from './Providers'
|
||||
import { Toaster } from './toaster'
|
||||
|
||||
const className = `${customFont.variable} ${sourceCodePro.variable}`
|
||||
@@ -131,16 +131,12 @@ export default async function Layout({ children }: RootLayoutProps) {
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<ThemeProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<Providers>
|
||||
<div vaul-drawer-wrapper="">
|
||||
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
|
||||
</div>
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"contentlayer2": "0.4.6",
|
||||
"common": "workspace:*",
|
||||
"date-fns": "^2.30.0",
|
||||
"dayjs": "1.11.13",
|
||||
"eslint-config-supabase": "workspace:*",
|
||||
@@ -29,7 +30,7 @@
|
||||
"markdown-wasm": "^1.2.0",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-data-grid": "7.0.0-beta.47",
|
||||
"react-day-picker": "^9.11.1",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronRight, Play, Sparkles } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useTheme } from 'next-themes'
|
||||
// End of third-party imports
|
||||
|
||||
import { isFeatureEnabled, useBreakpoint } from 'common'
|
||||
import { ChevronRight, Play, Sparkles } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import Link from 'next/link'
|
||||
import { cn, IconBackground } from 'ui'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
|
||||
import { getCustomContent } from '../lib/custom-content/getCustomContent'
|
||||
import DocsCoverLogo from './DocsCoverLogo'
|
||||
|
||||
@@ -41,7 +41,7 @@ const HomePageCover = (props) => {
|
||||
const iconSize = isXs ? 'sm' : 'lg'
|
||||
const { homepageHeading } = getCustomContent(['homepage:heading'])
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isLightMode = resolvedTheme !== 'dark'
|
||||
const isLightMode = !resolvedTheme?.includes('dark')
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
|
||||
@@ -381,9 +381,9 @@ with check (
|
||||
|
||||
## Interaction with Postgres Changes
|
||||
|
||||
Realtime Postgres Changes are separate from Channel authorization. The `private` Channel option does not apply to Postgres Changes.
|
||||
When using Postgres Changes on tables with RLS, database records are sent only to clients who are allowed to read them based on your RLS policies.
|
||||
|
||||
When using Postgres Changes with RLS, database records are sent only to clients who are allowed to read them based on your RLS policies.
|
||||
Private and public channels can subscribe to Postgres Changes.
|
||||
|
||||
## Updating RLS policies
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ function GlobalProviders({ children }: PropsWithChildren) {
|
||||
<DevToolbarProvider apiUrl={API_URL}>
|
||||
<PageTelemetry />
|
||||
<ScrollRestoration />
|
||||
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
<ThemeProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<DocsCommandProvider>
|
||||
<div className="flex flex-col">
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"next": "^15.5.15",
|
||||
"next-mdx-remote": "^6.0.0",
|
||||
"next-plugin-yaml": "^1.0.1",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"nuqs": "^1.19.1",
|
||||
"openai": "^4.75.1",
|
||||
"openapi-fetch": "0.12.4",
|
||||
|
||||
@@ -142,6 +142,7 @@ Jordi Enric
|
||||
José Luis Ledesma
|
||||
Joshen Lim
|
||||
Julien Goux
|
||||
Kai M
|
||||
Kalleby Santos
|
||||
Kanishk Dudeja
|
||||
Kamil Ogórek
|
||||
@@ -222,6 +223,7 @@ Sam Rome
|
||||
Sam Rose
|
||||
Samir Ketema
|
||||
Sana Cordeaux
|
||||
Sasi Kanumuri
|
||||
Sara Read
|
||||
Sean Oliver
|
||||
Sean Romberg
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@import './../../../packages/ui/build/css/source/global.css';
|
||||
@import './../../../packages/ui/build/css/themes/dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/faux-classic-dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/light.css';
|
||||
|
||||
@config '../tailwind.config.cjs';
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { AuthProvider, ThemeProvider } from 'common'
|
||||
import { Provider as JotaiProvider } from 'jotai'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { TooltipProvider } from 'ui'
|
||||
|
||||
import { FrameworkProvider } from '@/context/framework-context'
|
||||
import { MobileMenuProvider } from '@/context/mobile-menu-context'
|
||||
import { AuthProvider } from 'common'
|
||||
import { TooltipProvider } from 'ui'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<ThemeProvider>
|
||||
<MobileMenuProvider>
|
||||
<FrameworkProvider>
|
||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
||||
</FrameworkProvider>
|
||||
</MobileMenuProvider>
|
||||
</NextThemesProvider>
|
||||
</ThemeProvider>
|
||||
</JotaiProvider>
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FeatureFlagProvider, TelemetryTagManager } from 'common'
|
||||
import { genFaviconData } from 'common/MetaFavicons/app-router'
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
import { ThemeProvider } from './Providers'
|
||||
import { Providers } from './Providers'
|
||||
import { Toaster } from './toaster'
|
||||
import { API_URL } from '@/lib/constants'
|
||||
|
||||
@@ -47,14 +47,10 @@ export default async function Layout({ children }: RootLayoutProps) {
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<TelemetryTagManager />
|
||||
<FeatureFlagProvider API_URL={API_URL}>
|
||||
<ThemeProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<Providers>
|
||||
{children}
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</Providers>
|
||||
</FeatureFlagProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"lucide-react": "*",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-wrap-balancer": "^1.1.0",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@import './../../../packages/ui/build/css/source/global.css';
|
||||
@import './../../../packages/ui/build/css/themes/dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/classic-dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/light.css';
|
||||
|
||||
@config '../tailwind.config.js';
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormInputGroupInput,
|
||||
Input,
|
||||
Input_Shadcn_,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
Switch,
|
||||
useWatch,
|
||||
} from 'ui'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import * as z from 'zod'
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -284,14 +286,17 @@ export const CustomAuthProvidersList = () => {
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2 flex-wrap">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2">
|
||||
<Input
|
||||
placeholder="Search custom providers"
|
||||
size="tiny"
|
||||
icon={<Search />}
|
||||
value={filterString}
|
||||
className="w-full lg:w-52"
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
<InputGroup className="w-full lg:w-52">
|
||||
<InputGroupInput
|
||||
size="tiny"
|
||||
placeholder="Search custom providers"
|
||||
value={filterString}
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
<InputGroupAddon>
|
||||
<Search />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FilterPopover
|
||||
name="Provider Type"
|
||||
options={CUSTOM_PROVIDER_TYPE_OPTIONS}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Check, Webhook } from 'lucide-react'
|
||||
import { Badge, copyToClipboard, Input } from 'ui'
|
||||
import { Badge } from 'ui'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
|
||||
import { Hook } from './hooks.constants'
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
@@ -32,31 +33,33 @@ export const HookCard = ({ hook, onSelect }: HookCardProps) => {
|
||||
<span className="text-foreground">Postgres function</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-foreground-light w-20">schema</span>
|
||||
<label htmlFor="schema" className="text-foreground-light w-20">
|
||||
schema
|
||||
</label>
|
||||
<Input
|
||||
id="schema"
|
||||
title={hook.method.schema}
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100 flex-1"
|
||||
containerClassName="flex-1"
|
||||
className="font-mono text-xs md:text-xs disabled:text-foreground-light opacity-100"
|
||||
value={hook.method.schema}
|
||||
onCopy={() =>
|
||||
hook.method.type === 'postgres' && copyToClipboard(hook.method.schema)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-foreground-light w-20">function</span>
|
||||
<label htmlFor="functionName" className="text-foreground-light w-20">
|
||||
function
|
||||
</label>
|
||||
<Input
|
||||
id="functionName"
|
||||
title={hook.method.functionName}
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100 flex-1"
|
||||
containerClassName="flex-1"
|
||||
className="font-mono text-xs md:text-xs disabled:text-foreground-light opacity-100"
|
||||
value={hook.method.functionName}
|
||||
onCopy={() =>
|
||||
hook.method.type === 'postgres' && copyToClipboard(hook.method.functionName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,26 +70,33 @@ export const HookCard = ({ hook, onSelect }: HookCardProps) => {
|
||||
<span className="text-foreground">HTTPS endpoint</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-foreground-light w-20">endpoint</span>
|
||||
<label htmlFor="url" className="text-foreground-light w-20">
|
||||
endpoint
|
||||
</label>
|
||||
<Input
|
||||
id="url"
|
||||
title={hook.method.url}
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100 flex-1"
|
||||
containerClassName="flex-1"
|
||||
className="font-mono text-xs md:text-xs disabled:text-foreground-light opacity-100"
|
||||
value={hook.method.url}
|
||||
onCopy={() => hook.method.type === 'https' && copyToClipboard(hook.method.url)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-foreground-light w-20">secret</span>
|
||||
<label htmlFor="secret" className="text-foreground-light w-20">
|
||||
secret
|
||||
</label>
|
||||
<Input
|
||||
id="secret"
|
||||
copy
|
||||
title={hook.method.secret}
|
||||
reveal={true}
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100 flex-1"
|
||||
containerClassName="flex-1"
|
||||
className="font-mono text-xs md:text-xs disabled:text-foreground-light opacity-100"
|
||||
value={hook.method.secret}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -231,14 +233,17 @@ export const OAuthAppsList = () => {
|
||||
)}
|
||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2 flex-wrap">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2">
|
||||
<Input
|
||||
placeholder="Search OAuth apps"
|
||||
size="tiny"
|
||||
icon={<Search />}
|
||||
value={filterString}
|
||||
className="w-full lg:w-52"
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
<InputGroup className="w-full lg:w-52">
|
||||
<InputGroupInput
|
||||
size="tiny"
|
||||
placeholder="Search OAuth apps"
|
||||
value={filterString}
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
<InputGroupAddon>
|
||||
<Search />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FilterPopover
|
||||
name="Registration Type"
|
||||
options={OAUTH_APP_REGISTRATION_TYPE_OPTIONS}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input } from 'ui'
|
||||
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from 'ui'
|
||||
|
||||
interface PolicyNameProps {
|
||||
name: string
|
||||
@@ -16,16 +16,18 @@ export const PolicyName = ({ name = '', limit = 100, onUpdatePolicyName }: Polic
|
||||
<p className="text-sm text-foreground-lighter">A descriptive name for your policy</p>
|
||||
</div>
|
||||
<div className="relative md:w-2/3">
|
||||
<Input
|
||||
id="policy-name"
|
||||
value={name}
|
||||
onChange={(e) => onUpdatePolicyName(e.target.value)}
|
||||
actions={
|
||||
<span className="mr-3 text-sm text-foreground-lighter">
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
id="policy-name"
|
||||
value={name}
|
||||
onChange={(e) => onUpdatePolicyName(e.target.value)}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>
|
||||
{name.length}/{limit}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { Search } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Badge, cn, HoverCard, HoverCardContent, HoverCardTrigger, Input } from 'ui'
|
||||
import {
|
||||
Badge,
|
||||
cn,
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from 'ui'
|
||||
import { SimpleCodeBlock } from 'ui-patterns/SimpleCodeBlock'
|
||||
|
||||
import {
|
||||
@@ -56,14 +65,17 @@ export const PolicyTemplates = ({
|
||||
<label className="sr-only" htmlFor="template-search">
|
||||
Search templates
|
||||
</label>
|
||||
<Input
|
||||
size="small"
|
||||
id="template-search"
|
||||
icon={<Search className="text-foreground-muted" />}
|
||||
placeholder="Search templates"
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
id="template-search"
|
||||
placeholder="Search templates"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<InputGroupAddon>
|
||||
<Search />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
|
||||
{search.length > 0 && filteredTemplates.length === 0 && (
|
||||
<NoSearchResults searchString={search} className="min-w-full" />
|
||||
|
||||
96
apps/studio/components/interfaces/DatabaseNavShortcuts.tsx
Normal file
96
apps/studio/components/interfaces/DatabaseNavShortcuts.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import { useGenerateDatabaseMenu } from '@/components/layouts/DatabaseLayout/DatabaseMenu.utils'
|
||||
import { SHORTCUT_IDS, type ShortcutId } from '@/state/shortcuts/registry'
|
||||
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
||||
|
||||
export const DatabaseNavShortcuts = () => {
|
||||
const router = useRouter()
|
||||
const groups = useGenerateDatabaseMenu()
|
||||
|
||||
const urlByShortcut = useMemo(() => {
|
||||
const map = new Map<ShortcutId, string>()
|
||||
for (const group of groups) {
|
||||
for (const item of group.items) {
|
||||
if (item.shortcutId && item.url) map.set(item.shortcutId, item.url)
|
||||
}
|
||||
}
|
||||
return map
|
||||
}, [groups])
|
||||
|
||||
const navigate = useCallback(
|
||||
(id: ShortcutId) => {
|
||||
const url = urlByShortcut.get(id)
|
||||
if (url) router.push(url)
|
||||
},
|
||||
[router, urlByShortcut]
|
||||
)
|
||||
|
||||
useShortcut(SHORTCUT_IDS.NAV_DATABASE_TABLES, () => navigate(SHORTCUT_IDS.NAV_DATABASE_TABLES), {
|
||||
enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_TABLES),
|
||||
})
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_TRIGGERS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_TRIGGERS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_TRIGGERS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_INDEXES,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_INDEXES),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_INDEXES) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER) }
|
||||
)
|
||||
useShortcut(SHORTCUT_IDS.NAV_DATABASE_ROLES, () => navigate(SHORTCUT_IDS.NAV_DATABASE_ROLES), {
|
||||
enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_ROLES),
|
||||
})
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_BACKUPS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_BACKUPS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_BACKUPS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS) }
|
||||
)
|
||||
useShortcut(SHORTCUT_IDS.NAV_DATABASE_TYPES, () => navigate(SHORTCUT_IDS.NAV_DATABASE_TYPES), {
|
||||
enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_TYPES),
|
||||
})
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_SETTINGS,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_SETTINGS),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_SETTINGS) }
|
||||
)
|
||||
useShortcut(
|
||||
SHORTCUT_IDS.NAV_DATABASE_REPLICATION,
|
||||
() => navigate(SHORTCUT_IDS.NAV_DATABASE_REPLICATION),
|
||||
{ enabled: urlByShortcut.has(SHORTCUT_IDS.NAV_DATABASE_REPLICATION) }
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
Popover_Shadcn_,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
@@ -192,12 +195,12 @@ export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => {
|
||||
name="values.timeoutMs"
|
||||
render={({ field: { ref, ...rest } }) => (
|
||||
<FormItemLayout label="Timeout" layout="vertical" className="gap-1">
|
||||
<Input
|
||||
{...rest}
|
||||
type="number"
|
||||
placeholder="1000"
|
||||
actions={<p className="text-foreground-light pr-2">ms</p>}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="1000" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText> ms</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,11 @@ import {
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
Input_Shadcn_ as Input,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
@@ -63,12 +67,12 @@ export const HttpRequestSection = ({ form }: HttpRequestSectionProps) => {
|
||||
name="values.timeoutMs"
|
||||
render={({ field: { ref, ...rest } }) => (
|
||||
<FormItemLayout label="Timeout" className="gap-1">
|
||||
<Input
|
||||
{...rest}
|
||||
type="number"
|
||||
placeholder="1000"
|
||||
actions={<p className="text-foreground-light pr-2">ms</p>}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="1000" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText> ms</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { UseFormReturn } from 'react-hook-form'
|
||||
import { FormField, Input, Separator, SheetSection } from 'ui'
|
||||
import {
|
||||
FormField,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
Separator,
|
||||
SheetSection,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { CreateQueueForm } from './CreateQueueSheet.schema'
|
||||
@@ -21,12 +29,12 @@ export function PartitionConfigFields({ form }: { form: UseFormReturn<CreateQueu
|
||||
description="Number of messages per partition"
|
||||
className="gap-1"
|
||||
>
|
||||
<Input
|
||||
{...rest}
|
||||
type="number"
|
||||
placeholder="10000"
|
||||
actions={<p className="text-foreground-light pr-2">messages</p>}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="10000" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>messages</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
@@ -39,12 +47,12 @@ export function PartitionConfigFields({ form }: { form: UseFormReturn<CreateQueu
|
||||
description="Partitions older than this many messages behind the latest will be dropped"
|
||||
className="gap-1"
|
||||
>
|
||||
<Input
|
||||
{...rest}
|
||||
type="number"
|
||||
placeholder="100000"
|
||||
actions={<p className="text-foreground-light pr-2">messages</p>}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="10000" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>messages</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,16 @@ import { useParams } from 'common'
|
||||
import { useEffect } from 'react'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import { Form, FormControl, FormField, Input, Modal } from 'ui'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import z from 'zod'
|
||||
|
||||
@@ -102,12 +111,12 @@ export const SendMessageModal = ({ visible, onClose }: SendMessageModalProps) =>
|
||||
description="Time in seconds before the message becomes available for reading."
|
||||
>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...rest}
|
||||
type="number"
|
||||
placeholder="1"
|
||||
actions={<p className="text-foreground-light pr-2">sec</p>}
|
||||
/>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="1" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>sec</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { PropsWithChildren, ReactNode } from 'react'
|
||||
import { cn, Menu } from 'ui'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import type { SidebarSection } from './AccountLayout.types'
|
||||
import { getActiveKey, toSubMenuSections } from './AccountLayout.utils'
|
||||
import { SubMenu } from '@/components/ui/ProductMenu/SubMenu'
|
||||
|
||||
interface WithSidebarProps {
|
||||
title?: string
|
||||
sections: SidebarSection[]
|
||||
header?: ReactNode
|
||||
subitems?: any[]
|
||||
subitemsParentKey?: number
|
||||
hideSidebar?: boolean
|
||||
customSidebarContent?: ReactNode
|
||||
backToDashboardURL?: string
|
||||
}
|
||||
|
||||
@@ -21,24 +19,17 @@ export const WithSidebar = ({
|
||||
header,
|
||||
children,
|
||||
sections,
|
||||
subitems,
|
||||
subitemsParentKey,
|
||||
hideSidebar = false,
|
||||
customSidebarContent,
|
||||
backToDashboardURL,
|
||||
}: PropsWithChildren<WithSidebarProps>) => {
|
||||
const noContent = !sections && !customSidebarContent
|
||||
const noContent = !sections
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row h-full">
|
||||
{!hideSidebar && !noContent && (
|
||||
{!noContent && (
|
||||
<SidebarContent
|
||||
title={title}
|
||||
header={header}
|
||||
sections={sections}
|
||||
subitems={subitems}
|
||||
subitemsParentKey={subitemsParentKey}
|
||||
customSidebarContent={customSidebarContent}
|
||||
backToDashboardURL={backToDashboardURL}
|
||||
className="hidden md:flex"
|
||||
/>
|
||||
@@ -53,12 +44,12 @@ export const WithSidebar = ({
|
||||
export const SidebarContent = ({
|
||||
header,
|
||||
sections,
|
||||
subitems,
|
||||
subitemsParentKey,
|
||||
customSidebarContent,
|
||||
backToDashboardURL,
|
||||
className,
|
||||
}: PropsWithChildren<Omit<WithSidebarProps, 'breadcrumbs'>> & { className?: string }) => {
|
||||
const page = getActiveKey(sections)
|
||||
const subMenuSections = toSubMenuSections(sections)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -85,49 +76,8 @@ export const SidebarContent = ({
|
||||
)}
|
||||
{header && header}
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className="flex flex-col space-y-8">
|
||||
<Menu type="pills">
|
||||
{customSidebarContent}
|
||||
{sections.map((section, idx) => (
|
||||
<div key={section.key || section.heading}>
|
||||
{Boolean(section.heading) ? (
|
||||
<SectionWithHeaders
|
||||
key={section.key}
|
||||
section={section}
|
||||
subitems={subitems}
|
||||
subitemsParentKey={subitemsParentKey}
|
||||
/>
|
||||
) : (
|
||||
<div className="my-6 space-y-8">
|
||||
<div className="mx-3">
|
||||
{section.links.map((link, i: number) => {
|
||||
const isActive = link.isActive && !subitems
|
||||
return (
|
||||
<Menu.Item
|
||||
key={`${link.key}-${i}`}
|
||||
rounded
|
||||
active={isActive}
|
||||
icon={link.icon}
|
||||
>
|
||||
<Link href={link.href || ''} className="block">
|
||||
<div className="flex w-full items-center justify-between gap-1">
|
||||
<div className="flex items-center gap-2 truncate w-full">
|
||||
<span className="truncate">{link.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{idx !== sections.length - 1 && (
|
||||
<div className="h-px w-full bg-border-overlay" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Menu>
|
||||
<div className="flex flex-col">
|
||||
<SubMenu sections={subMenuSections} page={page} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,41 +85,3 @@ export const SidebarContent = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface SectionWithHeadersProps {
|
||||
section: SidebarSection
|
||||
subitems?: any[]
|
||||
subitemsParentKey?: number
|
||||
}
|
||||
|
||||
const SectionWithHeaders = ({ section, subitems }: SectionWithHeadersProps) => (
|
||||
<div key={section.heading} className="my-6 space-y-8">
|
||||
<div className="mx-3">
|
||||
{section.heading && (
|
||||
<Menu.Group
|
||||
title={
|
||||
<div className="flex flex-col space-y-2 uppercase font-mono">
|
||||
<span>{section.heading}</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
{section.links.map((link, i: number) => {
|
||||
const isActive = link.isActive && !subitems
|
||||
return (
|
||||
<Menu.Item key={`${link.key}-${i}`} rounded active={isActive} icon={link.icon}>
|
||||
<Link href={link.href || ''} className="block">
|
||||
<div className="flex w-full items-center justify-between gap-1">
|
||||
<div className="flex items-center gap-2 truncate w-full">
|
||||
<span className="truncate">{link.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { useGenerateDatabaseMenu } from './DatabaseMenu.utils'
|
||||
import { DatabaseNavShortcuts } from '@/components/interfaces/DatabaseNavShortcuts'
|
||||
import { ProductMenu } from '@/components/ui/ProductMenu'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
|
||||
@@ -26,6 +27,7 @@ const DatabaseLayout = ({ children, title }: PropsWithChildren<DatabaseLayoutPro
|
||||
productMenu={<DatabaseProductMenu />}
|
||||
isBlocking={false}
|
||||
>
|
||||
<DatabaseNavShortcuts />
|
||||
{children}
|
||||
</ProjectLayout>
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query
|
||||
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
|
||||
const ExternalLinkIcon = <ArrowUpRight strokeWidth={1} className="h-4 w-4" />
|
||||
|
||||
@@ -42,24 +43,70 @@ export const useGenerateDatabaseMenu = (): ProductMenuGroup[] => {
|
||||
{
|
||||
title: 'Database Management',
|
||||
items: [
|
||||
{ name: 'Schema Visualizer', key: 'schemas', url: getDatabaseURL('schemas') },
|
||||
{ name: 'Tables', key: 'tables', url: getDatabaseURL('tables') },
|
||||
{ name: 'Functions', key: 'functions', url: getDatabaseURL('functions') },
|
||||
{ name: 'Triggers', key: 'triggers', url: getDatabaseURL('triggers/data') },
|
||||
{ name: 'Enumerated Types', key: 'types', url: getDatabaseURL('types') },
|
||||
{ name: 'Extensions', key: 'extensions', url: getDatabaseURL('extensions') },
|
||||
{ name: 'Indexes', key: 'indexes', url: getDatabaseURL('indexes') },
|
||||
{ name: 'Publications', key: 'publications', url: getDatabaseURL('publications') },
|
||||
{
|
||||
name: 'Schema Visualizer',
|
||||
key: 'schemas',
|
||||
url: getDatabaseURL('schemas'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER,
|
||||
},
|
||||
{
|
||||
name: 'Tables',
|
||||
key: 'tables',
|
||||
url: getDatabaseURL('tables'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_TABLES,
|
||||
},
|
||||
{
|
||||
name: 'Functions',
|
||||
key: 'functions',
|
||||
url: getDatabaseURL('functions'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS,
|
||||
},
|
||||
{
|
||||
name: 'Triggers',
|
||||
key: 'triggers',
|
||||
url: getDatabaseURL('triggers/data'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_TRIGGERS,
|
||||
},
|
||||
{
|
||||
name: 'Enumerated Types',
|
||||
key: 'types',
|
||||
url: getDatabaseURL('types'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_TYPES,
|
||||
},
|
||||
{
|
||||
name: 'Extensions',
|
||||
key: 'extensions',
|
||||
url: getDatabaseURL('extensions'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS,
|
||||
},
|
||||
{
|
||||
name: 'Indexes',
|
||||
key: 'indexes',
|
||||
url: getDatabaseURL('indexes'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_INDEXES,
|
||||
},
|
||||
{
|
||||
name: 'Publications',
|
||||
key: 'publications',
|
||||
url: getDatabaseURL('publications'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Configuration',
|
||||
items: [
|
||||
showRoles && { name: 'Roles', key: 'roles', url: getDatabaseURL('roles') },
|
||||
showRoles && {
|
||||
name: 'Roles',
|
||||
key: 'roles',
|
||||
url: getDatabaseURL('roles'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_ROLES,
|
||||
},
|
||||
columnLevelPrivileges && {
|
||||
name: 'Column Privileges',
|
||||
key: 'column-privileges',
|
||||
url: getDatabaseURL('column-privileges'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES,
|
||||
},
|
||||
{
|
||||
name: 'Policies',
|
||||
@@ -67,7 +114,12 @@ export const useGenerateDatabaseMenu = (): ProductMenuGroup[] => {
|
||||
url: `/project/${ref}/auth/policies`,
|
||||
rightIcon: ExternalLinkIcon,
|
||||
},
|
||||
{ name: 'Settings', key: 'settings', url: getDatabaseURL('settings') },
|
||||
{
|
||||
name: 'Settings',
|
||||
key: 'settings',
|
||||
url: getDatabaseURL('settings'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_SETTINGS,
|
||||
},
|
||||
].filter(Boolean) as ProductMenuGroupItem[],
|
||||
},
|
||||
{
|
||||
@@ -79,13 +131,20 @@ export const useGenerateDatabaseMenu = (): ProductMenuGroup[] => {
|
||||
key: 'replication',
|
||||
url: getDatabaseURL('replication'),
|
||||
label: enablePgReplicate ? 'New' : undefined,
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_REPLICATION,
|
||||
},
|
||||
IS_PLATFORM && {
|
||||
name: 'Backups',
|
||||
key: 'backups',
|
||||
url: pitrEnabled ? getDatabaseURL('backups/pitr') : getDatabaseURL('backups/scheduled'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_BACKUPS,
|
||||
},
|
||||
{
|
||||
name: 'Migrations',
|
||||
key: 'migrations',
|
||||
url: getDatabaseURL('migrations'),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS,
|
||||
},
|
||||
{ name: 'Migrations', key: 'migrations', url: getDatabaseURL('migrations') },
|
||||
showWrappers && {
|
||||
name: 'Wrappers',
|
||||
key: 'wrappers',
|
||||
|
||||
@@ -236,5 +236,3 @@ export function OrganizationSettingsLayout({ children }: PropsWithChildren) {
|
||||
</WithSidebar>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrganizationSettingsLayout
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import type { ShortcutId } from '@/state/shortcuts/registry'
|
||||
|
||||
export interface ProductMenuGroup {
|
||||
title?: string
|
||||
/** Set to "main" if page is on a '/' route */
|
||||
@@ -25,6 +27,7 @@ export interface ProductMenuGroupItem {
|
||||
childIcon?: ReactNode
|
||||
childItems?: ProductMenuGroupItem[]
|
||||
pages?: string[]
|
||||
shortcutId?: ShortcutId
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from 'next/link'
|
||||
import { Badge, Button, Menu } from 'ui'
|
||||
|
||||
import { ProductMenuGroupItem } from './ProductMenu.types'
|
||||
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
|
||||
|
||||
interface ProductMenuItemProps {
|
||||
item: ProductMenuGroupItem
|
||||
@@ -18,16 +19,26 @@ export const ProductMenuItem = ({
|
||||
hoverText = '',
|
||||
onClick,
|
||||
}: ProductMenuItemProps) => {
|
||||
const { name = '', url = '', icon, rightIcon, isExternal, label, disabled } = item
|
||||
const { name = '', url = '', icon, rightIcon, isExternal, label, disabled, shortcutId } = item
|
||||
|
||||
const labelNode = shortcutId ? (
|
||||
<ShortcutTooltip shortcutId={shortcutId} side="right" delayDuration={1000}>
|
||||
<span className="truncate min-w-0">{name}</span>
|
||||
</ShortcutTooltip>
|
||||
) : (
|
||||
<span className="truncate flex-1 min-w-0">{name}</span>
|
||||
)
|
||||
|
||||
const menuItem = (
|
||||
<Menu.Item icon={icon} active={isActive} onClick={onClick}>
|
||||
<div className="flex w-full items-center justify-between gap-1">
|
||||
<div
|
||||
className="flex items-center gap-1 min-w-0 flex-1"
|
||||
title={hoverText ? hoverText : typeof name === 'string' ? name : ''}
|
||||
title={
|
||||
shortcutId ? undefined : hoverText ? hoverText : typeof name === 'string' ? name : ''
|
||||
}
|
||||
>
|
||||
<span className="truncate flex-1 min-w-0">{name}</span>
|
||||
{labelNode}
|
||||
{label !== undefined && (
|
||||
<Badge
|
||||
className="shrink-0"
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
"mime-db": "^1.53.0",
|
||||
"monaco-editor": "0.52.2",
|
||||
"next": "catalog:",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"nuqs": "2.7.1",
|
||||
"openai": "^4.104.0",
|
||||
"openapi-fetch": "0.12.4",
|
||||
|
||||
@@ -13,8 +13,6 @@ import '@/styles/storage.css'
|
||||
import '@/styles/stripe.css'
|
||||
import '@/styles/ui.css'
|
||||
import 'ui-patterns/ShimmeringLoader/index.css'
|
||||
import 'ui/build/css/themes/dark.css'
|
||||
import 'ui/build/css/themes/light.css'
|
||||
|
||||
import { loader } from '@monaco-editor/react'
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
@@ -184,12 +182,7 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<MetaFaviconsPagesRouter applicationName="Supabase Studio" includeManifest />
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<RouteValidationWrapper>
|
||||
<ThemeProvider
|
||||
defaultTheme="system"
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<ThemeProvider>
|
||||
<DevToolbarProvider apiUrl={API_URL}>
|
||||
<AiAssistantStateContextProvider>
|
||||
<CommandProvider>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { GeneralSettings } from '@/components/interfaces/Organization/GeneralSettings/GeneralSettings'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { usePermissionsQuery } from '@/data/permissions/permissions-query'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
@@ -25,9 +25,9 @@ import {
|
||||
PrivateAppsProvider,
|
||||
usePrivateApps,
|
||||
} from '@/components/interfaces/Organization/PrivateApps/PrivateAppsContext'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
function PrivateAppsContent() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { SecuritySettings } from '@/components/interfaces/Organization/SecuritySettings'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { UnknownInterface } from '@/components/ui/UnknownInterface'
|
||||
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
} from 'ui-patterns/PageHeader'
|
||||
|
||||
import { SSOConfig } from '@/components/interfaces/Organization/SSO/SSOConfig'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { UnknownInterface } from '@/components/ui/UnknownInterface'
|
||||
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { PlatformWebhooksPage } from '@/components/interfaces/Platform/Webhooks'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
const OrgWebhookEndpointSettings: NextPageWithLayout = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PlatformWebhooksPage } from '@/components/interfaces/Platform/Webhooks'
|
||||
import DefaultLayout from '@/components/layouts/DefaultLayout'
|
||||
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
|
||||
import OrganizationLayout from '@/components/layouts/OrganizationLayout'
|
||||
import OrganizationSettingsLayout from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
const OrgWebhooksSettings: NextPageWithLayout = () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DATABASE_NAV_SHORTCUT_IDS, databaseNavRegistry } from './registry/database-nav'
|
||||
import { LIST_PAGE_SHORTCUT_IDS, listPageRegistry } from './registry/list-page'
|
||||
import {
|
||||
SCHEMA_VISUALIZER_SHORTCUT_IDS,
|
||||
@@ -64,6 +65,9 @@ export const SHORTCUT_IDS = {
|
||||
|
||||
// Shared list-page shortcuts (database/* listing pages, etc.)
|
||||
...LIST_PAGE_SHORTCUT_IDS,
|
||||
|
||||
// Database sub-page navigation chords
|
||||
...DATABASE_NAV_SHORTCUT_IDS,
|
||||
} as const
|
||||
|
||||
/**
|
||||
@@ -321,4 +325,7 @@ export const SHORTCUT_DEFINITIONS: Record<ShortcutId, ShortcutDefinition> = {
|
||||
|
||||
// Shared list-page shortcut registration
|
||||
...listPageRegistry,
|
||||
|
||||
// Database sub-page navigation chord registration
|
||||
...databaseNavRegistry,
|
||||
}
|
||||
|
||||
117
apps/studio/state/shortcuts/registry/database-nav.ts
Normal file
117
apps/studio/state/shortcuts/registry/database-nav.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { RegistryDefinations } from '../types'
|
||||
|
||||
/**
|
||||
* Contextual chords for jumping between Database sub-pages — `D + <letter>`.
|
||||
*
|
||||
* Active only while DatabaseLayout is mounted (i.e. the user is somewhere
|
||||
* under `/project/<ref>/database/*`). The chord intentionally lives on the
|
||||
* page rather than globally so the leading `D` doesn't burn a global key for
|
||||
* a destination most users only care about while already in the section.
|
||||
*
|
||||
*/
|
||||
export const DATABASE_NAV_SHORTCUT_IDS = {
|
||||
NAV_DATABASE_TABLES: 'nav.database-tables',
|
||||
NAV_DATABASE_FUNCTIONS: 'nav.database-functions',
|
||||
NAV_DATABASE_TRIGGERS: 'nav.database-triggers',
|
||||
NAV_DATABASE_INDEXES: 'nav.database-indexes',
|
||||
NAV_DATABASE_EXTENSIONS: 'nav.database-extensions',
|
||||
NAV_DATABASE_SCHEMA_VISUALIZER: 'nav.database-schema-visualizer',
|
||||
NAV_DATABASE_ROLES: 'nav.database-roles',
|
||||
NAV_DATABASE_BACKUPS: 'nav.database-backups',
|
||||
NAV_DATABASE_MIGRATIONS: 'nav.database-migrations',
|
||||
NAV_DATABASE_TYPES: 'nav.database-types',
|
||||
NAV_DATABASE_PUBLICATIONS: 'nav.database-publications',
|
||||
NAV_DATABASE_COLUMN_PRIVILEGES: 'nav.database-column-privileges',
|
||||
NAV_DATABASE_SETTINGS: 'nav.database-settings',
|
||||
NAV_DATABASE_REPLICATION: 'nav.database-replication',
|
||||
}
|
||||
|
||||
export type DatabaseNavShortcutId =
|
||||
(typeof DATABASE_NAV_SHORTCUT_IDS)[keyof typeof DATABASE_NAV_SHORTCUT_IDS]
|
||||
|
||||
export const databaseNavRegistry: RegistryDefinations<DatabaseNavShortcutId> = {
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TABLES]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TABLES,
|
||||
label: 'Go to Tables',
|
||||
sequence: ['D', 'T'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_FUNCTIONS,
|
||||
label: 'Go to Functions',
|
||||
sequence: ['D', 'F'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TRIGGERS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TRIGGERS,
|
||||
label: 'Go to Triggers',
|
||||
sequence: ['D', 'R'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_INDEXES]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_INDEXES,
|
||||
label: 'Go to Indexes',
|
||||
sequence: ['D', 'I'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_EXTENSIONS,
|
||||
label: 'Go to Extensions',
|
||||
sequence: ['D', 'X'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_SCHEMA_VISUALIZER,
|
||||
label: 'Go to Schema Visualizer',
|
||||
sequence: ['D', 'V'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_ROLES]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_ROLES,
|
||||
label: 'Go to Roles',
|
||||
sequence: ['D', 'O'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_BACKUPS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_BACKUPS,
|
||||
label: 'Go to Backups',
|
||||
sequence: ['D', 'B'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_MIGRATIONS,
|
||||
label: 'Go to Migrations',
|
||||
sequence: ['D', 'M'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TYPES]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_TYPES,
|
||||
label: 'Go to Enumerated Types',
|
||||
sequence: ['D', 'E'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_PUBLICATIONS,
|
||||
label: 'Go to Publications',
|
||||
sequence: ['D', 'U'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_COLUMN_PRIVILEGES,
|
||||
label: 'Go to Column Privileges',
|
||||
sequence: ['D', 'C'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_SETTINGS]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_SETTINGS,
|
||||
label: 'Go to Database Settings',
|
||||
sequence: ['D', ','],
|
||||
showInSettings: false,
|
||||
},
|
||||
[DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_REPLICATION]: {
|
||||
id: DATABASE_NAV_SHORTCUT_IDS.NAV_DATABASE_REPLICATION,
|
||||
label: 'Go to Replication',
|
||||
sequence: ['D', 'L'],
|
||||
showInSettings: false,
|
||||
},
|
||||
}
|
||||
@@ -1,30 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { AuthProvider } from 'common'
|
||||
import { AuthProvider, ThemeProvider } from 'common'
|
||||
import { Provider as JotaiProvider } from 'jotai'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { TooltipProvider } from 'ui'
|
||||
|
||||
import { FrameworkProvider } from '@/context/framework-context'
|
||||
import { MobileMenuProvider } from '@/context/mobile-menu-context'
|
||||
import { useRootQueryClient } from '@/lib/fetch/queryClient'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
const queryClient = useRootQueryClient()
|
||||
|
||||
return (
|
||||
<AuthProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<ThemeProvider>
|
||||
<MobileMenuProvider>
|
||||
<FrameworkProvider>
|
||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
||||
</FrameworkProvider>
|
||||
</MobileMenuProvider>
|
||||
</NextThemesProvider>
|
||||
</ThemeProvider>
|
||||
</JotaiProvider>
|
||||
</QueryClientProvider>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { html } from 'monaco-editor'
|
||||
import { Metadata } from 'next'
|
||||
|
||||
import { BaseInjector } from './../base-injector'
|
||||
import { ThemeProvider } from '@/app/Providers'
|
||||
import { Providers } from '@/app/Providers'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Password Based Auth Example',
|
||||
@@ -27,11 +28,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</head>
|
||||
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
|
||||
<BaseInjector />
|
||||
<ThemeProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<Providers>
|
||||
<div
|
||||
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
|
||||
style={{ minHeight: '100%' }}
|
||||
@@ -39,7 +36,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
|
||||
<div className="w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Metadata } from 'next'
|
||||
|
||||
import { BaseInjector } from './../base-injector'
|
||||
import { ThemeProvider } from '@/app/Providers'
|
||||
import { Providers } from '@/app/Providers'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Social Auth Example',
|
||||
@@ -27,11 +27,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</head>
|
||||
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
|
||||
<BaseInjector />
|
||||
<ThemeProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<Providers>
|
||||
<div
|
||||
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
|
||||
style={{ minHeight: '100%' }}
|
||||
@@ -39,7 +35,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
|
||||
<div className="w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FeatureFlagProvider, TelemetryTagManager } from 'common'
|
||||
import { genFaviconData } from 'common/MetaFavicons/app-router'
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
import { ThemeProvider } from './Providers'
|
||||
import { Providers } from './Providers'
|
||||
import { Toaster } from './toaster'
|
||||
import { API_URL } from '@/lib/constants'
|
||||
|
||||
@@ -47,14 +47,10 @@ export default async function Layout({ children }: RootLayoutProps) {
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<TelemetryTagManager />
|
||||
<FeatureFlagProvider API_URL={API_URL}>
|
||||
<ThemeProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
>
|
||||
<Providers>
|
||||
{children}
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</Providers>
|
||||
</FeatureFlagProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"monaco-editor": "^0.55.1",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"openai": "^5.9.0",
|
||||
"openapi-fetch": "0.12.4",
|
||||
"radix-ui": "catalog:",
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
title: 'Realtime or ETL? How to choose the right tool'
|
||||
description: 'Both Supabase Realtime and Supabase ETL read changes from your Postgres database using logical replication. But they solve very different problems. Here is how to pick the right one.'
|
||||
author: eduardo_gurgel, riccardo_busetti
|
||||
date: '2026-05-05'
|
||||
categories:
|
||||
- developers
|
||||
tags:
|
||||
- realtime
|
||||
- postgres
|
||||
- etl
|
||||
- database
|
||||
imgSocial: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=announcement&layout=horizontal©=Realtime+or+ETL%0A%5BHow+to+choose+the+right+tool%5D&icon=icon-realtime.svg'
|
||||
imgThumb: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=ruler&layout=icon-only©=Realtime+or+ETL%0A%5BHow+to+choose+the+right+tool%5D&icon=icon-realtime.svg'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
Both [Supabase Realtime](https://supabase.com/docs/guides/realtime) and [Supabase ETL](https://supabase.com/features/supabase-etl) read changes from your Postgres database. They both use [logical replication](https://supabase.com/docs/guides/database/replication) under the hood. They even look similar when you squint. But they solve very different problems, and choosing the wrong one will frustrate you.
|
||||
|
||||
This post explains what each product does, how they differ, and when you should pick one over the other.
|
||||
|
||||
## Two tools, two jobs
|
||||
|
||||
Here is the simplest way to think about it:
|
||||
|
||||
- **Realtime** sends database changes to your users' browsers and apps, right now, as they happen. It is built for live experiences.
|
||||
- **ETL** sends database changes in near real-time to analytical destinations like BigQuery and Analytics Buckets. It is built for reliable data movement.
|
||||
|
||||
Realtime answers the question: "How do I show my users what just happened?"
|
||||
|
||||
ETL answers the question: "How do I get my production data into my analytics warehouse?"
|
||||
|
||||
If you mix these up, you will run into problems. We see it happen regularly, and the rest of this post will help you avoid that.
|
||||
|
||||
## What Realtime does
|
||||
|
||||
[Supabase Realtime](https://supabase.com/docs/guides/realtime) is three features in one product:
|
||||
|
||||
1. [**Broadcast.**](https://supabase.com/docs/guides/realtime/broadcast) Send messages between connected clients in real time. No database required. Think cursor positions, typing indicators, or game state.
|
||||
2. [**Presence.**](https://supabase.com/docs/guides/realtime/presence) Track who is online and what they are doing. Also no database required. Think "3 users are editing this document" or "Jane is typing..."
|
||||
3. [**Postgres Changes.**](https://supabase.com/docs/guides/realtime/postgres-changes) Listen to INSERT, UPDATE, and DELETE events on your database tables and deliver them to subscribed clients over WebSocket.
|
||||
|
||||
Two of these three features, Broadcast and Presence, can work without any database interaction. Client-to-client Broadcast sends messages purely over WebSocket with nothing stored. However, [Broadcast from Database](https://supabase.com/blog/realtime-broadcast-from-database) lets you trigger broadcasts from database changes using triggers, giving you control over which events reach which channels. This matters because Realtime is not just a database change listener. It is a real-time communication layer for your application.
|
||||
|
||||
### How Postgres Changes works
|
||||
|
||||
When a client subscribes to a table, Realtime uses a PostgreSQL replication slot to read changes from the Write-Ahead Log (WAL). For each change, it checks [Row Level Security (RLS)](https://supabase.com/docs/guides/database/postgres/row-level-security) policies against every subscribed user. If a user is authorized to see the change, Realtime sends it over their WebSocket connection.
|
||||
|
||||
This is designed for live UI updates. A user inserts a message into a chat table. Other users see it appear instantly. A row updates in a dashboard table. The chart refreshes automatically.
|
||||
|
||||
### What Realtime does not guarantee
|
||||
|
||||
Realtime's Postgres Changes feature does not guarantee delivery. If a client disconnects for 30 seconds and reconnects, the changes that happened during those 30 seconds are gone. Realtime does not queue them and does not track how far each client has read.
|
||||
|
||||
[Broadcast Replay](https://supabase.com/blog/realtime-broadcast-replay) offers limited catch-up for Broadcast from Database messages: clients can request up to 25 messages from the last 3 days using a `since` timestamp. But this only works on private channels, only for database-sourced broadcasts, and is currently in public alpha. It is not a general-purpose replay mechanism for all Realtime events.
|
||||
|
||||
Postgres Changes uses temporary replication slots. When no clients are subscribed, it stops replicating data entirely. When clients subscribe again, a new slot is created.
|
||||
|
||||
The Realtime team built it this way on purpose. Guaranteed delivery requires persistent state tracking, message queuing, and acknowledgment protocols. Those things add latency and complexity that would make Realtime worse at its actual job: delivering live updates as fast as possible.
|
||||
|
||||
If you need every change to arrive at its destination, no matter what, Realtime is not the right tool.
|
||||
|
||||
## What ETL does
|
||||
|
||||
Supabase ETL is a change-data-capture (CDC) pipeline. It reads every INSERT, UPDATE, DELETE, and TRUNCATE from your Postgres tables and writes them to a destination. Right now, the supported destinations are [Analytics Buckets](https://supabase.com/docs/guides/storage/analytics/introduction) (built on Apache Iceberg) and BigQuery.
|
||||
|
||||
ETL replicates your data 1-to-1 in near real-time. If your destination disconnects or has problems, ETL does not skip over data.
|
||||
|
||||
### How ETL works
|
||||
|
||||
When you create an ETL pipeline, it connects to your database through a permanent replication slot. First, it performs a full copy of your existing data. Then it switches to streaming mode and captures every change as it happens, with latency measured in milliseconds to seconds (based on configuration parameters, data size, and destination type).
|
||||
|
||||
It's important to note that Supabase ETL doesn't respect Row-Level Security. Supabase ETL reads every piece of data. If you need to filter data, you should use publication filters.
|
||||
|
||||
Changes are batched and written to your destination. If the pipeline crashes, it restarts from the last acknowledged position. No data changes are lost. Note that schema changes (adding or removing columns) do not propagate automatically and require manual handling.
|
||||
|
||||
### What ETL guarantees
|
||||
|
||||
ETL provides at-least-once delivery. Every change that happens in your database will reach the destination at least once. In rare cases (like a crash during a long-running transaction), a change might be delivered more than once. Exactly-once processing is handled by the destination. Some destinations like BigQuery deduplicate automatically, while others may not.
|
||||
|
||||
ETL uses permanent replication slots. This means Postgres holds onto WAL data until ETL confirms it has been processed. If you stop the pipeline for maintenance and restart it later, it picks up exactly where it left off. Be aware that while the pipeline is paused, Postgres continues to retain WAL data. Extended pauses can lead to significant disk growth, and depending on your Postgres configuration, the pipeline may fail if the WAL retention limit is exceeded.
|
||||
|
||||
This is the opposite of Realtime's approach. ETL trades speed for reliability. It may not deliver changes to your warehouse in the same millisecond they happen, but it will deliver every single one.
|
||||
|
||||
## The key differences
|
||||
|
||||
### Delivery guarantees
|
||||
|
||||
| | **Realtime** | **ETL** |
|
||||
| ----------------------- | ------------ | --------------------- |
|
||||
| Guarantee | Best effort | At-least-once |
|
||||
| Missed changes | Lost forever | Replayed on reconnect |
|
||||
| Replication slot | Temporary | Permanent |
|
||||
| Resume after disconnect | No | Yes |
|
||||
|
||||
This is the most important difference. If you need every change to arrive, use ETL. If you need changes to arrive fast and can tolerate occasional gaps, use Realtime.
|
||||
|
||||
### Where data goes
|
||||
|
||||
Realtime sends data to client applications over WebSocket connections. Your users' browsers and mobile apps are the destination.
|
||||
|
||||
ETL sends data to analytical systems. BigQuery, Analytics Buckets, and eventually other data warehouses are the destination.
|
||||
|
||||
These are fundamentally different targets with fundamentally different needs. Client apps need low latency. Analytical systems need completeness.
|
||||
|
||||
### Database dependency
|
||||
|
||||
Realtime's Broadcast and Presence features can work without touching the database. You can build an entire collaborative experience (cursors, presence indicators, ephemeral messaging) without writing a single database query. However, Postgres Changes and [Broadcast from Database](https://supabase.com/blog/realtime-broadcast-from-database) both require database interaction.
|
||||
|
||||
ETL is entirely database-driven. Every byte of data it moves comes from your Postgres tables.
|
||||
|
||||
### Scale characteristics
|
||||
|
||||
Realtime's Broadcast and Presence features are built for high throughput and low latency. They do not run per-subscriber database queries and scale well across many concurrent connections.
|
||||
|
||||
Postgres Changes works differently. It processes changes sequentially to maintain ordering. For each change, it runs an RLS authorization check against every subscribed client. With 100 subscribers watching a table, one INSERT generates 100 authorization queries. This is a deliberate design choice that prioritizes correctness and low latency for typical workloads over raw throughput.
|
||||
|
||||
ETL processes changes in configurable batches with tunable parallelism. It does not need to authorize individual users because it is moving data to a system, not to end users.
|
||||
|
||||
## When to use Realtime
|
||||
|
||||
Use Realtime when you need to push live updates to your users:
|
||||
|
||||
- **Chat applications.** Messages appear instantly for all participants.
|
||||
- **Collaborative editing.** See other users' cursors and changes in real time.
|
||||
- **Live dashboards.** Charts and metrics update without page refresh.
|
||||
- **Notifications.** Alert users when something relevant happens.
|
||||
- **Multiplayer features.** Synchronize game state or shared experiences.
|
||||
- **Presence tracking.** Show who is online, who is typing, who is viewing a document.
|
||||
|
||||
The common thread: a human is watching and needs to see changes as they happen.
|
||||
|
||||
## When to use ETL
|
||||
|
||||
Use ETL when you need reliable data movement to analytical systems:
|
||||
|
||||
- **Analytics and reporting.** Move production data to BigQuery or Iceberg for querying without impacting your production database.
|
||||
- **Audit trails.** Analytics Buckets stores an append-only changelog of every INSERT, UPDATE, and DELETE. Nothing is lost.
|
||||
- **Data warehousing.** Replicate your operational data to a columnar format optimized for analytical queries.
|
||||
- **Compliance.** Maintain a complete, verifiable history of all data changes.
|
||||
- **ML pipelines.** Feed fresh data to training or feature stores without querying production.
|
||||
- **Workload isolation.** Run heavy analytical queries against your warehouse instead of your production database.
|
||||
|
||||
The common thread: a system needs a complete, reliable copy of your data.
|
||||
|
||||
## The mistake we see most often
|
||||
|
||||
Some developers discover Realtime's Postgres Changes feature and think: "I can use this to replicate data from one system to another." They write 20 lines of code with supabase-js, subscribe to table changes, and pipe them into another system.
|
||||
|
||||
It works great in development. It even works fine in production for a while. Then a WebSocket connection drops for a few seconds and data goes missing. Or the subscribing process restarts and misses a batch of changes. Or load spikes and the sequential RLS authorization checks cannot keep up.
|
||||
|
||||
The problem is not that Realtime is broken. The problem is that Realtime was not designed for this job.
|
||||
|
||||
If you are piping database changes into another system and you need every change to arrive, use ETL. That is exactly what it was built for.
|
||||
|
||||
## Can I use both?
|
||||
|
||||
Yes. In fact, many applications should.
|
||||
|
||||
Consider an e-commerce platform. You might use Realtime to push order status updates to customers in real time ("Your order has shipped!"). At the same time, you use ETL to replicate all order data to BigQuery for daily sales reports and trend analysis.
|
||||
|
||||
Same database. Same tables. Different tools for different jobs.
|
||||
|
||||
Realtime handles the live experience. ETL handles the analytical pipeline. Each does what it was designed to do.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| | **Realtime** | **ETL** |
|
||||
| ------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Purpose | Live updates to client apps | Reliable data movement to analytics |
|
||||
| Delivery | Best effort | At-least-once |
|
||||
| Destinations | Browsers, mobile apps (WebSocket) | BigQuery, Analytics Buckets |
|
||||
| Replication slot | Temporary | Permanent |
|
||||
| Resume on reconnect | No | Yes |
|
||||
| Database required | Only for Postgres Changes and Broadcast from Database | Yes, always |
|
||||
| Processing | Sequential per-change with per-subscriber authorization | Batched with configurable parallelism |
|
||||
| Latency | Typically under 100ms | Seconds (batched) |
|
||||
| Best for | Human users watching live data | Systems consuming complete data |
|
||||
| Built with | Elixir (Phoenix) | Rust |
|
||||
| Open source | [github.com/supabase/realtime](https://github.com/supabase/realtime) | [github.com/supabase/etl](https://github.com/supabase/etl) |
|
||||
|
||||
## Getting started
|
||||
|
||||
Supabase Realtime is available on all Supabase projects. Check out the [Realtime documentation](https://supabase.com/docs/guides/realtime) to get started.
|
||||
|
||||
Supabase ETL is currently in private alpha. You can request access through the Supabase Dashboard or contact your account manager. Read the [ETL blog post](https://supabase.com/blog/introducing-supabase-etl) for more details on how it works.
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
getAbsoluteBlogSocialImage,
|
||||
toAbsoluteBlogImageUrl,
|
||||
} from '@/lib/blog-images'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { SITE_ORIGIN } from '@/lib/constants'
|
||||
import { blogPostingSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
import { blogPostingSchema, breadcrumbListSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
import { getAllPostSlugs, getPostdata, getSortedPosts } from '@/lib/posts'
|
||||
import type { Blog, BlogData, PostReturnType } from '@/types/post'
|
||||
|
||||
@@ -171,6 +172,10 @@ export default async function BlogPostPage({ params }: { params: Promise<Params>
|
||||
datePublished: frontmatter.date,
|
||||
authors: blogAuthors.length > 0 ? blogAuthors : [{ name: 'Supabase' }],
|
||||
})
|
||||
const breadcrumbJsonLd = breadcrumbListSchema([
|
||||
...breadcrumbs.blogIndex,
|
||||
{ name: frontmatter.title, url: `https://supabase.com/blog/${slug}` },
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -178,6 +183,10 @@ export default async function BlogPostPage({ params }: { params: Promise<Params>
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: serializeJsonLd(blogJsonLd) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: serializeJsonLd(breadcrumbJsonLd) }}
|
||||
/>
|
||||
<BlogPostClient {...props} />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { Metadata } from 'next'
|
||||
import type PostTypes from 'types/post'
|
||||
|
||||
import BlogClient from './BlogClient'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
import { getSortedPosts } from '@/lib/posts'
|
||||
|
||||
export const revalidate = 30
|
||||
@@ -39,17 +41,27 @@ export default async function BlogPage() {
|
||||
const totalListPosts = Math.max(0, allPosts.length - 1)
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<h1 className="sr-only">Supabase blog</h1>
|
||||
<div className="container relative mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
||||
{featuredPost && <FeaturedThumb key={featuredPost.slug} {...(featuredPost as PostTypes)} />}
|
||||
</div>
|
||||
|
||||
<div className="border-default border-t">
|
||||
<div className="container mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
||||
<BlogClient initialBlogs={listPosts} totalPosts={totalListPosts} />
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.blogIndex)),
|
||||
}}
|
||||
/>
|
||||
<DefaultLayout>
|
||||
<h1 className="sr-only">Supabase blog</h1>
|
||||
<div className="container relative mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
||||
{featuredPost && (
|
||||
<FeaturedThumb key={featuredPost.slug} {...(featuredPost as PostTypes)} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
|
||||
<div className="border-default border-t">
|
||||
<div className="container mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
||||
<BlogClient initialBlogs={listPosts} totalPosts={totalListPosts} />
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { getNotionEvents } from '~/lib/events'
|
||||
import { EventClientRenderer } from '~/components/Events/new/EventClientRenderer'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { getNotionEvents } from '~/lib/events'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'View all Supabase events and meetups.',
|
||||
@@ -11,5 +13,15 @@ export const metadata: Metadata = {
|
||||
export default async function EventsPage() {
|
||||
const notionEvents = await getNotionEvents()
|
||||
|
||||
return <EventClientRenderer notionEvents={notionEvents} />
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.eventsIndex)),
|
||||
}}
|
||||
/>
|
||||
<EventClientRenderer notionEvents={notionEvents} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,13 @@ import { plans } from 'shared-data/plans'
|
||||
|
||||
import PricingContent from './PricingContent'
|
||||
import pricingFaq from '@/data/PricingFAQ.json'
|
||||
import { faqPageSchema, pricingProductSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import {
|
||||
breadcrumbListSchema,
|
||||
faqPageSchema,
|
||||
pricingProductSchema,
|
||||
serializeJsonLd,
|
||||
} from '@/lib/json-ld'
|
||||
|
||||
const PRICING_DESCRIPTION =
|
||||
'Explore Supabase fees and pricing information. Find our competitive pricing Plans, with no hidden pricing. We have a generous Free Plan for those getting started, and Pay As You Go for those scaling up.'
|
||||
@@ -41,6 +47,12 @@ const PRODUCT_JSON_LD = serializeJsonLd(
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.pricing)),
|
||||
}}
|
||||
/>
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: FAQ_JSON_LD }} />
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: PRODUCT_JSON_LD }} />
|
||||
<PricingContent />
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { WwwCommandMenu } from 'components/CommandMenu'
|
||||
import { DevToolbar, DevToolbarProvider } from 'dev-tools'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { themes, TooltipProvider } from 'ui'
|
||||
import { TooltipProvider } from 'ui'
|
||||
import { CommandProvider } from 'ui-patterns/CommandMenu'
|
||||
import { useConsentToast } from 'ui-patterns/consent'
|
||||
|
||||
@@ -26,7 +26,7 @@ function Providers({ children }: { children: React.ReactNode }) {
|
||||
<AuthProvider>
|
||||
<FeatureFlagProvider API_URL={API_URL} enabled={IS_PLATFORM}>
|
||||
<DevToolbarProvider apiUrl={API_URL}>
|
||||
<ThemeProvider themes={themes.map((t) => t.value)} enableSystem disableTransitionOnChange>
|
||||
<ThemeProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<CommandProvider>
|
||||
<TelemetryTagManager />
|
||||
|
||||
23
apps/www/lib/breadcrumbs.ts
Normal file
23
apps/www/lib/breadcrumbs.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { BreadcrumbItem } from './json-ld'
|
||||
|
||||
const SITE = 'https://supabase.com'
|
||||
|
||||
const home: BreadcrumbItem = { name: 'Home', url: SITE }
|
||||
|
||||
export const breadcrumbs = {
|
||||
blogIndex: [home, { name: 'Blog', url: `${SITE}/blog` }],
|
||||
customersIndex: [home, { name: 'Customer Stories', url: `${SITE}/customers` }],
|
||||
eventsIndex: [home, { name: 'Events', url: `${SITE}/events` }],
|
||||
database: [home, { name: 'Database', url: `${SITE}/database` }],
|
||||
auth: [home, { name: 'Auth', url: `${SITE}/auth` }],
|
||||
storage: [home, { name: 'Storage', url: `${SITE}/storage` }],
|
||||
edgeFunctions: [home, { name: 'Edge Functions', url: `${SITE}/edge-functions` }],
|
||||
realtime: [home, { name: 'Realtime', url: `${SITE}/realtime` }],
|
||||
vector: [home, { name: 'Vector', url: `${SITE}/modules/vector` }],
|
||||
cron: [home, { name: 'Cron', url: `${SITE}/modules/cron` }],
|
||||
queues: [home, { name: 'Queues', url: `${SITE}/modules/queues` }],
|
||||
pricing: [home, { name: 'Pricing', url: `${SITE}/pricing` }],
|
||||
careers: [home, { name: 'Careers', url: `${SITE}/careers` }],
|
||||
company: [home, { name: 'Company', url: `${SITE}/company` }],
|
||||
features: [home, { name: 'Features', url: `${SITE}/features` }],
|
||||
} satisfies Record<string, BreadcrumbItem[]>
|
||||
@@ -85,6 +85,24 @@ export function softwareApplicationSchema(input: SoftwareApplicationSchemaInput)
|
||||
}
|
||||
}
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export function breadcrumbListSchema(items: BreadcrumbItem[]) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: items.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: item.url,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
interface BlogPostingSchemaInput {
|
||||
url: string
|
||||
headline: string
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"next": "^15.5.15",
|
||||
"next-mdx-remote": "^6.0.0",
|
||||
"next-seo": "^6.5.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"nuqs": "^2.8.1",
|
||||
"openai": "^4.75.1",
|
||||
"parse-numeric-range": "^1.3.0",
|
||||
|
||||
@@ -21,7 +21,7 @@ import { DefaultSeo } from 'next-seo'
|
||||
import type { AppProps } from 'next/app'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { themes, TooltipProvider } from 'ui'
|
||||
import { TooltipProvider } from 'ui'
|
||||
import { CommandProvider } from 'ui-patterns/CommandMenu'
|
||||
import { useConsentToast } from 'ui-patterns/consent'
|
||||
|
||||
@@ -105,12 +105,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
{/* [TODO] I think we need to deconflict with the providers in layout.tsx? */}
|
||||
<FeatureFlagProvider API_URL={API_URL} enabled={{ cc: true, ph: false }}>
|
||||
<DevToolbarProvider apiUrl={API_URL}>
|
||||
<ThemeProvider
|
||||
themes={themes.map((theme) => theme.value)}
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
forcedTheme={forceDarkMode ? 'dark' : undefined}
|
||||
>
|
||||
<ThemeProvider forcedTheme={forceDarkMode ? 'dark' : undefined}>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<CommandProvider app="www" onTelemetry={onTelemetry}>
|
||||
<Toaster />
|
||||
|
||||
@@ -19,7 +19,8 @@ import AuthProviders from '@/data/auth.json'
|
||||
import MainProducts from '@/data/MainProducts'
|
||||
import ApiExamples from '@/data/products/auth/auth-api-examples'
|
||||
import AuthSqlRulesExamples from '@/data/products/auth/auth-sql-rules-examples'
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const SplitCodeBlockCarousel = dynamic(
|
||||
() => import('~/components/Carousels/SplitCodeBlockCarousel')
|
||||
@@ -70,6 +71,12 @@ function AuthPage() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.auth)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<ProductsNav activePage={PRODUCT_NAMES.AUTHENTICATION} />
|
||||
|
||||
@@ -3,10 +3,13 @@ import Globe from '~/components/Globe'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import career from '~/data/career.json'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { filterGenericJob, groupJobsByTeam, JobItemProps, PLACEHOLDER_JOB_ID } from '~/lib/careers'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import Styles from '~/styles/career.module.css'
|
||||
import { GetServerSideProps } from 'next'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
@@ -109,6 +112,14 @@ const CareerPage = ({ jobs, placeholderJob, contributors }: CareersPageProps) =>
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.careers)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<header className="container relative mx-auto px-6 pt-12 pb-8 lg:pt-24 lg:px-16 xl:px-20 text-center space-y-4">
|
||||
<h1 className="text-sm text-brand md:text-base">
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import Layout from '~/components/Layouts/Default'
|
||||
|
||||
import SectionHeader from 'components/UI/SectionHeader'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import CTABanner from 'components/CTABanner/index'
|
||||
import ImageGrid from 'components/ImageGrid'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
|
||||
import PressData from 'data/Press'
|
||||
import SectionHeader from 'components/UI/SectionHeader'
|
||||
import CommunityData from 'data/Community'
|
||||
import CompaniesData from 'data/Companies'
|
||||
import InvestorData from 'data/Investors'
|
||||
|
||||
import PressData from 'data/Press'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, Card_legacy_, Space } from 'ui'
|
||||
import { NextSeo } from 'next-seo'
|
||||
|
||||
type Props = {}
|
||||
|
||||
@@ -43,6 +41,14 @@ const Index = ({}: Props) => {
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.company)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<Layout>
|
||||
<Header />
|
||||
<Community />
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import fs from 'fs'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import Head from 'next/head'
|
||||
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { generateRss } from '~/lib/rss'
|
||||
import { getSortedPosts } from '~/lib/posts'
|
||||
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import { getSortedPosts } from '~/lib/posts'
|
||||
import { generateRss } from '~/lib/rss'
|
||||
import styles from '~/styles/customers.module.css'
|
||||
import type PostTypes from '~/types/post'
|
||||
import { motion } from 'framer-motion'
|
||||
import styles from '~/styles/customers.module.css'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { GlassPanel } from 'ui-patterns/GlassPanel'
|
||||
import CustomersFilters from '../components/CustomerStories/CustomersFilters'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Button, cn } from 'ui'
|
||||
import { GlassPanel } from 'ui-patterns/GlassPanel'
|
||||
|
||||
import CustomersFilters from '../components/CustomerStories/CustomersFilters'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
|
||||
export async function getStaticProps() {
|
||||
const allPostsData: any[] = getSortedPosts({ directory: '_customers' })
|
||||
@@ -99,6 +99,12 @@ function CustomerStoriesPage(props: any) {
|
||||
title="RSS feed for customer stories"
|
||||
href={`${basePath}/customers-rss.xml`}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.customersIndex)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<NextSeo
|
||||
title={meta.title}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import matter from 'gray-matter'
|
||||
import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react'
|
||||
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { SITE_ORIGIN } from '~/lib/constants'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import mdxComponents from '~/lib/mdx/mdxComponents'
|
||||
import { mdxSerialize } from '~/lib/mdx/mdxSerialize'
|
||||
import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts'
|
||||
import matter from 'gray-matter'
|
||||
import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
|
||||
// table of contents extractor
|
||||
const toc = require('markdown-toc')
|
||||
@@ -89,6 +91,11 @@ function CaseStudyPage(props: any) {
|
||||
url: `${SITE_ORIGIN}/customers/${slug}`,
|
||||
}
|
||||
|
||||
const breadcrumbItems = [
|
||||
...breadcrumbs.customersIndex,
|
||||
{ name: meta_title ?? title, url: `https://supabase.com/customers/${slug}` },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
@@ -112,6 +119,14 @@ function CaseStudyPage(props: any) {
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbItems)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<div
|
||||
className="
|
||||
|
||||
@@ -24,7 +24,8 @@ import ExtensionsExamplesData from '@/data/products/database/extensions-examples
|
||||
import HighlightsCards from '@/data/products/database/highlight-cards'
|
||||
import SqlViewCarouselData from '@/data/products/database/sql-view-carousel.json'
|
||||
import TableViewCarouselData from '@/data/products/database/table-view-carousel.json'
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const NewFeatureCard = dynamic(() => import('~/components/NewFeatureCard'))
|
||||
const ImageCarousel = dynamic(() => import('~/components/Carousels/ImageCarousel'))
|
||||
@@ -88,6 +89,12 @@ function Database() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.database)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<ProductsNav activePage={PRODUCT_NAMES.DATABASE} />
|
||||
|
||||
@@ -13,7 +13,8 @@ import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { PRODUCT_NAMES, PRODUCT_SHORTNAMES } from 'shared-data/products'
|
||||
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const ExamplesCarousel = dynamic(() => import('~/components/Examples/ExamplesCarousel'))
|
||||
const GlobalPresenceSection = dynamic(
|
||||
@@ -62,6 +63,12 @@ function EdgeFunctions() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.edgeFunctions)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<ProductsNav activePage={PRODUCT_NAMES.FUNCTIONS} />
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ChevronLeft, X as XIcon } from 'lucide-react'
|
||||
import type { GetStaticProps, InferGetStaticPropsType } from 'next'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import NextImage from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
@@ -24,7 +25,9 @@ import ShareArticleActions from '@/components/Blog/ShareArticleActions'
|
||||
import DefaultLayout from '@/components/Layouts/Default'
|
||||
import SectionContainer from '@/components/Layouts/SectionContainer'
|
||||
import authors from '@/lib/authors.json'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { capitalize, isNotNullOrUndefined } from '@/lib/helpers'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '@/lib/json-ld'
|
||||
import mdxComponents from '@/lib/mdx/mdxComponents'
|
||||
import { mdxSerialize } from '@/lib/mdx/mdxSerialize'
|
||||
import { getAllPostSlugs, getPostdata } from '@/lib/posts'
|
||||
@@ -221,6 +224,22 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(
|
||||
breadcrumbListSchema([
|
||||
...breadcrumbs.eventsIndex,
|
||||
{
|
||||
name: event.meta_title ?? event.title,
|
||||
url: `https://supabase.com/events/${event.slug}`,
|
||||
},
|
||||
])
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<div className="flex flex-col w-full bg-alternative border-b border-muted">
|
||||
<SectionContainer className="py-2! flex items-start">
|
||||
|
||||
@@ -2,11 +2,14 @@ import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import Panel from '~/components/Panel'
|
||||
import { features } from '~/data/features'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import { motion } from 'framer-motion'
|
||||
import { debounce } from 'lib/helpers'
|
||||
import { Search } from 'lucide-react'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { useRouter } from 'next/compat/router'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Button, Checkbox, cn, InputGroup, InputGroupAddon, InputGroupInput } from 'ui'
|
||||
@@ -103,6 +106,14 @@ function FeaturesPage() {
|
||||
url: '/customers',
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.features)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<SectionContainer className="py-0! sm:px-0!">
|
||||
<div className="border border-muted rounded-xl bg-alternative my-4 px-6 py-8 md:py-10 lg:px-16 lg:py-20 xl:px-20 bg-center bg-cover bg-[url('/images/features/features-cover-light.svg')] dark:bg-[url('/images/features/features-cover-dark.svg')]">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import ModulesNav from '~/components/Modules/ModulesNav'
|
||||
import ProductModulesHeader from '~/components/Sections/ProductModulesHeader'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
|
||||
import { PRODUCT_MODULES_NAMES } from 'shared-data/products'
|
||||
import CronPageData from '~/data/products/modules/cron'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import dynamic from 'next/dynamic'
|
||||
import Head from 'next/head'
|
||||
import { PRODUCT_MODULES_NAMES } from 'shared-data/products'
|
||||
|
||||
const HighlightCards = dynamic(() => import('~/components/Sections/HighlightCards'))
|
||||
const CronSQLSection = dynamic(() => import('~/components/Modules/Cron/CronSQLSection'))
|
||||
@@ -33,6 +34,14 @@ function CronPage() {
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.cron)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout className="bg-alternative!" stickyNavbar={false}>
|
||||
<ModulesNav activePage={PRODUCT_MODULES_NAMES.CRON} docsUrl={pageData.docsUrl} />
|
||||
<ProductModulesHeader {...pageData.heroSection} />
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import ModulesNav from '~/components/Modules/ModulesNav'
|
||||
import ProductModulesHeader from '~/components/Sections/ProductModulesHeader'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
|
||||
import { PRODUCT_MODULES_NAMES } from 'shared-data/products'
|
||||
import QueuesPageData from '~/data/products/modules/queues'
|
||||
import { breadcrumbs } from '~/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd } from '~/lib/json-ld'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import dynamic from 'next/dynamic'
|
||||
import Head from 'next/head'
|
||||
import { PRODUCT_MODULES_NAMES } from 'shared-data/products'
|
||||
|
||||
const HighlightCards = dynamic(() => import('~/components/Sections/HighlightCards'))
|
||||
const QueuesSQLSection = dynamic(() => import('~/components/Modules/Queues/QueuesSQLSection'))
|
||||
@@ -34,6 +35,14 @@ function CronPage() {
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<Head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.queues)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout className="bg-alternative!" stickyNavbar={false}>
|
||||
<ModulesNav activePage={PRODUCT_MODULES_NAMES.QUEUES} docsUrl={pageData.docsUrl} />
|
||||
<ProductModulesHeader {...pageData.heroSection} />
|
||||
|
||||
@@ -9,7 +9,8 @@ import dynamic from 'next/dynamic'
|
||||
import Head from 'next/head'
|
||||
import { PRODUCT_MODULES_NAMES, PRODUCT_MODULES_SHORTNAMES } from 'shared-data/products'
|
||||
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const ProductModulesHeader = dynamic(() => import('~/components/Sections/ProductModulesHeader'))
|
||||
const HighlightCards = dynamic(() => import('~/components/Sections/HighlightCards'))
|
||||
@@ -61,6 +62,12 @@ function VectorPage() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.vector)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout className="bg-alternative!" stickyNavbar={false}>
|
||||
<ModulesNav activePage={PRODUCT_MODULES_NAMES.VECTOR} docsUrl={pageData.docsUrl} />
|
||||
|
||||
@@ -20,7 +20,8 @@ import { useRouter } from 'next/router'
|
||||
import { PRODUCT_NAMES } from 'shared-data/products'
|
||||
import { Button } from 'ui'
|
||||
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const SingleQuote = dynamic(() => import('~/components/Sections/SingleQuote'))
|
||||
|
||||
@@ -82,6 +83,12 @@ function RealtimePage() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.realtime)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<ProductsNav activePage={PRODUCT_NAMES.REALTIME} />
|
||||
|
||||
@@ -19,7 +19,8 @@ import Solutions from '@/data/MainProducts'
|
||||
import ApiExamples from '@/data/products/storage/api-examples'
|
||||
import DashboardViewData from '@/data/products/storage/dashboard-carousel.json'
|
||||
import StoragePermissionsData from '@/data/products/storage/permissions-examples'
|
||||
import { serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
import { breadcrumbs } from '@/lib/breadcrumbs'
|
||||
import { breadcrumbListSchema, serializeJsonLd, softwareApplicationSchema } from '@/lib/json-ld'
|
||||
|
||||
const APISection = dynamic(() => import('~/components/Sections/APISection'))
|
||||
const SingleQuote = dynamic(() => import('~/components/Sections/SingleQuote'))
|
||||
@@ -69,6 +70,12 @@ function StoragePage() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.storage)),
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<DefaultLayout>
|
||||
<ProductsNav activePage={PRODUCT_NAMES.STORAGE} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@import './../../../packages/ui/build/css/source/global.css';
|
||||
@import './../../../packages/ui/build/css/themes/dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/faux-classic-dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/light.css';
|
||||
|
||||
@config '../tailwind.config.js';
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import type { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from 'next-themes'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
// @ts-ignore next-themes is old :/
|
||||
|
||||
return (
|
||||
<NextThemesProvider themes={['dark', 'light']} defaultTheme="dark" {...props}>
|
||||
<NextThemesProvider
|
||||
themes={['dark', 'light', 'classic-dark']}
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"dat.gui": "^0.7.9",
|
||||
"flags": "^4.0.0",
|
||||
"lodash": "catalog:",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "catalog:",
|
||||
"posthog-js": "^1.333.0",
|
||||
"react-use": "^17.4.0",
|
||||
"valtio": "catalog:"
|
||||
|
||||
@@ -802,7 +802,7 @@
|
||||
"mdast": "^3.0.0",
|
||||
"mermaid": "^11.12.1",
|
||||
"monaco-editor": "*",
|
||||
"next-themes": "*",
|
||||
"next-themes": "catalog:",
|
||||
"openai": "^4.75.1",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"radix-ui": "catalog:",
|
||||
|
||||
68
packages/ui/build/css/themes/faux-classic-dark.css
Normal file
68
packages/ui/build/css/themes/faux-classic-dark.css
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This theme has the same values as the dark theme meant to be used in www and docs. www and docs can only support
|
||||
* light and dark themes because they only have images for light and dark themes.
|
||||
*/
|
||||
[data-theme='classic-dark'],
|
||||
.classic-dark {
|
||||
--helpers-os-appearance: Dark;
|
||||
--code-block-5: 13.8deg 89.7% 69.6%;
|
||||
--code-block-4: 276.1deg 67.7% 74.5%;
|
||||
--code-block-3: 83.8deg 61.7% 63.1%;
|
||||
--code-block-2: 33.2deg 90.3% 75.7%;
|
||||
--code-block-1: 170.8deg 43.1% 61.4%;
|
||||
--secondary-default: 247.8deg 100% 70%;
|
||||
--secondary-400: 248.3deg 54.5% 25.9%;
|
||||
--secondary-200: 248deg 53.6% 11%;
|
||||
--brand-link: 155deg 100% 38.6%;
|
||||
--brand-default: 153.1deg 60.2% 52.7%;
|
||||
--brand-600: 154.9deg 59.5% 70%;
|
||||
--brand-500: 154.9deg 100% 19.2%;
|
||||
--brand-400: 155.5deg 100% 9.6%;
|
||||
--brand-300: 155.1deg 100% 8%;
|
||||
--brand-200: 162deg 100% 2%;
|
||||
--warning-default: 38.9deg 100% 42.9%; /* warning-600 */
|
||||
--warning-600: 38.9deg 100% 42.9%;
|
||||
--warning-500: 34.8deg 90.9% 21.6%;
|
||||
--warning-400: 33.2deg 100% 14.5%;
|
||||
--warning-300: 32.3deg 100% 10.2%;
|
||||
--warning-200: 36.6deg 100% 8%;
|
||||
--destructive-default: 10.2deg 77.9% 53.9%;
|
||||
--destructive-600: 9.7deg 85.2% 62.9%;
|
||||
--destructive-500: 7.9deg 71.6% 29%;
|
||||
--destructive-400: 6.7deg 60% 20.6%;
|
||||
--destructive-300: 7.5deg 51.3% 15.3%;
|
||||
--destructive-200: 10.9deg 23.4% 9.2%;
|
||||
--border-stronger: 0deg 0% 27.1%;
|
||||
--border-strong: 0deg 0% 21.2%;
|
||||
--border-alternative: 0deg 0% 26.7%;
|
||||
--border-control: 0deg 0% 22.4%;
|
||||
--border-overlay: 0deg 0% 20%;
|
||||
--border-secondary: 0deg 0% 14.1%;
|
||||
--border-muted: 0deg 0% 14.1%;
|
||||
--border-default: 0deg 0% 18%;
|
||||
--background-dash-canvas: 0deg 0% 7.1%;
|
||||
--background-dash-sidebar: 0deg 0% 9%;
|
||||
--background-dialog-default: 0deg 0% 7.1%;
|
||||
--background-muted: 0deg 0% 14.1%;
|
||||
--background-overlay-hover: 0deg 0% 18%;
|
||||
--background-overlay-default: 0deg 0% 14.1%;
|
||||
--background-surface-400: 0deg 0% 16.1%;
|
||||
--background-surface-300: 0deg 0% 16.1%;
|
||||
--background-surface-200: 0deg 0% 12.9%;
|
||||
--background-surface-100: 0deg 0% 12.2%;
|
||||
--background-surface-75: 0deg 0% 9%;
|
||||
--background-control: 0deg 0% 14.1%;
|
||||
--background-selection: 0deg 0% 19.2%;
|
||||
--background-alternative-default: 0deg 0% 5.9%;
|
||||
--background-default: 0deg 0% 7.1%;
|
||||
--background-200: 0deg 0% 9%;
|
||||
--foreground-contrast: 0deg 0% 8.6%;
|
||||
--foreground-muted: 0deg 0% 30.2%;
|
||||
--foreground-lighter: 0deg 0% 53.7%;
|
||||
--foreground-light: 0deg 0% 70.6%;
|
||||
--foreground-default: 0deg 0% 98%;
|
||||
--border-button-hover: var(--colors-gray-dark-800);
|
||||
--border-button-default: var(--colors-gray-dark-700);
|
||||
--background-button-default: var(--colors-gray-dark-500);
|
||||
--background-alternative-200: var(--colors-gray-dark-200);
|
||||
}
|
||||
@@ -453,7 +453,7 @@ const SidebarGroupAction = React.forwardRef<
|
||||
ref={ref}
|
||||
data-sidebar="group-action"
|
||||
className={cn(
|
||||
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-5 [&>svg]:shrink-0',
|
||||
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-5 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:absolute after:-inset-2 after:md:hidden',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
@@ -503,14 +503,14 @@ SidebarMenuItem.displayName = 'SidebarMenuItem'
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
cn(
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md py-2 px-1.5 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-5 [&>svg]:shrink-0 text-foreground-lighter data-[active=true]:text-foreground'
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md py-2 px-1.5 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent/50 active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent/50 data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-5 [&>svg]:shrink-0 text-foreground-lighter data-[active=true]:text-foreground'
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
||||
default: 'hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground',
|
||||
outline:
|
||||
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
|
||||
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
|
||||
},
|
||||
size: {
|
||||
default: 'h-8 text-sm',
|
||||
@@ -614,7 +614,7 @@ const SidebarMenuAction = React.forwardRef<
|
||||
ref={ref}
|
||||
data-sidebar="menu-action"
|
||||
className={cn(
|
||||
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-5 [&>svg]:shrink-0',
|
||||
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-5 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:absolute after:-inset-2 after:md:hidden',
|
||||
'peer-data-[size=sm]/menu-button:top-1',
|
||||
@@ -722,7 +722,7 @@ const SidebarMenuSubButton = React.forwardRef<
|
||||
data-size={size}
|
||||
data-active={isActive}
|
||||
className={cn(
|
||||
'flex h-6 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-5 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
|
||||
'flex h-6 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent/50 active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-5 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
|
||||
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
||||
size === 'sm' && 'text-xs',
|
||||
size === 'md' && 'text-sm',
|
||||
|
||||
@@ -706,10 +706,11 @@ export default {
|
||||
rounded: `rounded-md`,
|
||||
},
|
||||
pills: {
|
||||
base: `px-3 py-1`,
|
||||
base: `my-px px-3 py-[3px] rounded-md transition-colors active:bg-sidebar-accent/50`,
|
||||
normal: `
|
||||
font-normal
|
||||
border-default
|
||||
hover:bg-sidebar-accent/50
|
||||
group-hover:border-foreground-muted`,
|
||||
active: `
|
||||
font-semibold
|
||||
|
||||
95
pnpm-lock.yaml
generated
95
pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ catalogs:
|
||||
next:
|
||||
specifier: 16.2.3
|
||||
version: 16.2.3
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6
|
||||
radix-ui:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
@@ -169,6 +172,9 @@ importers:
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
common:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common
|
||||
contentlayer2:
|
||||
specifier: 0.4.6
|
||||
version: 0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1)
|
||||
@@ -200,8 +206,8 @@ importers:
|
||||
specifier: 0.4.6
|
||||
version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: 'catalog:'
|
||||
version: 18.3.1
|
||||
@@ -462,8 +468,8 @@ importers:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nuqs:
|
||||
specifier: ^1.19.1
|
||||
version: 1.19.1(next@15.5.15(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))
|
||||
@@ -715,8 +721,8 @@ importers:
|
||||
specifier: 0.4.6
|
||||
version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: 'catalog:'
|
||||
version: 18.3.1
|
||||
@@ -913,7 +919,7 @@ importers:
|
||||
version: 0.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: 'catalog:'
|
||||
version: 10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))
|
||||
version: 10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))
|
||||
'@std/path':
|
||||
specifier: npm:@jsr/std__path@^1.0.8
|
||||
version: '@jsr/std__path@1.0.8'
|
||||
@@ -1065,11 +1071,11 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nuqs:
|
||||
specifier: 2.7.1
|
||||
version: 2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
openai:
|
||||
specifier: ^4.104.0
|
||||
version: 4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)
|
||||
@@ -1319,7 +1325,7 @@ importers:
|
||||
version: 2.11.3(@types/node@22.13.14)(typescript@6.0.2)
|
||||
next-router-mock:
|
||||
specifier: ^0.9.13
|
||||
version: 0.9.13(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
version: 0.9.13(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
node-mocks-http:
|
||||
specifier: ^1.17.2
|
||||
version: 1.17.2(@types/node@22.13.14)
|
||||
@@ -1426,8 +1432,8 @@ importers:
|
||||
specifier: 0.4.6
|
||||
version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
openai:
|
||||
specifier: ^5.9.0
|
||||
version: 5.9.0(ws@8.19.0)(zod@3.25.76)
|
||||
@@ -1724,8 +1730,8 @@ importers:
|
||||
specifier: ^6.5.0
|
||||
version: 6.5.0(next@15.5.15(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nuqs:
|
||||
specifier: ^2.8.1
|
||||
version: 2.8.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.5.15(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
@@ -2082,8 +2088,8 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
posthog-js:
|
||||
specifier: ^1.333.0
|
||||
version: 1.357.0
|
||||
@@ -2206,7 +2212,7 @@ importers:
|
||||
version: link:../config
|
||||
next-router-mock:
|
||||
specifier: ^0.9.13
|
||||
version: 0.9.13(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
version: 0.9.13(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
tailwindcss:
|
||||
specifier: 'catalog:'
|
||||
version: 4.2.4
|
||||
@@ -2563,8 +2569,8 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
next-themes:
|
||||
specifier: '*'
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
openai:
|
||||
specifier: ^4.75.1
|
||||
version: 4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)
|
||||
@@ -2688,7 +2694,7 @@ importers:
|
||||
version: link:../config
|
||||
next-router-mock:
|
||||
specifier: ^0.9.13
|
||||
version: 0.9.13(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
version: 0.9.13(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
tailwindcss:
|
||||
specifier: ^4.2.4
|
||||
version: 4.2.4
|
||||
@@ -3460,24 +3466,15 @@ packages:
|
||||
'@electric-sql/pglite@0.2.15':
|
||||
resolution: {integrity: sha512-Jiq31Dnk+rg8rMhcSxs4lQvHTyizNo5b269c1gCC3ldQ0sCLrNVPGzy+KnmonKy1ZArTUuXZf23/UamzFMKVaA==}
|
||||
|
||||
'@emnapi/core@1.9.0':
|
||||
resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==}
|
||||
|
||||
'@emnapi/core@1.9.2':
|
||||
resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
|
||||
|
||||
'@emnapi/runtime@0.43.1':
|
||||
resolution: {integrity: sha512-Q5sMc4Z4gsD4tlmlyFu+MpNAwpR7Gv2errDhVJ+SOhNjWcx8UTqy+hswb8L31RfC8jBvDgcnT87l3xI2w08rAg==}
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
|
||||
|
||||
'@emnapi/runtime@1.9.2':
|
||||
resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
|
||||
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
|
||||
|
||||
'@emnapi/wasi-threads@1.2.1':
|
||||
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
||||
|
||||
@@ -13886,11 +13883,11 @@ packages:
|
||||
react: '>=16.0.0'
|
||||
react-dom: '>=16.0.0'
|
||||
|
||||
next-themes@0.3.0:
|
||||
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
|
||||
next-themes@0.4.6:
|
||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17 || ^18
|
||||
react-dom: ^16.8 || ^17 || ^18
|
||||
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
|
||||
next-tick@1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
@@ -19310,12 +19307,6 @@ snapshots:
|
||||
'@electric-sql/pglite@0.2.15':
|
||||
optional: true
|
||||
|
||||
'@emnapi/core@1.9.0':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.2.0
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/core@1.9.2':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.2.1
|
||||
@@ -19326,21 +19317,11 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.2.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -20336,7 +20317,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.7.1
|
||||
'@emnapi/runtime': 1.9.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
@@ -20745,8 +20726,8 @@ snapshots:
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.9.0
|
||||
'@emnapi/runtime': 1.7.1
|
||||
'@emnapi/core': 1.9.2
|
||||
'@emnapi/runtime': 1.9.2
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
@@ -23591,7 +23572,7 @@ snapshots:
|
||||
- supports-color
|
||||
- webpack
|
||||
|
||||
'@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))':
|
||||
'@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.38.0
|
||||
@@ -31613,7 +31594,7 @@ snapshots:
|
||||
dependencies:
|
||||
js-yaml-loader: 1.2.2
|
||||
|
||||
next-router-mock@0.9.13(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1):
|
||||
next-router-mock@0.9.13(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1):
|
||||
dependencies:
|
||||
next: 16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
react: 18.3.1
|
||||
@@ -31624,7 +31605,7 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
@@ -31946,7 +31927,7 @@ snapshots:
|
||||
mitt: 3.0.1
|
||||
next: 15.5.15(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
|
||||
nuqs@2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||
nuqs@2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
react: 18.3.1
|
||||
|
||||
@@ -20,6 +20,7 @@ catalog:
|
||||
lodash: ^4.18.1
|
||||
lodash-es: ^4.18.1
|
||||
next: 16.2.3
|
||||
next-themes: ^0.4.6
|
||||
postcss: ^8.5.10
|
||||
radix-ui: ^1.4.3
|
||||
react: ^18.3.0
|
||||
|
||||
Reference in New Issue
Block a user