- {/* Top Banner */}
-
-
-
-
-
- {/* Main Content Area */}
-
- {/* Sidebar - Only show for project pages, not account pages */}
- {!router.pathname.startsWith('/account') &&
}
- {/* Main Content with Layout Sidebar */}
-
-
- {children}
-
-
+
+
+ {/* Top Banner */}
+
+
+
+
-
+
+ {/* Main Content Area */}
+
+ {/* Sidebar - Only show for project pages, not account pages */}
+ {!router.pathname.startsWith('/account') &&
}
+ {/* Main Content with Layout Sidebar */}
+
+
+ {children}
+
+
+
+
-
-
-
+
+
+
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx
index 31f9342a360a0..3c4579f01c941 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx
@@ -15,9 +15,9 @@ import {
PopoverTrigger_Shadcn_,
} from 'ui'
-import { ASSISTANT_SUGGESTIONS } from '../HelpDropdown/HelpDropdown.constants'
-import { getSupportLinkQueryParams } from '../HelpDropdown/HelpDropdown.utils'
-import { HelpSection } from '../HelpDropdown/HelpSection'
+import { ASSISTANT_SUGGESTIONS } from '../HelpPanel/HelpPanel.constants'
+import { getSupportLinkQueryParams } from '../HelpPanel/HelpPanel.utils'
+import { HelpSection } from '../HelpPanel/HelpSection'
import { FeedbackWidget } from './FeedbackWidget'
export const FeedbackDropdown = ({ className }: { className?: string }) => {
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.tsx
deleted file mode 100644
index 8e9489008b055..0000000000000
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { IS_PLATFORM } from 'common'
-import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
-import { ButtonTooltip } from 'components/ui/ButtonTooltip'
-import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
-import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { HelpCircle } from 'lucide-react'
-import Image from 'next/legacy/image'
-import { useRouter } from 'next/router'
-import { useState } from 'react'
-import SVG from 'react-inlinesvg'
-import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
-import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
-import {
- Button,
- cn,
- Popover,
- Popover_Shadcn_,
- PopoverContent_Shadcn_,
- PopoverTrigger_Shadcn_,
-} from 'ui'
-
-import { ASSISTANT_SUGGESTIONS } from './HelpDropdown.constants'
-import { getSupportLinkQueryParams } from './HelpDropdown.utils'
-import { HelpSection } from './HelpSection'
-
-export const HelpDropdown = () => {
- const router = useRouter()
- const { data: project } = useSelectedProjectQuery()
- const { data: org } = useSelectedOrganizationQuery()
- const snap = useAiAssistantStateSnapshot()
- const { openSidebar } = useSidebarManagerSnapshot()
- const { mutate: sendEvent } = useSendEventMutation()
- const [isOpen, setIsOpen] = useState(false)
-
- const projectRef = project?.parent_project_ref ?? (router.query.ref as string | undefined)
- const supportLinkQueryParams = getSupportLinkQueryParams(
- project,
- org,
- router.query.ref as string | undefined
- )
-
- return (
-
-
- {
- if (isOpen) return // Don't send telemetry event if dropdown is already open
- sendEvent({
- action: 'help_button_clicked',
- groups: { project: project?.ref, organization: org?.slug },
- })
- }}
- tooltip={{ content: { side: 'bottom', text: 'Help' } }}
- >
-
-
-
-
- {
- setIsOpen(false)
- openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
- snap.newChat(ASSISTANT_SUGGESTIONS)
- }}
- onSupportClick={() => setIsOpen(false)}
- />
-
-
-
-
Community support
-
- Our Discord community can help with code-related issues. Many questions are answered
- in minutes.
-
-
-
-
-
-
- )
-}
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpButton.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpButton.tsx
new file mode 100644
index 0000000000000..c986e14a34c83
--- /dev/null
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpButton.tsx
@@ -0,0 +1,46 @@
+import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
+import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { HelpCircle } from 'lucide-react'
+import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
+import { cn } from 'ui'
+
+export const HelpButton = () => {
+ const { toggleSidebar, activeSidebar } = useSidebarManagerSnapshot()
+ const { data: project } = useSelectedProjectQuery()
+ const { data: org } = useSelectedOrganizationQuery()
+ const { mutate: sendEvent } = useSendEventMutation()
+
+ const isOpen = activeSidebar?.id === SIDEBAR_KEYS.HELP_PANEL
+
+ return (
+
{
+ toggleSidebar(SIDEBAR_KEYS.HELP_PANEL)
+ // Don't send telemetry event if dropdown is already open
+ if (!isOpen) {
+ sendEvent({
+ action: 'help_button_clicked',
+ groups: { project: project?.ref, organization: org?.slug },
+ })
+ }
+ }}
+ tooltip={{ content: { side: 'bottom', text: 'Help' } }}
+ >
+
+
+ )
+}
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpOptionsList.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpOptionsList.tsx
similarity index 97%
rename from apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpOptionsList.tsx
rename to apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpOptionsList.tsx
index 33171c30f4444..278f35584623b 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpOptionsList.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpOptionsList.tsx
@@ -6,8 +6,8 @@ import { useRouter } from 'next/router'
import SVG from 'react-inlinesvg'
import { AiIconAnimation, ButtonGroup, ButtonGroupItem } from 'ui'
-import type { HelpOptionId } from './HelpDropdown.constants'
-import { HELP_OPTION_IDS } from './HelpDropdown.constants'
+import type { HelpOptionId } from './HelpPanel.constants'
+import { HELP_OPTION_IDS } from './HelpPanel.constants'
const DISCORD_URL = 'https://discord.supabase.com'
const STATUS_URL = 'https://status.supabase.com'
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.constants.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.constants.ts
similarity index 100%
rename from apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.constants.ts
rename to apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.constants.ts
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.tsx
new file mode 100644
index 0000000000000..264450d190d89
--- /dev/null
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.tsx
@@ -0,0 +1,97 @@
+import { IS_PLATFORM } from 'common'
+import type { SupportFormUrlKeys } from 'components/interfaces/Support/SupportForm.utils'
+import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import { X } from 'lucide-react'
+import Image from 'next/image'
+import { useRouter } from 'next/router'
+import SVG from 'react-inlinesvg'
+import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
+import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
+import { Button, cn, Separator } from 'ui'
+import styleHandler from 'ui/src/lib/theme/styleHandler'
+
+import { ASSISTANT_SUGGESTIONS } from './HelpPanel.constants'
+import { HelpSection } from './HelpSection'
+
+export const HelpPanel = ({
+ onClose,
+ projectRef,
+ supportLinkQueryParams,
+}: {
+ onClose: () => void
+ projectRef: string | undefined
+ supportLinkQueryParams: Partial
| undefined
+}) => {
+ const snap = useAiAssistantStateSnapshot()
+ const { openSidebar, closeSidebar } = useSidebarManagerSnapshot()
+ const router = useRouter()
+
+ const __styles = styleHandler('popover')
+
+ return (
+
+
+ Help & Support
+ closeSidebar(SIDEBAR_KEYS.HELP_PANEL)}
+ icon={}
+ tooltip={{ content: { side: 'bottom', text: 'Close' } }}
+ />
+
+
{
+ onClose()
+ openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
+ snap.newChat(ASSISTANT_SUGGESTIONS)
+ }}
+ onSupportClick={onClose}
+ />
+
+
+
+
Community support
+
+ Our Discord community can help with code-related issues. Many questions are answered in
+ minutes.
+
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.utils.test.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.utils.test.ts
new file mode 100644
index 0000000000000..95e640f0e3846
--- /dev/null
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.utils.test.ts
@@ -0,0 +1,57 @@
+import { describe, expect, it } from 'vitest'
+
+import { getSupportLinkQueryParams } from './HelpPanel.utils'
+
+describe('getSupportLinkQueryParams', () => {
+ it('returns { projectRef } when project has parent_project_ref', () => {
+ expect(
+ getSupportLinkQueryParams(
+ { parent_project_ref: 'main-project' },
+ { slug: 'my-org' },
+ 'router-ref'
+ )
+ ).toEqual({ projectRef: 'main-project' })
+ })
+
+ it('returns { projectRef } from routerRef when project has no parent_project_ref', () => {
+ expect(getSupportLinkQueryParams({}, { slug: 'my-org' }, 'router-ref')).toEqual({
+ projectRef: 'router-ref',
+ })
+ })
+
+ it('returns { projectRef } from routerRef when project is undefined', () => {
+ expect(getSupportLinkQueryParams(undefined, { slug: 'my-org' }, 'router-ref')).toEqual({
+ projectRef: 'router-ref',
+ })
+ })
+
+ it('returns { orgSlug } when no projectRef (no project, no routerRef)', () => {
+ expect(getSupportLinkQueryParams(undefined, { slug: 'my-org' }, undefined)).toEqual({
+ orgSlug: 'my-org',
+ })
+ })
+
+ it('returns { orgSlug } when project and routerRef are undefined but org has slug', () => {
+ expect(getSupportLinkQueryParams(undefined, { slug: 'acme' }, undefined)).toEqual({
+ orgSlug: 'acme',
+ })
+ })
+
+ it('returns undefined when project, org and routerRef give no ref', () => {
+ expect(getSupportLinkQueryParams(undefined, undefined, undefined)).toBeUndefined()
+ })
+
+ it('returns undefined when org has no slug and no projectRef', () => {
+ expect(getSupportLinkQueryParams(undefined, {}, undefined)).toBeUndefined()
+ })
+
+ it('returns undefined when org is undefined and no projectRef', () => {
+ expect(getSupportLinkQueryParams(undefined, undefined, undefined)).toBeUndefined()
+ })
+
+ it('prefers parent_project_ref over routerRef when both are present', () => {
+ expect(
+ getSupportLinkQueryParams({ parent_project_ref: 'parent-ref' }, { slug: 'org' }, 'router-ref')
+ ).toEqual({ projectRef: 'parent-ref' })
+ })
+})
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.utils.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.utils.ts
similarity index 100%
rename from apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpDropdown.utils.ts
rename to apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel.utils.ts
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpSection.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpSection.tsx
similarity index 95%
rename from apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpSection.tsx
rename to apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpSection.tsx
index bc6a246880002..7aea2451dc4f3 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpDropdown/HelpSection.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpSection.tsx
@@ -1,8 +1,8 @@
import type { SupportFormUrlKeys } from 'components/interfaces/Support/SupportForm.utils'
import { cn } from 'ui'
-import type { HelpOptionId } from './HelpDropdown.constants'
import { HelpOptionsList } from './HelpOptionsList'
+import type { HelpOptionId } from './HelpPanel.constants'
type HelpSectionProps = {
excludeIds?: HelpOptionId[]
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
index 08f9f317bf2d3..ce5d2e78aebc4 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
@@ -26,7 +26,7 @@ import { CommandMenuTriggerInput } from 'ui-patterns'
import { BreadcrumbsView } from './BreadcrumbsView'
import { FeedbackDropdown } from './FeedbackDropdown/FeedbackDropdown'
-import { HelpDropdown } from './HelpDropdown/HelpDropdown'
+import { HelpButton } from './HelpPanel/HelpButton'
import { HomeIcon } from './HomeIcon'
import { LocalVersionPopover } from './LocalVersionPopover'
import { MergeRequestButton } from './MergeRequestButton'
@@ -231,7 +231,7 @@ export const LayoutHeader = ({
'[&_.command-shortcut>div]:text-foreground-lighter'
)}
/>
-
+
{!!projectRef && (
@@ -257,7 +257,7 @@ export const LayoutHeader = ({
[&_.command-shortcut>div]:text-foreground-lighter
"
/>
-
+
{!!projectRef && (
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider.tsx
index f653c316d499d..74aa05af224b7 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider.tsx
@@ -1,22 +1,37 @@
-import { useRouter } from 'next/router'
-import { parseAsString, useQueryState } from 'nuqs'
-import { PropsWithChildren, useEffect } from 'react'
-
import { LOCAL_STORAGE_KEYS } from 'common'
-import { AdvisorPanel } from 'components/ui/AdvisorPanel/AdvisorPanel'
-import { AIAssistant } from 'components/ui/AIAssistantPanel/AIAssistant'
-import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import useLatest from 'hooks/misc/useLatest'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import dynamic from 'next/dynamic'
+import { useRouter } from 'next/router'
+import { parseAsString, useQueryState } from 'nuqs'
+import { useEffect, type PropsWithChildren } from 'react'
import { useRegisterSidebar, useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
+import { getSupportLinkQueryParams } from '../LayoutHeader/HelpPanel/HelpPanel.utils'
+
+const AdvisorPanel = dynamic(() =>
+ import('components/ui/AdvisorPanel/AdvisorPanel').then((m) => m.AdvisorPanel)
+)
+const AIAssistant = dynamic(() =>
+ import('components/ui/AIAssistantPanel/AIAssistant').then((m) => m.AIAssistant)
+)
+const EditorPanel = dynamic(() =>
+ import('components/ui/EditorPanel/EditorPanel').then((m) => m.EditorPanel)
+)
+const HelpPanel = dynamic(() =>
+ import('components/layouts/ProjectLayout/LayoutHeader/HelpPanel/HelpPanel').then(
+ (m) => m.HelpPanel
+ )
+)
+
export const SIDEBAR_KEYS = {
AI_ASSISTANT: 'ai-assistant',
EDITOR_PANEL: 'editor-panel',
ADVISOR_PANEL: 'advisor-panel',
+ HELP_PANEL: 'help-panel',
} as const
export const LayoutSidebarProvider = ({ children }: PropsWithChildren) => {
@@ -24,7 +39,7 @@ export const LayoutSidebarProvider = ({ children }: PropsWithChildren) => {
const { data: project } = useSelectedProjectQuery()
const { data: org } = useSelectedOrganizationQuery()
const { mutate: sendEvent } = useSendEventMutation()
- const { openSidebar, activeSidebar } = useSidebarManagerSnapshot()
+ const { openSidebar, closeSidebar, activeSidebar } = useSidebarManagerSnapshot()
const [sidebarURLParam, setSidebarUrlParam] = useQueryState('sidebar', parseAsString)
const [sidebarLocalStorage, setSidebarLocalStorage, { isSuccess: isLoadedLocalStorage }] =
@@ -36,6 +51,23 @@ export const LayoutSidebarProvider = ({ children }: PropsWithChildren) => {
useRegisterSidebar(SIDEBAR_KEYS.AI_ASSISTANT, () => , {}, 'i', !!project)
useRegisterSidebar(SIDEBAR_KEYS.EDITOR_PANEL, () => , {}, 'e', !!project)
useRegisterSidebar(SIDEBAR_KEYS.ADVISOR_PANEL, () => , {}, undefined, true)
+ useRegisterSidebar(
+ SIDEBAR_KEYS.HELP_PANEL,
+ () => (
+ closeSidebar(SIDEBAR_KEYS.HELP_PANEL)}
+ projectRef={project?.ref}
+ supportLinkQueryParams={getSupportLinkQueryParams(
+ project,
+ org,
+ router.query.ref as string | undefined
+ )}
+ />
+ ),
+ {},
+ undefined,
+ true
+ )
useEffect(() => {
if (!!project) {
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.test.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.test.tsx
index 889e07bc12443..8872548186be2 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.test.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.test.tsx
@@ -1,10 +1,11 @@
import { act, screen, waitFor } from '@testing-library/react'
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-
import { sidebarManagerState } from 'state/sidebar-manager-state'
import { render } from 'tests/helpers'
import { routerMock } from 'tests/lib/route-mock'
import { ResizablePanel, ResizablePanelGroup } from 'ui'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { MobileSheetProvider } from '../NavigationBar/MobileSheetContext'
import { LayoutSidebar } from './index'
import { LayoutSidebarProvider, SIDEBAR_KEYS } from './LayoutSidebarProvider'
@@ -109,7 +110,9 @@ describe('LayoutSidebar', () => {
-
+
+
+
)
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.tsx
index 3e3161f0ce447..ac76bf481647d 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutSidebar/index.tsx
@@ -1,5 +1,10 @@
+import { useBreakpoint } from 'common'
+import { useEffect } from 'react'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
-import { ResizableHandle, ResizablePanel, cn } from 'ui'
+import { cn, ResizableHandle, ResizablePanel } from 'ui'
+import { MobileSheetNav } from 'ui-patterns'
+
+import { useMobileSheet } from '../NavigationBar/MobileSheetContext'
// Having these params as props as otherwise it's quite hard to visually check the sizes in DefaultLayout
// as react resizeable panels requires all these values to be valid to render correctly
@@ -14,10 +19,38 @@ export const LayoutSidebar = ({
maxSize = '50',
defaultSize = '30',
}: LayoutSidebarProps) => {
- const { activeSidebar } = useSidebarManagerSnapshot()
+ const { activeSidebar, closeActive } = useSidebarManagerSnapshot()
+ const isMobile = useBreakpoint('md')
+ const { content: mobileSheetContent, setContent: setMobileSheetContent } = useMobileSheet()
+
+ // On mobile the sidebar content is rendered in MobileSheetNav
+ useEffect(() => {
+ if (isMobile && activeSidebar?.component) {
+ setMobileSheetContent(activeSidebar.id)
+ } else {
+ setMobileSheetContent(null)
+ }
+ }, [isMobile, activeSidebar, setMobileSheetContent])
if (!activeSidebar?.component) return null
+ if (isMobile)
+ return (
+ {
+ if (!open) {
+ setMobileSheetContent(null)
+ closeActive()
+ }
+ }}
+ >
+ {activeSidebar?.component?.()}
+
+ )
+
return (
<>
diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileNavigationBar.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileNavigationBar.tsx
index 04b87035fd372..15873138927b0 100644
--- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileNavigationBar.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileNavigationBar.tsx
@@ -1,14 +1,12 @@
+import { useParams } from 'common'
+import { SidebarContent } from 'components/interfaces/Sidebar'
+import { IS_PLATFORM } from 'lib/constants'
import { Menu, Search } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
-
-import { useParams } from 'common'
-import { SidebarContent } from 'components/interfaces/Sidebar'
-import { IS_PLATFORM } from 'lib/constants'
import { Button, cn } from 'ui'
-import { CommandMenuTrigger } from 'ui-patterns'
-import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav'
+import { CommandMenuTrigger, MobileSheetNav } from 'ui-patterns'
export const ICON_SIZE = 20
export const ICON_STROKE_WIDTH = 1.5
diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileSheetContext.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileSheetContext.tsx
new file mode 100644
index 0000000000000..72a6f76ff5059
--- /dev/null
+++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/MobileSheetContext.tsx
@@ -0,0 +1,33 @@
+import type { PropsWithChildren } from 'react'
+import { createContext, useCallback, useContext, useState } from 'react'
+
+export type MobileSheetContentType = null | string
+
+type MobileSheetContextValue = {
+ content: MobileSheetContentType
+ setContent: (content: MobileSheetContentType) => void
+}
+
+const MobileSheetContext = createContext(null)
+
+export function MobileSheetProvider({ children }: PropsWithChildren) {
+ const [content, setContentState] = useState(null)
+
+ const setContent = useCallback((next: MobileSheetContentType) => {
+ setContentState(next)
+ }, [])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function useMobileSheet(): MobileSheetContextValue {
+ const ctx = useContext(MobileSheetContext)
+ if (!ctx) {
+ throw new Error('useMobileSheet must be used within MobileSheetProvider')
+ }
+ return ctx
+}
diff --git a/apps/studio/components/ui/QueryBlock/QueryBlock.tsx b/apps/studio/components/ui/QueryBlock/QueryBlock.tsx
index c8a4bf9a38c0a..d6d530ebbe37d 100644
--- a/apps/studio/components/ui/QueryBlock/QueryBlock.tsx
+++ b/apps/studio/components/ui/QueryBlock/QueryBlock.tsx
@@ -146,59 +146,62 @@ export const QueryBlock = ({
label={label}
badge={isWriteQuery && Write}
actions={
- disabled ? null : (
- <>
- }
- onClick={() => setShowSql(!showSql)}
- tooltip={{
- content: { side: 'bottom', text: showSql ? 'Hide query' : 'Show query' },
- }}
- />
- {hasResults && (
- {
- if (onUpdateChartConfig) onUpdateChartConfig({ chartConfig: { view: nextView } })
- setChartSettings({ ...chartSettings, view: nextView })
- }}
- updateChartConfig={(config) => {
- if (onUpdateChartConfig) onUpdateChartConfig({ chartConfig: config })
- setChartSettings(config)
+ <>
+ {!disabled && (
+ <>
+ }
+ onClick={() => setShowSql(!showSql)}
+ tooltip={{
+ content: { side: 'bottom', text: showSql ? 'Hide query' : 'Show query' },
}}
/>
- )}
+ {hasResults && (
+ {
+ if (onUpdateChartConfig)
+ onUpdateChartConfig({ chartConfig: { view: nextView } })
+ setChartSettings({ ...chartSettings, view: nextView })
+ }}
+ updateChartConfig={(config) => {
+ if (onUpdateChartConfig) onUpdateChartConfig({ chartConfig: config })
+ setChartSettings(config)
+ }}
+ />
+ )}
-
- }
- loading={isExecuting}
- disabled={isExecuting || disabled || !sql}
- onClick={runSelect}
- tooltip={{
- content: {
- side: 'bottom',
- className: 'max-w-56 text-center',
- text: isExecuting
- ? 'Query is running. Check the SQL Editor to manage running queries.'
- : 'Run query',
- },
- }}
- />
+
+ }
+ loading={isExecuting}
+ disabled={isExecuting || disabled || !sql}
+ onClick={runSelect}
+ tooltip={{
+ content: {
+ side: 'bottom',
+ className: 'max-w-56 text-center',
+ text: isExecuting
+ ? 'Query is running. Check the SQL Editor to manage running queries.'
+ : 'Run query',
+ },
+ }}
+ />
+ >
+ )}
- {actions}
- >
- )
+ {actions}
+ >
}
>
{!!showWarning && !blockWriteQueries && (
diff --git a/apps/studio/pages/reset-password.tsx b/apps/studio/pages/reset-password.tsx
index d4aec3b39b931..8613000a38120 100644
--- a/apps/studio/pages/reset-password.tsx
+++ b/apps/studio/pages/reset-password.tsx
@@ -1,4 +1,4 @@
-import ResetPasswordForm from 'components/interfaces/SignIn/ResetPasswordForm'
+import { ResetPasswordForm } from 'components/interfaces/SignIn/ResetPasswordForm'
import ForgotPasswordLayout from 'components/layouts/SignInLayout/ForgotPasswordLayout'
import { withAuth } from 'hooks/misc/withAuth'
import type { NextPageWithLayout } from 'types'
diff --git a/apps/studio/state/storage-explorer.tsx b/apps/studio/state/storage-explorer.tsx
index fa1a9590e69e0..e4fcede7007a1 100644
--- a/apps/studio/state/storage-explorer.tsx
+++ b/apps/studio/state/storage-explorer.tsx
@@ -8,10 +8,6 @@ import * as tus from 'tus-js-client'
import { Button, SONNER_DEFAULT_DURATION, SonnerProgress } from 'ui'
import { proxy, useSnapshot } from 'valtio'
-import {
- inverseValidObjectKeyRegex,
- validObjectKeyRegex,
-} from '@/components/interfaces/Storage/CreateBucketModal.utils'
import {
STORAGE_BUCKET_SORT,
STORAGE_ROW_STATUS,
@@ -32,6 +28,10 @@ import {
formatFolderItems,
formatTime,
getFilesDataTransferItems,
+ getPathAlongFoldersToIndex,
+ getPathAlongOpenedFolders,
+ sanitizeNameForDuplicateInColumn,
+ validateFolderName,
} from '@/components/interfaces/Storage/StorageExplorer/StorageExplorer.utils'
import { convertFromBytes } from '@/components/interfaces/Storage/StorageSettings/StorageSettings.utils'
import { InlineLink } from '@/components/ui/InlineLink'
@@ -73,7 +73,7 @@ const DEFAULT_PREFERENCES = {
}
const STORAGE_PROGRESS_INFO_TEXT = "Do not close the browser until it's completed"
-let abortController: any
+let abortController: AbortController
if (typeof window !== 'undefined') {
abortController = new AbortController()
}
@@ -100,7 +100,6 @@ function createStorageExplorerState({
resumableUploadUrl,
uploadProgresses: [] as UploadProgress[],
- // abortController,
abortApiCalls: () => {
if (abortController) {
abortController.abort()
@@ -133,7 +132,6 @@ function createStorageExplorerState({
popOpenedFolders: () => {
state.openedFolders = state.openedFolders.slice(0, state.openedFolders.length - 1)
},
-
popOpenedFoldersAtIndex: (index: number) => {
state.openedFolders = state.openedFolders.slice(0, index + 1)
},
@@ -241,33 +239,6 @@ function createStorageExplorerState({
// ======== Folders CRUD ========
- getPathAlongOpenedFolders: (includeBucket = true) => {
- if (includeBucket) {
- return state.openedFolders.length > 0
- ? `${state.selectedBucket.name}/${state.openedFolders.map((folder) => folder.name).join('/')}`
- : state.selectedBucket.name
- }
- return state.openedFolders.map((folder) => folder.name).join('/')
- },
-
- getPathAlongFoldersToIndex: (index: number) => {
- return state.openedFolders
- .slice(0, index)
- .map((folder) => folder.name)
- .join('/')
- },
-
- validateFolderName: (name: string) => {
- if (!validObjectKeyRegex.test(name)) {
- const [match] = name.match(inverseValidObjectKeyRegex) ?? []
- return !!match
- ? `Folder name cannot contain the "${match}" character`
- : 'Folder name contains an invalid special character'
- }
-
- return null
- },
-
addNewFolderPlaceholder: (columnIndex: number) => {
const isPrepend = true
const folderName = 'Untitled folder'
@@ -293,7 +264,7 @@ function createStorageExplorerState({
onError?: () => void
}) => {
const autofix = false
- const formattedName = state.sanitizeNameForDuplicateInColumn({
+ const formattedName = sanitizeNameForDuplicateInColumn(state, {
name: folderName,
autofix,
columnIndex,
@@ -308,7 +279,7 @@ function createStorageExplorerState({
return state.removeTempRows(columnIndex)
}
- const folderNameError = state.validateFolderName(formattedName)
+ const folderNameError = validateFolderName(formattedName)
if (folderNameError) {
onError?.()
return toast.error(folderNameError)
@@ -640,7 +611,7 @@ function createStorageExplorerState({
})
}
- const folderNameError = state.validateFolderName(newName)
+ const folderNameError = validateFolderName(newName)
if (folderNameError) {
onError?.()
return toast.error(folderNameError)
@@ -1103,7 +1074,7 @@ function createStorageExplorerState({
const path = file.path.split('/')
const topLevelFolder = path.length > 1 ? path[0] : null
if (topLevelFolders.includes(topLevelFolder as string)) {
- const newTopLevelFolder = state.sanitizeNameForDuplicateInColumn({
+ const newTopLevelFolder = sanitizeNameForDuplicateInColumn(state, {
name: topLevelFolder as string,
autofix,
columnIndex,
@@ -1144,7 +1115,7 @@ function createStorageExplorerState({
const isWithinFolder = (file?.path ?? '').split('/').length > 1
const fileName = !isWithinFolder
- ? state.sanitizeNameForDuplicateInColumn({ name: file.name, autofix })
+ ? sanitizeNameForDuplicateInColumn(state, { name: file.name, autofix })
: file.name
const unsanitizedFormattedFileName =
has(file, ['path']) && isWithinFolder ? file.path : fileName
@@ -1707,7 +1678,7 @@ function createStorageExplorerState({
columnIndex,
updatedName: newName,
})
- const pathToFile = state.getPathAlongFoldersToIndex(columnIndex)
+ const pathToFile = getPathAlongFoldersToIndex(state, columnIndex)
const fromPath = pathToFile.length > 0 ? `${pathToFile}/${originalName}` : originalName
const toPath = pathToFile.length > 0 ? `${pathToFile}/${newName}` : newName
@@ -1777,54 +1748,6 @@ function createStorageExplorerState({
}
},
- sanitizeNameForDuplicateInColumn: ({
- name,
- columnIndex,
- autofix = false,
- }: {
- name: string
- columnIndex?: number
- autofix?: boolean
- }) => {
- const columnIndex_ = columnIndex !== undefined ? columnIndex : state.getLatestColumnIndex()
- const currentColumn = state.columns[columnIndex_]
- const currentColumnItems = currentColumn.items.filter(
- (item) => item.status !== STORAGE_ROW_STATUS.EDITING
- )
- // [Joshen] JFYI storage does support folders of the same name with different casing
- // but its an issue with the List V1 endpoint that's causing an issue with fetching contents
- // for folders of the same name with different casing
- // We should remove this check once all projects are on the List V2 endpoint
- const hasSameNameInColumn =
- currentColumnItems.filter((item) => item.name.toLowerCase() === name.toLowerCase()).length >
- 0
-
- if (hasSameNameInColumn) {
- if (autofix) {
- const fileNameSegments = name.split('.')
- const fileName = fileNameSegments.slice(0, fileNameSegments.length - 1).join('.')
- const fileExt = fileNameSegments[fileNameSegments.length - 1]
-
- const dupeNameRegex = new RegExp(
- `${fileName} \\([-0-9]+\\)${fileExt ? '.' + fileExt : ''}$`
- )
- const itemsWithSameNameInColumn = currentColumnItems.filter((item) =>
- item.name.match(dupeNameRegex)
- )
-
- const updatedFileName = fileName + ` (${itemsWithSameNameInColumn.length + 1})`
- return fileExt ? `${updatedFileName}.${fileExt}` : updatedFileName
- } else {
- toast.error(
- `The name ${name} already exists in the current directory. Please use a different name.`
- )
- return null
- }
- }
-
- return name
- },
-
addTempRow: ({
type,
name,
@@ -1927,7 +1850,7 @@ function createStorageExplorerState({
return state
}
-type StorageExplorerState = ReturnType
+export type StorageExplorerState = ReturnType
const DEFAULT_STATE_CONFIG = {
projectRef: '',
diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts
index 7286edec5fd23..4eb26581df46f 100644
--- a/packages/common/telemetry-constants.ts
+++ b/packages/common/telemetry-constants.ts
@@ -2320,7 +2320,7 @@ export interface SidebarOpenedEvent {
/**
* The sidebar panel that was opened, e.g. ai-assistant, editor-panel, advisor-panel
*/
- sidebar: 'ai-assistant' | 'editor-panel' | 'advisor-panel'
+ sidebar: 'ai-assistant' | 'editor-panel' | 'advisor-panel' | 'help-panel'
}
groups: TelemetryGroups
}
diff --git a/packages/ui-patterns/index.tsx b/packages/ui-patterns/index.tsx
index 1be06287121d5..f7ffdde0b60fd 100644
--- a/packages/ui-patterns/index.tsx
+++ b/packages/ui-patterns/index.tsx
@@ -16,6 +16,7 @@ export * from './src/FilterBar'
export * from './src/GlassPanel'
export * from './src/InnerSideMenu'
export * from './src/McpUrlBuilder'
+export * from './src/MobileSheetNav'
export * from './src/PageContainer'
export * from './src/PageHeader'
export * from './src/PageSection'
diff --git a/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.test.tsx b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.test.tsx
new file mode 100644
index 0000000000000..65137313efda0
--- /dev/null
+++ b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.test.tsx
@@ -0,0 +1,199 @@
+import { act, render, screen, waitFor } from '@testing-library/react'
+import { useEffect, useState } from 'react'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { MobileSheetNav } from './MobileSheetNav'
+
+const mockRouter = vi.hoisted(() => ({ asPath: '/initial' }))
+const mockWindowSize = vi.hoisted(() => ({ width: 400 }))
+
+vi.mock('next/router', () => ({
+ useRouter: () => mockRouter,
+}))
+
+vi.mock('react-use', () => ({
+ useWindowSize: () => mockWindowSize,
+}))
+
+function MobileSheetNavWithState({
+ shouldCloseOnRouteChange = true,
+ shouldCloseOnViewportResize = true,
+}: {
+ shouldCloseOnRouteChange?: boolean
+ shouldCloseOnViewportResize?: boolean
+}) {
+ const [open, setOpen] = useState(false)
+ useEffect(() => {
+ setOpen(true)
+ }, [])
+ return (
+ <>
+ {String(open)}
+
+ Nav content
+
+ >
+ )
+}
+
+describe('MobileSheetNav', () => {
+ const defaultProps = {
+ onOpenChange: vi.fn(),
+ children: Nav content
,
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockRouter.asPath = '/initial'
+ mockWindowSize.width = 400
+ })
+
+ describe('shouldCloseOnRouteChange', () => {
+ it('calls onOpenChange(false) when route changes and shouldCloseOnRouteChange is true (default)', () => {
+ const onOpenChange = vi.fn()
+ const { rerender } = render()
+
+ onOpenChange.mockClear()
+ mockRouter.asPath = '/other-page'
+ act(() => {
+ rerender()
+ })
+
+ expect(onOpenChange).toHaveBeenCalledWith(false)
+ })
+
+ it('effectively closes the sheet on route change when shouldCloseOnRouteChange is true', async () => {
+ const { rerender } = render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ mockRouter.asPath = '/other-page'
+ act(() => {
+ rerender()
+ })
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('false')
+ })
+ })
+
+ it('effectively does NOT close the sheet on route change when shouldCloseOnRouteChange is false', async () => {
+ const { rerender } = render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ mockRouter.asPath = '/other-page'
+ act(() => {
+ rerender()
+ })
+
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ it('does not call onOpenChange when route changes if shouldCloseOnRouteChange is false', () => {
+ const onOpenChange = vi.fn()
+ const { rerender } = render(
+
+ )
+
+ onOpenChange.mockClear()
+ mockRouter.asPath = '/other-page'
+ act(() => {
+ rerender(
+
+ )
+ })
+
+ expect(onOpenChange).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('shouldCloseOnViewportResize', () => {
+ it('calls onOpenChange(false) when width changes and shouldCloseOnViewportResize is true (default)', () => {
+ const onOpenChange = vi.fn()
+ const { rerender } = render()
+
+ onOpenChange.mockClear()
+ mockWindowSize.width = 800
+ act(() => {
+ rerender()
+ })
+
+ expect(onOpenChange).toHaveBeenCalledWith(false)
+ })
+
+ it('effectively closes the sheet on viewport resize when shouldCloseOnViewportResize is true', async () => {
+ const { rerender } = render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ mockWindowSize.width = 800
+ act(() => {
+ rerender()
+ })
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('false')
+ })
+ })
+
+ it('effectively does NOT close the sheet on viewport resize when shouldCloseOnViewportResize is false', async () => {
+ const { rerender } = render()
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ mockWindowSize.width = 800
+ act(() => {
+ rerender()
+ })
+
+ expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
+ })
+
+ it('does not call onOpenChange when width changes if shouldCloseOnViewportResize is false', () => {
+ const onOpenChange = vi.fn()
+ const { rerender } = render(
+
+ )
+
+ onOpenChange.mockClear()
+ mockWindowSize.width = 800
+ act(() => {
+ rerender(
+
+ )
+ })
+
+ expect(onOpenChange).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx
index 6e06fc0621dd1..064035ac60739 100644
--- a/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx
+++ b/packages/ui-patterns/src/MobileSheetNav/MobileSheetNav.tsx
@@ -11,17 +11,31 @@ const MobileSheetNav: React.FC<{
children: React.ReactNode
open?: boolean
onOpenChange(open: boolean): void
-}> = ({ children, open = false, onOpenChange }) => {
+ className?: string
+ shouldCloseOnRouteChange?: boolean
+ shouldCloseOnViewportResize?: boolean
+}> = ({
+ children,
+ open = false,
+ onOpenChange,
+ className,
+ shouldCloseOnRouteChange = true,
+ shouldCloseOnViewportResize = true,
+}) => {
const router = useRouter()
const { width } = useWindowSize()
const pathWithoutQuery = router?.asPath?.split('?')?.[0]
useEffect(() => {
- onOpenChange(false)
+ if (shouldCloseOnRouteChange) {
+ onOpenChange(false)
+ }
}, [pathWithoutQuery])
useEffect(() => {
- onOpenChange(false)
+ if (shouldCloseOnViewportResize) {
+ onOpenChange(false)
+ }
}, [width])
return (
@@ -31,7 +45,10 @@ const MobileSheetNav: React.FC<{
showClose={false}
size="full"
side="bottom"
- className={cn('rounded-t-lg overflow-hidden overflow-y-scroll h-[85dvh] md:max-h-[500px]')}
+ className={cn(
+ 'rounded-t-lg bg-background overflow-hidden overflow-y-scroll h-[85dvh] md:max-h-[500px]',
+ className
+ )}
>
}>{children}
@@ -39,4 +56,5 @@ const MobileSheetNav: React.FC<{
)
}
+export { MobileSheetNav }
export default MobileSheetNav
diff --git a/packages/ui-patterns/src/MobileSheetNav/index.ts b/packages/ui-patterns/src/MobileSheetNav/index.ts
index fd58868d48141..06b0a8256915b 100644
--- a/packages/ui-patterns/src/MobileSheetNav/index.ts
+++ b/packages/ui-patterns/src/MobileSheetNav/index.ts
@@ -1 +1 @@
-export * from './MobileSheetNav'
+export { default as MobileSheetNav } from './MobileSheetNav'
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1fdf57c8ef973..8fc7135cb2ae4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,17 +10,17 @@ catalogs:
specifier: ^10.26.0
version: 10.27.0
'@supabase/auth-js':
- specifier: 2.97.1-canary.3
- version: 2.97.1-canary.3
+ specifier: 2.98.0
+ version: 2.98.0
'@supabase/postgrest-js':
- specifier: 2.97.1-canary.3
- version: 2.97.1-canary.3
+ specifier: 2.98.0
+ version: 2.98.0
'@supabase/realtime-js':
- specifier: 2.97.1-canary.3
- version: 2.97.1-canary.3
+ specifier: 2.98.0
+ version: 2.98.0
'@supabase/supabase-js':
- specifier: 2.97.1-canary.3
- version: 2.97.1-canary.3
+ specifier: 2.98.0
+ version: 2.98.0
'@types/node':
specifier: ^22.0.0
version: 22.13.14
@@ -317,7 +317,7 @@ importers:
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@15.5.10(@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.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.9.2)))
@@ -829,7 +829,7 @@ importers:
version: 7.5.0
'@supabase/auth-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@supabase/mcp-server-supabase':
specifier: ^0.6.3
version: 0.6.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(supports-color@8.1.1)(zod@3.25.76))(zod@3.25.76)
@@ -841,7 +841,7 @@ importers:
version: link:../../packages/pg-meta
'@supabase/realtime-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@supabase/shared-types':
specifier: 0.1.84
version: 0.1.84
@@ -850,7 +850,7 @@ importers:
version: 0.1.6(encoding@0.1.13)(supports-color@8.1.1)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@tanstack/react-query':
specifier: ^5.0.0
version: 5.83.0(react@18.3.1)
@@ -1367,7 +1367,7 @@ importers:
version: 7.4.0(@react-router/dev@7.9.6(@types/node@22.13.14)(@vitejs/plugin-rsc@0.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)))(babel-plugin-macros@3.1.0)(jiti@2.5.1)(react-router@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1))(typescript@5.9.2)
'@supabase/postgrest-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@supabase/supa-mdx-lint':
specifier: 0.2.6-alpha
version: 0.2.6-alpha
@@ -1488,10 +1488,10 @@ importers:
version: 1.6.0
'@supabase/ssr':
specifier: ^0.7.0
- version: 0.7.0(@supabase/supabase-js@2.97.1-canary.3)
+ version: 0.7.0(@supabase/supabase-js@2.98.0)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@tanstack/react-router':
specifier: ^1.150.0
version: 1.158.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1602,7 +1602,7 @@ importers:
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@15.5.10(@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.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@vercel/og':
specifier: ^0.6.2
version: 0.6.2
@@ -1849,10 +1849,10 @@ importers:
dependencies:
'@supabase/ssr':
specifier: ^0.7.0
- version: 0.7.0(@supabase/supabase-js@2.97.1-canary.3)
+ version: 0.7.0(@supabase/supabase-js@2.98.0)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@vueuse/core':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.21(typescript@5.9.2))
@@ -1898,7 +1898,7 @@ importers:
version: 1.56.1
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
cross-fetch:
specifier: ^4.1.0
version: 4.1.0(encoding@0.1.13)
@@ -1926,7 +1926,7 @@ importers:
version: 0.18.5
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
ai:
specifier: 5.0.52
version: 5.0.52(zod@3.25.76)
@@ -2020,10 +2020,10 @@ importers:
dependencies:
'@supabase/auth-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@types/dat.gui':
specifier: ^0.7.12
version: 0.7.12
@@ -2582,7 +2582,7 @@ importers:
version: 0.1.6(encoding@0.1.13)(supports-color@8.1.1)
'@supabase/supabase-js':
specifier: 'catalog:'
- version: 2.97.1-canary.3
+ version: 2.98.0
'@vitest/coverage-v8':
specifier: ^3.2.0
version: 3.2.4(supports-color@8.1.1)(vitest@3.2.4)
@@ -8209,12 +8209,12 @@ packages:
resolution: {integrity: sha512-Cq3KKe+G1o7PSBMbmrgpT2JgBeyH2THHr3RdIX2MqF7AnBuspIMgtZ3ktcCgP7kZsTMvnmWymr7zZCT1zeWbMw==}
engines: {node: '>=12.16'}
- '@supabase/auth-js@2.97.1-canary.3':
- resolution: {integrity: sha512-MLsim92wAP6zC20dfl98HlZmpNdVWCQHvuaVvorT1wXiqe4n6iseGtLXhHrC1rBsV8UreEJ7TltPa97bUAH32w==}
+ '@supabase/auth-js@2.98.0':
+ resolution: {integrity: sha512-GBH361T0peHU91AQNzOlIrjUZw9TZbB9YDRiyFgk/3Kvr3/Z1NWUZ2athWTfHhwNNi8IrW00foyFxQD9IO/Trg==}
engines: {node: '>=20.0.0'}
- '@supabase/functions-js@2.97.1-canary.3':
- resolution: {integrity: sha512-lgjHUVnRwsmtlbrfBvq/BTK8SIzzX8zGskjLQpxI0SRVeppJ0/krcrhgqys4bdns5bGOfn2QIRqhGSW4eDpj5g==}
+ '@supabase/functions-js@2.98.0':
+ resolution: {integrity: sha512-N/xEyiNU5Org+d+PNCpv+TWniAXRzxIURxDYsS/m2I/sfAB/HcM9aM2Dmf5edj5oWb9GxID1OBaZ8NMmPXL+Lg==}
engines: {node: '>=20.0.0'}
'@supabase/mcp-server-supabase@0.6.3':
@@ -8234,12 +8234,12 @@ packages:
resolution: {integrity: sha512-vz5gc6RKNfDVnIfRUmH2ssTMYFI0U3MYOVyQ9R4YkzOS2dKSanjC4rTEDGjlMFwGTCUPW3N3pbY7HJIW81wMyg==}
engines: {node: '>=16', npm: '>=8'}
- '@supabase/postgrest-js@2.97.1-canary.3':
- resolution: {integrity: sha512-M8IVst0FYK7LunTyVDorT2PVn9R9p1u0dBAsqOUxhZDdhvUiMrU2hq8xlUK2F6TEMN2YHLjCC3tFm9rfg9R54g==}
+ '@supabase/postgrest-js@2.98.0':
+ resolution: {integrity: sha512-v6e9WeZuJijzUut8HyXu6gMqWFepIbaeaMIm1uKzei4yLg9bC9OtEW9O14LE/9ezqNbSAnSLO5GtOLFdm7Bpkg==}
engines: {node: '>=20.0.0'}
- '@supabase/realtime-js@2.97.1-canary.3':
- resolution: {integrity: sha512-jZ+UK2M7aQbXze5p+IAt02jLZdDDCP981WdrhoUnQR536WdY385tRqAMoKgl21xCpozAo3maO4VraOhagipUtg==}
+ '@supabase/realtime-js@2.98.0':
+ resolution: {integrity: sha512-rOWt28uGyFipWOSd+n0WVMr9kUXiWaa7J4hvyLCIHjRFqWm1z9CaaKAoYyfYMC1Exn3WT8WePCgiVhlAtWC2yw==}
engines: {node: '>=20.0.0'}
'@supabase/shared-types@0.1.84':
@@ -8253,8 +8253,8 @@ packages:
peerDependencies:
'@supabase/supabase-js': ^2.43.4
- '@supabase/storage-js@2.97.1-canary.3':
- resolution: {integrity: sha512-2QK8NR8OFtiLmyHWduG/6Gi+6ArJvmEzD5yegLeqbsh0VAoBoW4qUCQyiCNYbua7RPpb72zsaaKQg4wO2y6ZIw==}
+ '@supabase/storage-js@2.98.0':
+ resolution: {integrity: sha512-tzr2mG+v7ILSAZSfZMSL9OPyIH4z1ikgQ8EcQTKfMRz4EwmlFt3UnJaGzSOxyvF5b+fc9So7qdSUWTqGgeLokQ==}
engines: {node: '>=20.0.0'}
'@supabase/supa-mdx-lint-darwin@0.2.6-alpha':
@@ -8392,8 +8392,8 @@ packages:
resolution: {integrity: sha512-iqJHDk/ToyxFMa/um9A5gsp9jYN7iI0cIabNq9nyBpK/Yat/J9I2IIMueQwIMy3TsG6a2qdPyUkZkuPC37SdRg==}
hasBin: true
- '@supabase/supabase-js@2.97.1-canary.3':
- resolution: {integrity: sha512-fpZT3obVxsNvjdBEYeGqcGDaF9XsNYuGN9bKFt4Ooqwhe97y9tpf72sswqiRzuFRmgxmZ1dFnAMiFveTeoQDwQ==}
+ '@supabase/supabase-js@2.98.0':
+ resolution: {integrity: sha512-Ohc97CtInLwZyiSASz7tT9/Abm/vqnIbO9REp+PivVUII8UZsuI3bngRQnYgJdFoOIwvaEII1fX1qy8x0CyNiw==}
engines: {node: '>=20.0.0'}
'@swc/helpers@0.5.15':
@@ -25542,11 +25542,11 @@ snapshots:
'@stripe/stripe-js@7.5.0': {}
- '@supabase/auth-js@2.97.1-canary.3':
+ '@supabase/auth-js@2.98.0':
dependencies:
tslib: 2.8.1
- '@supabase/functions-js@2.97.1-canary.3':
+ '@supabase/functions-js@2.98.0':
dependencies:
tslib: 2.8.1
@@ -25581,11 +25581,11 @@ snapshots:
- pg-native
- supports-color
- '@supabase/postgrest-js@2.97.1-canary.3':
+ '@supabase/postgrest-js@2.98.0':
dependencies:
tslib: 2.8.1
- '@supabase/realtime-js@2.97.1-canary.3':
+ '@supabase/realtime-js@2.98.0':
dependencies:
'@types/phoenix': 1.6.6
'@types/ws': 8.18.1
@@ -25606,12 +25606,12 @@ snapshots:
- encoding
- supports-color
- '@supabase/ssr@0.7.0(@supabase/supabase-js@2.97.1-canary.3)':
+ '@supabase/ssr@0.7.0(@supabase/supabase-js@2.98.0)':
dependencies:
- '@supabase/supabase-js': 2.97.1-canary.3
+ '@supabase/supabase-js': 2.98.0
cookie: 1.0.2
- '@supabase/storage-js@2.97.1-canary.3':
+ '@supabase/storage-js@2.98.0':
dependencies:
iceberg-js: 0.8.1
tslib: 2.8.1
@@ -25712,13 +25712,13 @@ snapshots:
'@supabase/supa-mdx-lint-win32-x64': 0.3.2
node-pty: 1.0.0
- '@supabase/supabase-js@2.97.1-canary.3':
+ '@supabase/supabase-js@2.98.0':
dependencies:
- '@supabase/auth-js': 2.97.1-canary.3
- '@supabase/functions-js': 2.97.1-canary.3
- '@supabase/postgrest-js': 2.97.1-canary.3
- '@supabase/realtime-js': 2.97.1-canary.3
- '@supabase/storage-js': 2.97.1-canary.3
+ '@supabase/auth-js': 2.98.0
+ '@supabase/functions-js': 2.98.0
+ '@supabase/postgrest-js': 2.98.0
+ '@supabase/realtime-js': 2.98.0
+ '@supabase/storage-js': 2.98.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 92e2730fc4334..1f0deece909f6 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -6,10 +6,10 @@ packages:
catalog:
'@sentry/nextjs': ^10.26.0
- '@supabase/auth-js': 2.97.1-canary.3
- '@supabase/postgrest-js': 2.97.1-canary.3
- '@supabase/realtime-js': 2.97.1-canary.3
- '@supabase/supabase-js': 2.97.1-canary.3
+ '@supabase/auth-js': 2.98.0
+ '@supabase/postgrest-js': 2.98.0
+ '@supabase/realtime-js': 2.98.0
+ '@supabase/supabase-js': 2.98.0
'@types/node': ^22.0.0
'@types/react': ^18.3.0
'@types/react-dom': ^18.3.0