mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
update branch
This commit is contained in:
28 files changed
+254
-393
No files matched your search
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { PropsWithChildren, ReactNode } from 'react'
|
||||
import { cn, Menu } from 'ui'
|
||||
|
||||
import { NavUser } from '../NavigationV2/NavUser'
|
||||
import type { SidebarSection } from './AccountLayout.types'
|
||||
import { useAppStateSnapshot } from '@/state/app-state'
|
||||
|
||||
@@ -64,79 +65,78 @@ export const SidebarContent = ({
|
||||
className,
|
||||
}: PropsWithChildren<Omit<WithSidebarProps, 'breadcrumbs'>> & { className?: string }) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="with-sidebar"
|
||||
className={cn(
|
||||
'h-full bg-dash-sidebar flex flex-col justify-between',
|
||||
'hide-scrollbar w-full md:w-64 md:border-r border-default',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 flex flex-col">
|
||||
{backToDashboardURL && (
|
||||
<div className="flex-shrink-0 hidden md:block">
|
||||
<div className="flex h-12 max-h-12 items-center border-b px-6 border-default">
|
||||
<Link
|
||||
href={backToDashboardURL}
|
||||
className="flex text-sm flex-row gap-2 items-center text-foreground-lighter focus-visible:text-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeft strokeWidth={1.5} size={16} />
|
||||
Back to dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{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
|
||||
id="with-sidebar"
|
||||
className={cn(
|
||||
'h-full bg-dash-sidebar flex flex-col justify-between',
|
||||
'hide-scrollbar w-full md:w-64 md:border-r border-default',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 flex flex-col">
|
||||
{backToDashboardURL && (
|
||||
<div className="flex-shrink-0 hidden md:block">
|
||||
<div className="flex h-12 max-h-12 items-center border-b px-6 border-default">
|
||||
<Link
|
||||
href={backToDashboardURL}
|
||||
className="flex text-sm flex-row gap-2 items-center text-foreground-lighter focus-visible:text-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeft strokeWidth={1.5} size={16} />
|
||||
Back to dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{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>
|
||||
</div>
|
||||
<footer className="p-2">
|
||||
<NavUser />
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { useRouter } from 'next/router'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { AdvisorsSidebarMenu } from './AdvisorsSidebarMenu'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { useRouter } from 'next/router'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { useGenerateAuthMenu } from './AuthLayout.utils'
|
||||
import { ProductMenu } from '@/components/ui/ProductMenu'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { useRouter } from 'next/router'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { useGenerateDatabaseMenu } from './DatabaseMenu.utils'
|
||||
import { ProductMenu } from '@/components/ui/ProductMenu'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ResizablePanel, ResizablePanelGroup } from 'ui'
|
||||
import { DefaultLayoutProviders } from './DefaultLayoutProviders'
|
||||
import { LayoutHeader } from './Navigation/LayoutHeader/LayoutHeader'
|
||||
import MobileNavigationBar from './Navigation/NavigationBar/MobileNavigationBar'
|
||||
import { DefaultLayoutV2 } from './NavigationV2/DefaultLayoutV2'
|
||||
import { DefaultLayoutV2 } from './NavigationV2/DefaultLayout'
|
||||
import { LayoutSidebar } from './ProjectLayout/LayoutSidebar'
|
||||
import { AppBannerWrapper } from '@/components/interfaces/App/AppBannerWrapper'
|
||||
import { Sidebar } from '@/components/interfaces/Sidebar'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { useRouter } from 'next/router'
|
||||
import type { ComponentProps, PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { ProductMenu } from '@/components/ui/ProductMenu'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
|
||||
import { ProjectLayoutV2 } from 'components/layouts/NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from 'components/layouts/NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from 'components/layouts/ProjectLayout'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { ProductMenu } from 'components/ui/ProductMenu'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { LogsSidebarMenuV2 } from './LogsSidebarMenuV2'
|
||||
import NoPermission from '@/components/ui/NoPermission'
|
||||
|
||||
@@ -36,7 +36,7 @@ import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganizati
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
|
||||
const LayoutHeaderDivider = ({ className, ...props }: React.HTMLProps<HTMLSpanElement>) => (
|
||||
export const LayoutHeaderDivider = ({ className, ...props }: React.HTMLProps<HTMLSpanElement>) => (
|
||||
<span className={cn('text-border-stronger pr-2', className)} {...props}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -57,13 +57,10 @@ export function OrgSelector() {
|
||||
const triggerButton = (
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground gap-2 h-auto text-left group px-1.5 py-1 touch-manipulation"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground gap-2 h-10 text-left group px-1.5 py-1 touch-manipulation"
|
||||
onClick={isMobile ? () => setOpen(true) : undefined}
|
||||
>
|
||||
<span className="flex w-8 aspect-square shrink-0 items-center justify-center rounded border bg-surface-100 text-xs font-medium text-foreground-lighter">
|
||||
{selectedOrgInitial}
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-1 flex-col text-left -mb-0.5">
|
||||
<div className="flex min-w-0 flex-1 flex-col text-left pl-0.5">
|
||||
<div className="truncate text-foreground font-medium leading-tight min-w-[100px] max-w-[250px]">
|
||||
{selectedOrganization?.name ?? 'Select organization'}
|
||||
</div>
|
||||
|
||||
@@ -44,12 +44,11 @@ export function ProjectBranchSelector() {
|
||||
)
|
||||
|
||||
const selectedBranch = branches?.find((b) => b.project_ref === ref)
|
||||
const { isMainBranch, branchDisplayName, selectedOrgInitial, organizationHref } =
|
||||
getProjectBranchSelectorState({
|
||||
selectedBranch,
|
||||
isBranchingEnabled,
|
||||
selectedOrganization: selectedOrganization ?? undefined,
|
||||
})
|
||||
const { isMainBranch, branchDisplayName, organizationHref } = getProjectBranchSelectorState({
|
||||
selectedBranch,
|
||||
isBranchingEnabled,
|
||||
selectedOrganization: selectedOrganization ?? undefined,
|
||||
})
|
||||
|
||||
const goToOrganization = () => {
|
||||
setOpen(false)
|
||||
@@ -75,7 +74,7 @@ export function ProjectBranchSelector() {
|
||||
|
||||
const triggerProps = {
|
||||
displayProjectName: displayProject.name,
|
||||
selectedOrgInitial,
|
||||
selectedOrg: selectedOrganization,
|
||||
isBranch,
|
||||
isProductionBranch,
|
||||
branchDisplayName,
|
||||
|
||||
+4
-3
@@ -5,7 +5,6 @@ export interface ProjectBranchSelectorState {
|
||||
selectedBranch: Branch | undefined
|
||||
isMainBranch: boolean
|
||||
branchDisplayName: string
|
||||
selectedOrgInitial: string
|
||||
organizationHref: string
|
||||
}
|
||||
|
||||
@@ -18,7 +17,6 @@ export function getProjectBranchSelectorState(params: {
|
||||
|
||||
const isMainBranch = !isBranchingEnabled || selectedBranch?.is_default === true
|
||||
const branchDisplayName = isBranchingEnabled ? (selectedBranch?.name ?? 'main') : 'main'
|
||||
const selectedOrgInitial = selectedOrganization?.name?.trim().charAt(0).toUpperCase() ?? 'O'
|
||||
const organizationHref = selectedOrganization?.slug
|
||||
? `/org/${selectedOrganization.slug}`
|
||||
: '/organizations'
|
||||
@@ -27,7 +25,10 @@ export function getProjectBranchSelectorState(params: {
|
||||
selectedBranch,
|
||||
isMainBranch,
|
||||
branchDisplayName,
|
||||
selectedOrgInitial,
|
||||
organizationHref,
|
||||
}
|
||||
}
|
||||
|
||||
export function getSelectedOrgInitial(orgName: string): string {
|
||||
return orgName.trim().charAt(0).toUpperCase()
|
||||
}
|
||||
+4
-2
@@ -17,8 +17,6 @@ import { useEffect, useState } from 'react'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import type { Organization } from 'types'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Command_Shadcn_,
|
||||
CommandEmpty_Shadcn_,
|
||||
CommandGroup_Shadcn_,
|
||||
@@ -113,6 +111,7 @@ export function ProjectBranchSelectorPopover({ onClose }: ProjectBranchSelectorP
|
||||
selectedBranch={selectedBranch}
|
||||
isBranchingEnabled={isBranchingEnabled}
|
||||
isBranchesLoaded={isBranchesSuccess}
|
||||
projectRef={parentRef ?? ref ?? ''}
|
||||
onSelect={(branch) => {
|
||||
const sanitizedRoute = sanitizeRoute(router.route, router.query)
|
||||
const href =
|
||||
@@ -238,6 +237,7 @@ function BranchColumn({
|
||||
selectedBranch,
|
||||
isBranchingEnabled,
|
||||
isBranchesLoaded,
|
||||
projectRef,
|
||||
onSelect,
|
||||
onCreateBranch,
|
||||
onManageBranches,
|
||||
@@ -247,6 +247,7 @@ function BranchColumn({
|
||||
selectedBranch?: Branch
|
||||
isBranchingEnabled: boolean
|
||||
isBranchesLoaded: boolean
|
||||
projectRef: string
|
||||
onSelect: (branch: Branch) => void
|
||||
onCreateBranch: () => void
|
||||
onManageBranches: () => void
|
||||
@@ -301,6 +302,7 @@ function BranchColumn({
|
||||
</>
|
||||
)}
|
||||
</CommandList_Shadcn_>
|
||||
<CommandSeparator_Shadcn_ />
|
||||
<CommandGroup_Shadcn_ className="space-y-0.5">
|
||||
<CommandItem_Shadcn_ className="cursor-pointer w-full" onSelect={() => onClose()}>
|
||||
<Link
|
||||
|
||||
+48
-41
@@ -1,12 +1,19 @@
|
||||
import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { ChevronsUpDown, GitBranch } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
import { forwardRef } from 'react'
|
||||
import { cn, SidebarMenuButton as SidebarMenuButtonComponent } from 'ui'
|
||||
import {
|
||||
cn,
|
||||
SidebarMenuButton as SidebarMenuButtonComponent,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { getSelectedOrgInitial } from './ProjectBranchSelector.utils'
|
||||
import type { Organization } from '@/types'
|
||||
|
||||
export interface ProjectBranchSelectorTriggerProps {
|
||||
displayProjectName: string
|
||||
selectedOrgInitial: string
|
||||
selectedOrg: Organization
|
||||
isBranch: boolean
|
||||
isProductionBranch: boolean
|
||||
branchDisplayName: string
|
||||
@@ -20,46 +27,46 @@ export const ProjectBranchSelectorTrigger = React.forwardRef<
|
||||
React.ComponentPropsWithoutRef<typeof SidebarMenuButtonComponent>,
|
||||
keyof ProjectBranchSelectorTriggerProps
|
||||
>
|
||||
>(
|
||||
(
|
||||
{ displayProjectName, selectedOrgInitial, isBranch, branchDisplayName, ...buttonProps },
|
||||
ref
|
||||
) => {
|
||||
const isNavigationV2 = useIsNavigationV2Enabled()
|
||||
>(({ displayProjectName, selectedOrg, isBranch, branchDisplayName, ...buttonProps }, ref) => {
|
||||
const selectedOrgInitial = getSelectedOrgInitial(selectedOrg?.name ?? 'O')
|
||||
|
||||
return (
|
||||
<SidebarMenuButtonComponent
|
||||
ref={ref}
|
||||
size="lg"
|
||||
className="group py-1 gap-1.5 w-full flex h-auto text-left data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground touch-manipulation"
|
||||
{...buttonProps}
|
||||
>
|
||||
<div className="relative flex h-8 aspect-square shrink-0 items-center bg-background-muted group-hover:border-stronger justify-center rounded border border-strong text-xs">
|
||||
{selectedOrgInitial}
|
||||
</div>
|
||||
return (
|
||||
<SidebarMenuButtonComponent
|
||||
ref={ref}
|
||||
size="lg"
|
||||
className="group py-1 gap-1.5 w-full flex h-auto text-left data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground touch-manipulation"
|
||||
{...buttonProps}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="relative flex h-8 aspect-square shrink-0 items-center bg-background-muted group-hover:border-stronger justify-center rounded border border-strong text-xs">
|
||||
{selectedOrgInitial}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{selectedOrg?.name}</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="text-left flex-grow min-w-0">
|
||||
<div className="text-left flex-grow min-w-0">
|
||||
<div className="text-left flex-grow min-w-0">
|
||||
<div className="w-full truncate text-foreground leading-tight max-w-[250px]">
|
||||
{displayProjectName}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-0.5',
|
||||
isBranch ? 'text-foreground-lighter' : 'text-warning'
|
||||
)}
|
||||
>
|
||||
<GitBranch className="shrink-0 size-3" strokeWidth={1.5} />
|
||||
<span className="truncate min-w-0 leading-tight text-xs">{branchDisplayName}</span>
|
||||
</div>
|
||||
<div className="w-full truncate text-foreground leading-tight max-w-[250px]">
|
||||
{displayProjectName}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-0.5',
|
||||
isBranch ? 'text-foreground-lighter' : 'text-warning'
|
||||
)}
|
||||
>
|
||||
<GitBranch className="shrink-0 size-3" strokeWidth={1.5} />
|
||||
<span className="truncate min-w-0 leading-tight text-xs">{branchDisplayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronsUpDown
|
||||
strokeWidth={1.5}
|
||||
className="ml-auto text-foreground-lighter !w-4 !h-4 md:hidden md:group-hover:flex"
|
||||
/>
|
||||
</SidebarMenuButtonComponent>
|
||||
)
|
||||
}
|
||||
)
|
||||
<ChevronsUpDown
|
||||
strokeWidth={1.5}
|
||||
className="ml-auto text-foreground-lighter !w-4 !h-4 md:hidden md:group-hover:flex"
|
||||
/>
|
||||
</SidebarMenuButtonComponent>
|
||||
)
|
||||
})
|
||||
ProjectBranchSelectorTrigger.displayName = 'ProjectBranchSelectorTrigger'
|
||||
+4
-6
@@ -1,4 +1,3 @@
|
||||
import { usePHFlag } from 'hooks/ui/useFlag'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { Plug, Search } from 'lucide-react'
|
||||
import { parseAsBoolean, useQueryState } from 'nuqs'
|
||||
@@ -11,6 +10,7 @@ import { NavGroup } from './NavGroup'
|
||||
import { NavUser } from './NavUser'
|
||||
import { useAppSidebarNavItems, type AppSidebarScope } from './useAppSidebarNavItems'
|
||||
import { ConnectSheet } from '@/components/interfaces/ConnectSheet/ConnectSheet'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
|
||||
export type { AppSidebarScope }
|
||||
|
||||
@@ -21,9 +21,7 @@ interface AppSidebarV2Props {
|
||||
export function AppSidebarV2({ scope }: AppSidebarV2Props = {}) {
|
||||
const setCommandMenuOpen = useSetCommandMenuOpen()
|
||||
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
|
||||
const connectSheetFlag = usePHFlag<string | boolean>('connectSheet')
|
||||
const isFlagResolved = connectSheetFlag !== undefined
|
||||
const isConnectSheetEnabled = connectSheetFlag === true || connectSheetFlag === 'variation'
|
||||
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
||||
|
||||
const {
|
||||
isProjectScope,
|
||||
@@ -78,14 +76,14 @@ export function AppSidebarV2({ scope }: AppSidebarV2Props = {}) {
|
||||
<NavGroup id="observability" label="Observability" items={observabilityItems} />
|
||||
<NavGroup id="integrations" label="Integrations" items={integrationsItems} />
|
||||
</>
|
||||
) : (
|
||||
) : selectedOrganization ? (
|
||||
<NavGroup
|
||||
id="organization"
|
||||
label="Organization"
|
||||
items={organizationItems}
|
||||
isCollapsible={false}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</SidebarContent>
|
||||
<div
|
||||
aria-hidden
|
||||
+2
-2
@@ -3,12 +3,12 @@ import { AppBannerWrapper } from 'components/interfaces/App/AppBannerWrapper'
|
||||
import MobileNavigationBar from 'components/layouts/Navigation/NavigationBar/MobileNavigationBar'
|
||||
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
|
||||
import { useRouter } from 'next/router'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui'
|
||||
|
||||
import { DefaultLayoutProviders } from '../DefaultLayoutProviders'
|
||||
import { AppSidebarV2 } from './AppSidebarV2'
|
||||
import { AppSidebarV2 } from './AppSidebar'
|
||||
import { RightRailLayout } from './RightIconRail'
|
||||
|
||||
export interface DefaultLayoutV2Props {
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useFeaturePreviewModal } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { UserDropdownContent } from 'components/interfaces/UserDropdown'
|
||||
import { ProfileImage } from 'components/ui/ProfileImage'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useProfileNameAndPicture } from 'lib/profile'
|
||||
import { ChevronsUpDown, Loader2 } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,96 @@
|
||||
import { mergeRefs } from 'common'
|
||||
import { CreateBranchModal } from 'components/interfaces/BranchManagement/CreateBranchModal'
|
||||
import { ProjectAPIDocs } from 'components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
|
||||
import { ResourceExhaustionWarningBanner } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner'
|
||||
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { forwardRef, type PropsWithChildren } from 'react'
|
||||
|
||||
import { useSetMainScrollContainer } from '../MainScrollContainerContext'
|
||||
import { ContentWrapper } from '../ProjectLayout'
|
||||
import { ProjectPausedState } from '../ProjectLayout/PausedState/ProjectPausedState'
|
||||
|
||||
export interface ProjectLayoutV2Props {
|
||||
title?: string
|
||||
isLoading?: boolean
|
||||
isBlocking?: boolean
|
||||
product?: string
|
||||
selectedTable?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Project layout - renders content directly without a secondary product menu sidebar.
|
||||
* In V2, all navigation is in the primary sidebar (AppSidebar), so this layout
|
||||
* only handles title, loading states, and content wrapping.
|
||||
*/
|
||||
export const ProjectLayoutV2 = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayoutV2Props>>(
|
||||
({ title, isLoading = false, isBlocking = true, product = '', children, selectedTable }, ref) => {
|
||||
const router = useRouter()
|
||||
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
||||
const { data: selectedProject } = useSelectedProjectQuery()
|
||||
|
||||
const setMainScrollContainer = useSetMainScrollContainer()
|
||||
const combinedRef = mergeRefs(ref, setMainScrollContainer)
|
||||
|
||||
const { appTitle } = useCustomContent(['app:title'])
|
||||
const titleSuffix = appTitle || 'Supabase'
|
||||
|
||||
const projectName = selectedProject?.name
|
||||
const organizationName = selectedOrganization?.name
|
||||
|
||||
const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE
|
||||
|
||||
const ignorePausedState =
|
||||
router.pathname === '/project/[ref]' ||
|
||||
router.pathname.includes('/project/[ref]/settings') ||
|
||||
router.pathname.includes('/project/[ref]/functions')
|
||||
const showPausedState = isPaused && !ignorePausedState
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{title
|
||||
? `${title} | ${titleSuffix}`
|
||||
: selectedTable
|
||||
? `${selectedTable} | ${projectName} | ${organizationName} | ${titleSuffix}`
|
||||
: projectName
|
||||
? `${projectName} | ${organizationName} | ${titleSuffix}`
|
||||
: organizationName
|
||||
? `${organizationName} | ${titleSuffix}`
|
||||
: titleSuffix}
|
||||
</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
</Head>
|
||||
<main
|
||||
className="@container flex h-full min-h-0 w-full flex-1 flex-col overflow-x-hidden overflow-y-auto"
|
||||
ref={combinedRef}
|
||||
>
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ContentWrapper isLoading={isLoading} isBlocking={isBlocking}>
|
||||
<ResourceExhaustionWarningBanner />
|
||||
{children}
|
||||
</ContentWrapper>
|
||||
)}
|
||||
</main>
|
||||
<CreateBranchModal />
|
||||
<ProjectAPIDocs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ProjectLayoutV2.displayName = 'ProjectLayoutV2'
|
||||
|
||||
export const ProjectLayoutV2WithAuth = withAuth(ProjectLayoutV2)
|
||||
@@ -1,235 +0,0 @@
|
||||
import { mergeRefs, useParams } from 'common'
|
||||
import { CreateBranchModal } from 'components/interfaces/BranchManagement/CreateBranchModal'
|
||||
import { ProjectAPIDocs } from 'components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
|
||||
import { ResourceExhaustionWarningBanner } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner'
|
||||
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { usePHFlag } from 'hooks/ui/useFlag'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { forwardRef, Fragment, PropsWithChildren, useEffect } from 'react'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { LogoLoader } from 'ui'
|
||||
|
||||
import { useSetMainScrollContainer } from '../MainScrollContainerContext'
|
||||
import BuildingState from '../ProjectLayout/BuildingState'
|
||||
import ConnectingState from '../ProjectLayout/ConnectingState'
|
||||
import { LoadingState } from '../ProjectLayout/LoadingState'
|
||||
import { ProjectPausedState } from '../ProjectLayout/PausedState/ProjectPausedState'
|
||||
import { PauseFailedState } from '../ProjectLayout/PauseFailedState'
|
||||
import { PausingState } from '../ProjectLayout/PausingState'
|
||||
import { ResizingState } from '../ProjectLayout/ResizingState'
|
||||
import RestartingState from '../ProjectLayout/RestartingState'
|
||||
import { RestoreFailedState } from '../ProjectLayout/RestoreFailedState'
|
||||
import { RestoringState } from '../ProjectLayout/RestoringState'
|
||||
import { UpgradingState } from '../ProjectLayout/UpgradingState'
|
||||
|
||||
const routesToIgnoreProjectDetailsRequest = [
|
||||
'/project/[ref]/settings/general',
|
||||
'/project/[ref]/database/settings',
|
||||
'/project/[ref]/storage/settings',
|
||||
'/project/[ref]/settings/infrastructure',
|
||||
'/project/[ref]/settings/addons',
|
||||
]
|
||||
|
||||
const routesToIgnoreDBConnection = [
|
||||
'/project/[ref]/branches',
|
||||
'/project/[ref]/database/backups/scheduled',
|
||||
'/project/[ref]/database/backups/pitr',
|
||||
'/project/[ref]/settings/addons',
|
||||
'/project/[ref]/functions',
|
||||
]
|
||||
|
||||
const routesToIgnorePostgrestConnection = [
|
||||
'/project/[ref]/reports',
|
||||
'/project/[ref]/settings/general',
|
||||
'/project/[ref]/database/settings',
|
||||
'/project/[ref]/settings/infrastructure',
|
||||
'/project/[ref]/settings/addons',
|
||||
]
|
||||
|
||||
export interface ProjectLayoutV2Props {
|
||||
title?: string
|
||||
isLoading?: boolean
|
||||
isBlocking?: boolean
|
||||
product?: string
|
||||
selectedTable?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Project layout - renders content directly without a secondary product menu sidebar.
|
||||
* In V2, all navigation is in the primary sidebar (AppSidebarV2), so this layout
|
||||
* only handles title, loading states, and content wrapping.
|
||||
*/
|
||||
export const ProjectLayoutV2 = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayoutV2Props>>(
|
||||
({ title, isLoading = false, isBlocking = true, product = '', children, selectedTable }, ref) => {
|
||||
const router = useRouter()
|
||||
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
||||
const { data: selectedProject } = useSelectedProjectQuery()
|
||||
|
||||
const setMainScrollContainer = useSetMainScrollContainer()
|
||||
const combinedRef = mergeRefs(ref, setMainScrollContainer)
|
||||
|
||||
const { appTitle } = useCustomContent(['app:title'])
|
||||
const titleSuffix = appTitle || 'Supabase'
|
||||
|
||||
const projectName = selectedProject?.name
|
||||
const organizationName = selectedOrganization?.name
|
||||
|
||||
const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE
|
||||
|
||||
const ignorePausedState =
|
||||
router.pathname === '/project/[ref]' ||
|
||||
router.pathname.includes('/project/[ref]/settings') ||
|
||||
router.pathname.includes('/project/[ref]/functions')
|
||||
const showPausedState = isPaused && !ignorePausedState
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{title
|
||||
? `${title} | ${titleSuffix}`
|
||||
: selectedTable
|
||||
? `${selectedTable} | ${projectName} | ${organizationName} | ${titleSuffix}`
|
||||
: projectName
|
||||
? `${projectName} | ${organizationName} | ${titleSuffix}`
|
||||
: organizationName
|
||||
? `${organizationName} | ${titleSuffix}`
|
||||
: titleSuffix}
|
||||
</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
</Head>
|
||||
<main
|
||||
className="@container flex h-full min-h-0 w-full flex-1 flex-col overflow-x-hidden overflow-y-auto"
|
||||
ref={combinedRef}
|
||||
>
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ContentWrapper isLoading={isLoading} isBlocking={isBlocking}>
|
||||
<ResourceExhaustionWarningBanner />
|
||||
{children}
|
||||
</ContentWrapper>
|
||||
)}
|
||||
</main>
|
||||
<CreateBranchModal />
|
||||
<ProjectAPIDocs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ProjectLayoutV2.displayName = 'ProjectLayoutV2'
|
||||
|
||||
export const ProjectLayoutV2WithAuth = withAuth(ProjectLayoutV2)
|
||||
|
||||
interface ContentWrapperProps {
|
||||
isLoading: boolean
|
||||
isBlocking?: boolean
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapperProps) => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const { data: selectedProject } = useSelectedProjectQuery()
|
||||
const isHomeNew = usePHFlag('homeNew') === 'new-home'
|
||||
|
||||
const isBranchesPage = router.pathname.includes('/project/[ref]/branches')
|
||||
const isSettingsPages = router.pathname.includes('/project/[ref]/settings')
|
||||
const isEdgeFunctionPages = router.pathname.includes('/project/[ref]/functions')
|
||||
const isVaultPage = router.pathname === '/project/[ref]/settings/vault'
|
||||
const isBackupsPage = router.pathname.includes('/project/[ref]/database/backups')
|
||||
const isHomePage = router.pathname === '/project/[ref]'
|
||||
|
||||
const requiresDbConnection: boolean =
|
||||
(!isEdgeFunctionPages &&
|
||||
!isSettingsPages &&
|
||||
!routesToIgnoreDBConnection.includes(router.pathname)) ||
|
||||
isVaultPage
|
||||
const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname)
|
||||
const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname)
|
||||
|
||||
const isRestarting = selectedProject?.status === PROJECT_STATUS.RESTARTING
|
||||
const isResizing = selectedProject?.status === PROJECT_STATUS.RESIZING
|
||||
const isProjectUpgrading = selectedProject?.status === PROJECT_STATUS.UPGRADING
|
||||
const isProjectRestoring = selectedProject?.status === PROJECT_STATUS.RESTORING
|
||||
const isProjectRestoreFailed = selectedProject?.status === PROJECT_STATUS.RESTORE_FAILED
|
||||
const isProjectBuilding =
|
||||
selectedProject?.status === PROJECT_STATUS.COMING_UP ||
|
||||
selectedProject?.status === PROJECT_STATUS.UNKNOWN
|
||||
const isProjectPausing = selectedProject?.status === PROJECT_STATUS.PAUSING
|
||||
const isProjectPauseFailed = selectedProject?.status === PROJECT_STATUS.PAUSE_FAILED
|
||||
const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'
|
||||
|
||||
const shouldRedirectToHomeForBuilding =
|
||||
isHomeNew && requiresDbConnection && isProjectBuilding && !isBranchesPage && !isHomePage
|
||||
|
||||
const shouldShowBuildingState =
|
||||
requiresDbConnection && isProjectBuilding && !isBranchesPage && !(isHomeNew && isHomePage)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldRedirectToHomeForBuilding && ref) {
|
||||
router.replace(`/project/${ref}`)
|
||||
}
|
||||
}, [shouldRedirectToHomeForBuilding, ref, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) state.setSelectedDatabaseId(ref)
|
||||
}, [ref])
|
||||
|
||||
if (isBlocking && (isLoading || (requiresProjectDetails && selectedProject === undefined))) {
|
||||
return router.pathname.endsWith('[ref]') ? <LoadingState /> : <LogoLoader />
|
||||
}
|
||||
|
||||
if (isRestarting && !isBackupsPage) {
|
||||
return <RestartingState />
|
||||
}
|
||||
|
||||
if (isResizing && !isBackupsPage) {
|
||||
return <ResizingState />
|
||||
}
|
||||
|
||||
if (isProjectUpgrading && !isBackupsPage) {
|
||||
return <UpgradingState />
|
||||
}
|
||||
|
||||
if (isProjectPausing) {
|
||||
return <PausingState project={selectedProject} />
|
||||
}
|
||||
|
||||
if (isProjectPauseFailed) {
|
||||
return <PauseFailedState />
|
||||
}
|
||||
|
||||
if (requiresPostgrestConnection && isProjectOffline) {
|
||||
return <ConnectingState project={selectedProject} />
|
||||
}
|
||||
|
||||
if (requiresDbConnection && isProjectRestoring) {
|
||||
return <RestoringState />
|
||||
}
|
||||
|
||||
if (isProjectRestoreFailed && !isBackupsPage && !isEdgeFunctionPages) {
|
||||
return <RestoreFailedState />
|
||||
}
|
||||
|
||||
if (shouldRedirectToHomeForBuilding) {
|
||||
return <LogoLoader />
|
||||
}
|
||||
|
||||
if (shouldShowBuildingState) {
|
||||
return <BuildingState />
|
||||
}
|
||||
|
||||
return <Fragment key={selectedProject?.ref}>{children}</Fragment>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export { DefaultLayoutV2 } from './DefaultLayoutV2'
|
||||
export { ProjectLayoutV2, ProjectLayoutV2WithAuth } from './ProjectLayoutV2'
|
||||
export { AppSidebarV2 } from './AppSidebarV2'
|
||||
export { DefaultLayoutV2 } from './DefaultLayout'
|
||||
export { ProjectLayoutV2, ProjectLayoutV2WithAuth } from './ProjectLayout'
|
||||
export { AppSidebarV2 } from './AppSidebar'
|
||||
export { NavGroup } from './NavGroup'
|
||||
export type { NavGroupItem, NavGroupProps } from './NavGroup'
|
||||
export { RightRailLayout } from './RightIconRail'
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
generateDatabaseNavItems,
|
||||
generateObservabilityNavItems,
|
||||
generatePlatformNavItems,
|
||||
} from './NavigationV2.utils'
|
||||
} from './Navigation.utils'
|
||||
|
||||
export type AppSidebarScope = 'project' | 'organization'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useEffect, useRef, type PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import ObservabilityMenu from './ObservabilityMenu'
|
||||
import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
|
||||
|
||||
@@ -318,7 +318,7 @@ const MenuBarWrapper = ({
|
||||
return !isLoading && productMenu && showMenuBar ? children : null
|
||||
}
|
||||
|
||||
interface ContentWrapperProps {
|
||||
export interface ContentWrapperProps {
|
||||
isLoading: boolean
|
||||
isBlocking?: boolean
|
||||
children: ReactNode
|
||||
@@ -336,7 +336,7 @@ interface ContentWrapperProps {
|
||||
*
|
||||
* [TODO] Next iteration should scrape long polling and just listen to the project's status
|
||||
*/
|
||||
const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapperProps) => {
|
||||
export const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapperProps) => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { useGenerateSettingsMenu } from './SettingsMenu.utils'
|
||||
import { useIsNavigationV2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useIsNavigationV2Enabled } from 'components/interfaces/App/FeaturePrevi
|
||||
import { useRouter } from 'next/router'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { generateRealtimeMenu } from './RealtimeMenu.utils'
|
||||
import { ProductMenu } from '@/components/ui/ProductMenu'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayoutV2'
|
||||
import { ProjectLayoutV2 } from '../NavigationV2/ProjectLayout'
|
||||
import { ProjectLayout } from '../ProjectLayout'
|
||||
import { useIsNavigationV2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { StorageMenuV2 } from '@/components/interfaces/Storage/StorageMenuV2'
|
||||
|
||||
@@ -113,4 +113,4 @@ const ResizableHandle = ({
|
||||
const useDefaultLayout = ResizablePrimitive.useDefaultLayout
|
||||
const usePanelRef = ResizablePrimitive.usePanelRef
|
||||
|
||||
export { ResizableHandle, ResizablePanel, ResizablePanelGroup, type, useDefaultLayout, usePanelRef }
|
||||
export { ResizableHandle, ResizablePanel, ResizablePanelGroup, useDefaultLayout, usePanelRef }
|
||||
Reference in new issue
Block a user