This commit is contained in:
Jonathan Summers-Muir committed 2024-12-18 19:18:00 +08:00
1 parent 80abc1b5da
commit e76f0d8066
29 files changed
+970 -291

No files matched your search

@@ -27,10 +27,23 @@ const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
}, [resolvedTheme, monaco])
return (
<div className="min-h-full flex flex-col">
<div className="flex-none">
{ongoingIncident && <IncidentBanner />}
{showNoticeBanner && <NoticeBanner />}
<div className="flex flex-col">
<div className="flex-shrink-0">
{
// ongoingIncident
true && (
//
<IncidentBanner />
)
}
{/* {
//showNoticeBanner
//
true && (
//
<NoticeBanner />
)
} */}
{profile !== undefined && <RestrictionBanner />}
</div>
{children}
@@ -45,7 +45,7 @@ const TerminalInstructions = forwardRef<
// get the .co or .net TLD from the restUrl
const restUrl = `https://${endpoint}`
const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co'
const restUrlTld = restUrl ? new URL(restUrl).hostname?.split('.').pop() : 'co'
const commands: Commands[] = [
{
@@ -71,6 +71,7 @@ const LintPageTabs = ({
return (
<Tabs_Shadcn_
defaultValue={currentTab}
className=""
onValueChange={(value) => {
setCurrentTab(value as LINTER_LEVELS)
setSelectedLint(null)
@@ -78,15 +79,14 @@ const LintPageTabs = ({
router.push({ ...router, query: { ...rest, preset: value, id: null } })
}}
>
<TabsList_Shadcn_ className={cn('flex gap-0 border-0 items-end z-10 relative')}>
<TabsList_Shadcn_ className={cn('px-5 my-3 flex gap-2 border-0 items-end z-10 relative')}>
{LINT_TABS.map((tab) => (
<TabsTrigger_Shadcn_
key={tab.id}
value={tab.id}
className={cn(
'group relative',
'px-6 py-3 border-b-0 flex flex-col items-start !shadow-none border-default border-t',
'even:border-x last:border-r even:!border-x-strong last:!border-r-strong',
'group relative rounded-full',
'px-2 py-1 flex flex-row items-center gap-3 !shadow-none border border-default',
tab.id === currentTab ? '!bg-surface-200' : '!bg-surface-200/[33%]',
'hover:!bg-surface-100',
'data-[state=active]:!bg-surface-200',
@@ -94,9 +94,9 @@ const LintPageTabs = ({
'transition'
)}
>
{tab.id === currentTab && (
{/* {tab.id === currentTab && (
<div className="absolute top-0 left-0 w-full h-[1px] bg-foreground" />
)}
)} */}
<div className="flex items-center gap-x-2">
<span
className={
@@ -111,14 +111,14 @@ const LintPageTabs = ({
</span>
<span className="">{tab.label}</span>
<Tooltip_Shadcn_>
<TooltipTrigger_Shadcn_ asChild>
<InformationCircleIcon className="transition text-foreground-muted w-3 h-3 data-[state=delayed-open]:text-foreground-light" />
</TooltipTrigger_Shadcn_>
<TooltipContent_Shadcn_ side="top">{tab.description}</TooltipContent_Shadcn_>
</Tooltip_Shadcn_>
</div>
<LintCountLabel tab={tab} />
<Tooltip_Shadcn_>
<TooltipTrigger_Shadcn_ asChild>
<InformationCircleIcon className="transition text-foreground-muted w-3 h-3 data-[state=delayed-open]:text-foreground-light" />
</TooltipTrigger_Shadcn_>
<TooltipContent_Shadcn_ side="top">{tab.description}</TooltipContent_Shadcn_>
</Tooltip_Shadcn_>
</TabsTrigger_Shadcn_>
))}
</TabsList_Shadcn_>
@@ -0,0 +1,431 @@
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import { Archive, ArrowDown, ArrowUp, Loader2, RefreshCw, Search, X } from 'lucide-react'
import { UIEvent, useEffect, useMemo, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import { useParams } from 'common'
import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import AlertError from 'components/ui/AlertError'
import APIDocsButton from 'components/ui/APIDocsButton'
import { FilterPopover } from 'components/ui/FilterPopover'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import {
Button,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
LoadingLine,
ResizablePanel,
ResizablePanelGroup,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { useBucketsQuery } from 'data/storage/buckets-query'
import type { StorageBucket } from 'components/interfaces/Storage/Storage.types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import CreateBucketModal from './CreateBucketModal'
import { formatBytes } from 'lib/helpers'
import Link from 'next/link'
type Filter = 'all' | 'public' | 'private'
export type BucketsTableColumn = {
id: string
name: string
minWidth?: number
width?: number
resizable?: boolean
}
export type ColumnConfiguration = { id: string; width?: number }
export const BUCKETS_TABLE_COLUMNS: BucketsTableColumn[] = [
{ id: 'name', name: 'Name', width: 280 },
{ id: 'public', name: 'Public', width: 100 },
{ id: 'created_at', name: 'Created at', width: 260 },
{ id: 'file_count', name: 'Files', width: 100 },
{ id: 'size', name: 'Size', width: 100 },
{ id: 'owner', name: 'Owner', width: 200 },
]
// [Joshen] Just naming it as V2 as its a rewrite of the old one, to make it easier for reviews
// Can change it to remove V2 thereafter
export const StorageBucketList = () => {
const { ref } = useParams()
const { project } = useProjectContext()
const gridRef = useRef<DataGridHandle>(null)
const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
const canCreateBuckets = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
const [columns, setColumns] = useState<Column<any>[]>([])
const [search, setSearch] = useState('')
const [filter, setFilter] = useState<Filter>('all')
const [filterKeywords, setFilterKeywords] = useState('')
const [selectedColumns, setSelectedColumns] = useState<string[]>([])
const [selectedRow, setSelectedRow] = useState<number>()
const [sortByValue, setSortByValue] = useState<string>('created_at:desc')
const [columnConfiguration, setColumnConfiguration] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.STORAGE_BUCKETS_COLUMN_CONFIG(ref ?? ''),
null as ColumnConfiguration[] | null
)
const [showCreateBucketModal, setShowCreateBucketModal] = useState(false)
const [sortColumn, sortOrder] = sortByValue.split(':')
const { data, error, isLoading, isError, isSuccess, isRefetching, refetch } = useBucketsQuery({
projectRef: ref,
})
const buckets = data ?? []
const sortedBuckets =
sortColumn === 'name'
? buckets.sort(
(a, b) =>
(sortOrder === 'asc' ? 1 : -1) *
a.name.toLowerCase().trim().localeCompare(b.name.toLowerCase().trim())
)
: buckets.sort(
(a, b) =>
(sortOrder === 'asc' ? 1 : -1) *
(new Date(b.created_at) > new Date(a.created_at) ? -1 : 1)
)
const filteredBuckets =
filterKeywords.length > 1
? sortedBuckets.filter((bucket) =>
bucket.name.toLowerCase().includes(filterKeywords.toLowerCase().trim())
)
: sortedBuckets
const handleScroll = (event: UIEvent<HTMLDivElement>) => {
// if (isLoading || !isAtBottom(event)) return
// refetch()
}
const clearSearch = () => {
setSearch('')
setFilterKeywords('')
}
const swapColumns = (data: any[], sourceIdx: number, targetIdx: number) => {
const updatedColumns = data.slice()
const [removed] = updatedColumns.splice(sourceIdx, 1)
updatedColumns.splice(targetIdx, 0, removed)
return updatedColumns
}
// [Joshen] Left off here - it's tricky trying to do both column toggling and re-ordering
const saveColumnConfiguration = AwesomeDebouncePromise(
(event: 'resize' | 'reorder' | 'toggle', value) => {
if (event === 'toggle') {
const columnConfig = value.columns.map((col: any) => ({
id: col.key,
width: col.width,
}))
setColumnConfiguration(columnConfig)
} else if (event === 'resize') {
const columnConfig = columns.map((col, idx) => ({
id: col.key,
width: idx === value.idx ? value.width : col.width,
}))
setColumnConfiguration(columnConfig)
} else if (event === 'reorder') {
const columnConfig = value.columns.map((col: any) => ({
id: col.key,
width: col.width,
}))
setColumnConfiguration(columnConfig)
}
},
500
)
useEffect(() => {
if (!isRefetching) {
const columns = formatBucketColumns({
config: columnConfiguration ?? [],
buckets: buckets ?? [],
visibleColumns: selectedColumns,
setSortByValue,
sortByValue,
ref,
})
setColumns(columns)
if (columns.length < BUCKETS_TABLE_COLUMNS.length) {
setSelectedColumns(columns.map((col) => col.key))
}
}
}, [isSuccess, isRefetching, buckets, columnConfiguration, selectedColumns, sortByValue])
return (
<div className="h-full flex flex-col">
<div className="py-3 px-5 flex items-center justify-between">
<div className="flex items-center gap-x-2">
<Input
size="tiny"
className="w-52 pl-7 bg-transparent"
iconContainerClassName="pl-2"
icon={<Search size={14} className="text-foreground-lighter" />}
placeholder="Search bucket name"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
if (e.code === 'Enter') {
setSearch(search.trim())
setFilterKeywords(search.trim().toLocaleLowerCase())
}
}}
actions={[
search && (
<Button
size="tiny"
type="text"
icon={<X />}
onClick={() => clearSearch()}
className="p-0 h-5 w-5"
/>
),
]}
/>
<Select_Shadcn_ value={filter} onValueChange={(val) => setFilter(val as Filter)}>
<SelectTrigger_Shadcn_
size="tiny"
className={cn('w-[140px] !bg-transparent', filter === 'all' && 'border-dashed')}
>
<SelectValue_Shadcn_ />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value="all">All buckets</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="public">Public buckets</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="private">Private buckets</SelectItem_Shadcn_>
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button icon={sortOrder === 'desc' ? <ArrowDown /> : <ArrowUp />}>
Sorted by {sortColumn.replaceAll('_', ' ')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44" align="start">
<DropdownMenuRadioGroup value={sortByValue} onValueChange={setSortByValue}>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Sort by name</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioItem value="name:asc">Ascending</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="name:desc">Descending</DropdownMenuRadioItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Sort by created at</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioItem value="created_at:asc">Ascending</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="created_at:desc">
Descending
</DropdownMenuRadioItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center gap-2">
{isNewAPIDocsEnabled && <APIDocsButton section={['storage']} />}
<Button
size="tiny"
icon={<RefreshCw />}
type="default"
loading={isRefetching}
onClick={() => refetch()}
>
Refresh
</Button>
<Button
size="tiny"
type="primary"
disabled={!canCreateBuckets}
onClick={() => setShowCreateBucketModal(true)}
>
Create bucket
</Button>
</div>
</div>
<LoadingLine loading={isLoading || isRefetching} />
<ResizablePanelGroup
direction="horizontal"
className="relative flex flex-grow bg-alternative min-h-0"
>
<ResizablePanel defaultSize={1}>
<div className="flex flex-col w-full h-full">
<DataGrid
ref={gridRef}
className="flex-grow border-t-0"
rowHeight={44}
headerRowHeight={36}
columns={columns}
rows={filteredBuckets}
rowClass={(_, idx) => {
const isSelected = idx === selectedRow
return [
`${isSelected ? 'bg-surface-300 dark:bg-surface-300' : 'bg-200'} cursor-pointer`,
'[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-none [&>.rdg-cell]:shadow-none',
'[&>.rdg-cell:first-child>div]:ml-4',
].join(' ')
}}
renderers={{
noRowsFallback: isLoading ? (
<div className="absolute top-14 px-6 w-full">
<GenericSkeletonLoader />
</div>
) : isError ? (
<div className="absolute top-14 px-6 flex flex-col items-center justify-center w-full">
<AlertError subject="Failed to retrieve buckets" error={error} />
</div>
) : (
<div className="absolute top-20 px-6 flex flex-col items-center justify-center w-full gap-y-2">
<Archive className="text-foreground-lighter" strokeWidth={1} />
<div className="text-center">
<p className="text-foreground">
{filter !== 'all' || filterKeywords.length > 0
? 'No buckets found'
: 'No buckets in your project'}
</p>
<p className="text-foreground-light">
{filter !== 'all' || filterKeywords.length > 0
? 'There are currently no buckets based on the filters applied'
: 'There are currently no buckets in your project'}
</p>
</div>
</div>
),
}}
/>
</div>
</ResizablePanel>
</ResizablePanelGroup>
<div className="flex justify-between min-h-9 h-9 overflow-hidden items-center px-6 w-full border-t text-xs text-foreground-light">
{isLoading || isRefetching
? 'Loading buckets...'
: `Total: ${filteredBuckets.length} buckets`}
{(isLoading || isRefetching) && (
<span className="flex items-center gap-2">
<Loader2 size={14} className="animate-spin" /> Loading...
</span>
)}
</div>
<CreateBucketModal
visible={showCreateBucketModal}
onClose={() => setShowCreateBucketModal(false)}
/>
</div>
)
}
const formatBucketColumns = ({
config,
buckets,
visibleColumns,
setSortByValue,
sortByValue,
ref,
}: {
config: ColumnConfiguration[]
buckets: StorageBucket[]
visibleColumns: string[]
setSortByValue: (value: string) => void
sortByValue: string
ref
}) => {
const columnOrder = config.map((c) => c.id) ?? BUCKETS_TABLE_COLUMNS.map((c) => c.id)
let gridColumns = BUCKETS_TABLE_COLUMNS.map((col) => {
const savedConfig = config.find((c) => c.id === col.id)
return {
key: col.id,
name: col.name,
resizable: col.resizable ?? true,
sortable: ['name', 'created_at'].includes(col.id),
draggable: true,
width: savedConfig?.width ?? col.width,
minWidth: col.minWidth ?? 120,
headerCellClass: 'z-50 outline-none !shadow-none',
onSort: () => {
if (col.id === 'name' || col.id === 'created_at') {
const order = sortByValue === `${col.id}:asc` ? 'desc' : 'asc'
setSortByValue(`${col.id}:${order}`)
}
},
renderHeaderCell: () => {
const icon = sortByValue.includes(col.id) ? (
sortByValue.includes('asc') ? (
<ArrowUp size={14} />
) : (
<ArrowDown size={14} />
)
) : null
return (
<div className={`flex items-center gap-2 ${col.id === 'name' ? 'pl-3' : ''}`}>
{col.name}
{icon && <div className="opacity-50">{icon}</div>}
</div>
)
},
renderCell: ({ row }) => {
switch (col.id) {
case 'name':
return (
<Link
href={`/project/${ref}/storage/buckets/${row.name}`}
className="flex items-center gap-2 text-foreground hover:text-foreground-light transition pl-3"
>
{row.name}
</Link>
)
case 'public':
return row.public ? 'Public' : 'Private'
case 'created_at':
return new Date(row.created_at).toLocaleString()
case 'file_count':
return row.file_count ?? 0
case 'size':
return formatBytes(row.size ?? 0)
case 'owner':
return row.owner ?? '-'
default:
return row[col.id]
}
},
}
})
if (columnOrder.length > 0) {
gridColumns = gridColumns
.filter((col) => columnOrder.includes(col.key))
.sort((a: any, b: any) => columnOrder.indexOf(a.key) - columnOrder.indexOf(b.key))
}
return visibleColumns.length === 0
? gridColumns
: gridColumns.filter((col) => visibleColumns.includes(col.key))
}
@@ -21,6 +21,7 @@ import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
import {
Button,
cn,
Collapsible_Shadcn_,
CollapsibleContent_Shadcn_,
CollapsibleTrigger_Shadcn_,
@@ -28,6 +29,8 @@ import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
@@ -39,7 +42,15 @@ import {
} from 'ui'
import { AppDefaultNavigationUsageBanner } from './app-default-navigation-usage-banner'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { Blocks, Boxes, ChevronDown, ChevronRight, CornerLeftUp, SidebarClose } from 'lucide-react'
import {
Blocks,
Boxes,
ChevronDown,
ChevronLeft,
ChevronRight,
CornerLeftUp,
SidebarClose,
} from 'lucide-react'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import Connect from './Connect/Connect'
import { useFlag } from 'hooks/ui/useFlag'
@@ -49,6 +60,8 @@ import { UserDropdown } from './user-dropdown'
export const ICON_SIZE = 18
export const ICON_STROKE_WIDTH = 1.5
const SidebarMotion = motion(Sidebar)
export function AppDefaultNavigation() {
// Function logic goes here
const router = useRouter()
@@ -68,23 +81,35 @@ export function AppDefaultNavigation() {
return (
<>
<Sidebar className="!border-r-0" collapsible="offcanvas">
<SidebarHeader className="px-3">
<div className="flex flex-col gap-1">
{!isProjects && <OrganizationDropdown />}
<AnimatePresence>
{isProjects && (
<motion.div
initial={{ opacity: 0, x: -20, height: 0 }}
animate={{ opacity: 1, x: 0, height: 'auto' }}
exit={{ opacity: 0, x: -20, height: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
className="flex gap-1.5 items-center mb-1"
>
<div className="-ml-5">
<AnimatePresence>
<SidebarMotion
className={cn(
'!border-r-0'
// !open && 'pt-[38px]'
)}
transition={{
delay: 0.4,
duration: 0.4,
}}
collapsible="icon"
>
{open && (
<SidebarHeader className="">
<div className="flex flex-col gap-1">
{!isProjects && <OrganizationDropdown />}
<AnimatePresence>
{isProjects && open && (
<motion.div
initial={{ opacity: 0, x: -20, height: 0 }}
animate={{ opacity: 1, x: 0, height: 'auto' }}
exit={{ opacity: 0, x: -20, height: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
className="flex gap-1.5 items-center mb-1 px-2"
>
{/* <div className="-ml-5">
<Button
onClick={() => setOpen(false)}
className="px-1 justify-start"
@@ -92,108 +117,121 @@ export function AppDefaultNavigation() {
>
<SidebarClose size={12} strokeWidth={1} />
</Button>
</div>
<button
onClick={() => router.push(`/org/${selectedOrg?.slug}`)}
className="group/org-back-button text-foreground-lighter flex items-center gap-1 hover:text-foreground text-xs cursor-pointer"
>
<CornerLeftUp
size={14}
strokeWidth={1}
className="text-forefground-lighter group-hover/org-back-button:text-foreground group-hover/org-back-button:translate-y-[-2px] transition-transform"
/>
Organization
</button>
</div> */}
<button
onClick={() => router.push(`/org/${selectedOrg?.slug}`)}
className="group/org-back-button text-foreground-lighter flex items-center gap-1 hover:text-foreground text-xs cursor-pointer"
>
<CornerLeftUp
size={14}
strokeWidth={1}
className="text-forefground-lighter group-hover/org-back-button:text-foreground group-hover/org-back-button:translate-y-[-2px] transition-transform"
/>
Organization
</button>
</motion.div>
)}
</AnimatePresence>
{/* <UserDropdown /> */}
<AnimatePresence>
{ref && open && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, x: -20, height: 0 }}
animate={{ opacity: 1, x: 0, height: 'auto' }}
exit={{ opacity: 0, x: -20, height: 0 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
>
<ProjectDropdown />
<AppDefaultNavigationUsageBanner />
{selectedProject && isBranchingEnabled && (
<>
<BranchDropdown />
</>
)}
</motion.div>
)}
</AnimatePresence>
</div>
</SidebarHeader>
)}
<AnimatePresence mode="wait">
<SidebarContent className="">
{isProjects ? (
<motion.div
key="project-links"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
<ProjectLinks />
</motion.div>
) : (
<motion.div
key="org-links"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
<OrganizationLinks />
</motion.div>
)}
</AnimatePresence>
{/* <UserDropdown /> */}
</SidebarContent>
</AnimatePresence>
<SidebarFooter>
<SidebarGroup>
<button onClick={() => toggleSidebar()}>
<ChevronLeft
size={14}
strokeWidth={2}
className={cn(
'text-foreground-muted transition-transform',
!open && 'rotate-180'
)}
/>
</button>
</SidebarGroup>
<AnimatePresence>
{ref && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, x: -20, height: 0 }}
animate={{ opacity: 1, x: 0, height: 'auto' }}
exit={{ opacity: 0, x: -20, height: 0 }}
className="flex flex-col"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
duration: 0.15,
duration: 0.12,
ease: 'easeOut',
}}
>
<ProjectDropdown />
<AppDefaultNavigationUsageBanner />
{selectedProject && isBranchingEnabled && (
<>
<BranchDropdown />
</>
)}
{!isBranchingEnabled && <EnableBranchingButton />}
</motion.div>
)}
{connectDialogUpdate && ref && (
<motion.div
className="px-3"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
duration: 0.12,
ease: 'easeOut',
}}
>
<div>
<Connect />
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</SidebarHeader>
<SidebarContent className="px-3">
<AnimatePresence mode="wait">
{isProjects ? (
<motion.div
key="project-links"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.12, ease: 'easeOut' }}
>
<ProjectLinks />
</motion.div>
) : (
<motion.div
key="org-links"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.12, ease: 'easeOut' }}
>
<OrganizationLinks />
</motion.div>
)}
</AnimatePresence>
<Separator />
</SidebarContent>
<SidebarFooter>
<AnimatePresence>
{ref && (
<motion.div
className="flex flex-col"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
duration: 0.12,
ease: 'easeOut',
}}
>
{!isBranchingEnabled && <EnableBranchingButton />}
</motion.div>
)}
{connectDialogUpdate && ref && (
<motion.div
className="px-3"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
duration: 0.12,
ease: 'easeOut',
}}
>
<div>
<Connect />
</div>
</motion.div>
)}
</AnimatePresence>
<UserDropdown />
</SidebarFooter>
</Sidebar>
<UserDropdown />
</SidebarFooter>
</SidebarMotion>
</AnimatePresence>
</>
)
}
@@ -205,23 +243,24 @@ function NavLink({ route, active }: { route: any; active?: boolean }) {
// console.log('route', route)
if (!route.items) {
return (
<SidebarMenuItem key={route.key}>
<SidebarMenuButton
asChild
isActive={active}
className="text-sm [&_svg]:opacity-50"
size={'sm'}
>
<Link href={route.link ?? ''}>
{route.icon}
{route.label}
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)
}
// if (!route.items) {
return (
<SidebarMenuItem key={route.key}>
<SidebarMenuButton
tooltip={route.label}
asChild
isActive={active}
className="text-sm [&_svg]:opacity-50"
size={'sm'}
>
<Link href={route.link ?? ''}>
{route.icon}
{route.label}
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)
// }
return (
<Collapsible_Shadcn_ className="group/collapsible" open={open} onOpenChange={setOpen}>
@@ -271,6 +310,7 @@ function ProjectLinks() {
const router = useRouter()
const { ref } = useParams()
const { project } = useProjectContext()
const { open } = useSidebar()
const activeRoute = router.pathname.split('/')[3]
@@ -300,45 +340,57 @@ function ProjectLinks() {
return (
<SidebarMenu className="gap-[2px]">
<NavLink
key="home"
active={isUndefined(activeRoute) && !isUndefined(router.query.ref)}
route={{
key: 'HOME',
label: 'Project overview',
icon: <Home size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: `/project/${ref}`,
linkElement: <ProjectIndexPageLink projectRef={ref} />,
}}
/>
{toolRoutes.map((route, i) => (
<NavLink key={`tools-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
<Separator className="my-1 bg-border-muted" />
<span className="text-foreground-muted text-xs uppercase font-mono text-[12px] px-1.5 mt-2 tracking-wider">
products
</span>
{productRoutes.map((route, i) => (
<NavLink key={`product-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
<Separator className="my-1 bg-border-muted" />
<span className="text-foreground-muted text-xs uppercase font-mono text-[12px] px-1.5 mt-2 tracking-wider">
develop
</span>
{otherRoutes.map((route, i) => (
<NavLink key={`other-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
{settingsRoutes.map((route, i) => (
<NavLink key={`settings-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
<SidebarGroup
// className={cn(!open && 'pt-0')}
>
<NavLink
key="home"
active={isUndefined(activeRoute) && !isUndefined(router.query.ref)}
route={{
key: 'HOME',
label: 'Project overview',
icon: <Home size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: `/project/${ref}`,
linkElement: <ProjectIndexPageLink projectRef={ref} />,
}}
/>
{toolRoutes.map((route, i) => (
<NavLink key={`tools-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
</SidebarGroup>
{/* <Separator className="my-1 bg-border-muted" /> */}
<SidebarGroup>
<SidebarGroupLabel className="text-foreground-muted text-xs uppercase font-mono text-[12px] tracking-wider">
products
</SidebarGroupLabel>
{productRoutes.map((route, i) => (
<NavLink key={`product-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
</SidebarGroup>
{/* <Separator className="my-1 bg-border-muted" /> */}
<SidebarGroup>
<SidebarGroupLabel className="text-foreground-muted text-xs uppercase font-mono text-[12px] tracking-wider">
develop
</SidebarGroupLabel>
{otherRoutes.map((route, i) => (
<NavLink key={`other-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
</SidebarGroup>
<SidebarGroup>
{settingsRoutes.map((route, i) => (
<NavLink key={`settings-routes-${i}`} route={route} active={activeRoute === route.key} />
))}
</SidebarGroup>
</SidebarMenu>
)
}
const OrganizationLinks = () => {
const router = useRouter()
const activeRoute = router.pathname.split('/')[3]
const { slug } = useParams()
const { open } = useSidebar()
const activeRoute = router.pathname.split('/')[3]
const navMenuItems = [
{
@@ -404,24 +456,25 @@ const OrganizationLinks = () => {
},
]
// const filteredNavMenuItems = navMenuItems.filter((item) => !item.hidden)
// console.log(activeRoute)
return (
<ul className="flex flex-col gap-1 items-start">
{navMenuItems.map((item) => (
<NavLink
key={item.label}
active={activeRoute === item.key}
route={{
label: item.label,
link: item.href,
key: item.label,
icon: item.icon,
}}
/>
))}
</ul>
<SidebarMenu className="flex flex-col gap-1 items-start">
<SidebarGroup
// className={cn(!open && 'pt-0')}
>
{navMenuItems.map((item, i) => (
<SidebarMenuItem key={`${item.label}-${i}`}>
<NavLink
active={i === 0 ? activeRoute === undefined : activeRoute === item.key}
route={{
label: item.label,
link: item.href,
key: item.label,
icon: item.icon,
}}
/>
</SidebarMenuItem>
))}
</SidebarGroup>
</SidebarMenu>
)
}
@@ -1,14 +1,24 @@
import { AnimatePresence, motion } from 'framer-motion'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { Box, Boxes } from 'lucide-react'
import { Box, Boxes, CornerLeftUp } from 'lucide-react'
import { useSidebar } from 'ui'
import { SidebarOpenButton } from './sidebar-open-button'
import { useRouter } from 'next/router'
import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown'
import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown'
import { FeedbackDropdown } from 'components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown'
import NotificationsPopoverV2 from 'components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsPopover'
import HelpPopover from 'components/layouts/ProjectLayout/LayoutHeader/HelpPopover'
import { UserDropdown } from './user-dropdown'
import { IS_PLATFORM } from 'lib/constants'
export function SidebarOpenTopBanner() {
const { state, setOpen, open } = useSidebar()
const router = useRouter()
const project = useSelectedProject()
const org = useSelectedOrganization()
return (
<AnimatePresence>
{state === 'collapsed' && (
@@ -26,18 +36,50 @@ export function SidebarOpenTopBanner() {
height: 0,
transition: { duration: 0.05 },
}}
className="relative items-center flex w-full h-5 mt-0.5 gap-5 px-0.5"
style={{
paddingLeft: open ? '16px' : '8px',
paddingRight: open ? '16px' : '8px',
}}
className="relative items-center flex w-full gap-5 pt-[6px] pb-[1px]"
>
<div className="relative items-center flex w-full h-5 mt-0.5 gap-5 px-0.5">
<SidebarOpenButton />
{/* <SidebarOpenButton /> */}
<AnimatePresence>
{project && (
<motion.button
onClick={() => router.push(`/org/${org?.slug}`)}
className="group/org-back-button text-foreground-lighter flex items-center gap-1 hover:text-foreground text-xs cursor-pointer"
initial={{ opacity: 0, x: -20, width: 0 }}
animate={{ opacity: 1, x: 0, width: 'auto' }}
exit={{ opacity: 0, x: 20, width: 0 }}
transition={{ duration: 0.2 }}
>
<CornerLeftUp
size={14}
strokeWidth={1}
className="text-forefground-lighter group-hover/org-back-button:text-foreground group-hover/org-back-button:translate-y-[-2px] transition-transform"
/>
Organization
</motion.button>
)}
</AnimatePresence>
<div className="flex items-center gap-2">
<OrganizationDropdown />
{/* <Boxes className="text-foreground-lighter" size={14} strokeWidth={1} />
<span className="text-[12px] text-foreground-light"> {org?.name}</span> */}
</div>
{project && (
<div className="flex items-center gap-2">
<Boxes className="text-foreground-lighter" size={14} strokeWidth={1} />
<span className="text-[12px] text-foreground-light"> {org?.name}</span>
</div>
<div className="flex items-center gap-2">
<Box className="text-foreground-lighter" size={14} strokeWidth={1} />
<span className="text-[12px] text-foreground-light"> {project?.name}</span>
<ProjectDropdown />
</div>
)}
<div className="flex items-center gap-x-2">
{IS_PLATFORM && (
<>
<FeedbackDropdown />
<NotificationsPopoverV2 />
<HelpPopover />
</>
)}
</div>
</motion.div>
)}
@@ -6,6 +6,7 @@ import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { withAuth } from 'hooks/misc/withAuth'
import ProjectLayout from '../ProjectLayout/ProjectLayout'
import { generateAdvisorsMenu } from './AdvisorsMenu.utils'
import { ProjectPageNavigation } from '../AuthLayout/ProjectPageNavigation'
export interface AdvisorsLayoutProps {
title?: string
@@ -21,8 +22,9 @@ const AdvisorsLayout = ({ children }: PropsWithChildren<AdvisorsLayoutProps>) =>
<ProjectLayout
isLoading={false}
product="Advisors"
productMenu={<ProductMenu page={page} menu={generateAdvisorsMenu(project)} />}
// productMenu={<ProductMenu page={page} menu={generateAdvisorsMenu(project)} />}
>
<ProjectPageNavigation navKey="advisors">{children}</ProjectPageNavigation>
{children}
</ProjectLayout>
)
@@ -0,0 +1,28 @@
import type { ProductMenuGroupItem } from 'components/ui/ProductMenu/ProductMenu.types'
export const generateAdvisorsPageItemsMenu = (
ref: string,
flags?: {
pgNetExtensionExists: boolean
pitrEnabled: boolean
columnLevelPrivileges: boolean
}
): ProductMenuGroupItem[] => {
return [
{
name: 'Security',
key: 'security',
url: `/project/${ref}/advisors/security`,
},
{
name: 'Performance',
key: 'performance',
url: `/project/${ref}/advisors/performance`,
},
{
name: 'Query',
key: 'query',
url: `/project/${ref}/advisors/query-performance`,
},
]
}
@@ -4,11 +4,11 @@ import Link from 'next/link'
const IncidentBanner = () => {
return (
<Link href="https://status.supabase.com" target="_blank" rel="noreferrer">
<div className="flex cursor-pointer items-center justify-center space-x-2 bg-green-900 py-3 transition hover:bg-green-1000 text-foreground">
<p className="text-sm font-medium">
<div className="flex cursor-pointer items-center justify-center space-x-2 bg-brand-400 mx-2 border-l border-b border-r border-brand-500 py-1 transition hover:bg-green-1000 text-foreground rounded-b-[7px]">
<p className="text-sm">
We are currently investigating a technical issue, follow status.supabase.com for updates
</p>
<ExternalLink size={16} strokeWidth={2} />
<ExternalLink size={14} strokeWidth={1} />
</div>
</Link>
)
@@ -43,10 +43,7 @@ export const NoticeBanner = () => {
}
return (
<div
style={{ height: '44px' }}
className="flex items-center justify-center gap-x-4 bg-surface-100 py-3 transition text-foreground box-border border-b border-default"
>
<div className="flex items-center justify-center gap-x-4 bg-surface-100 py-1 mx-5 border transition text-foreground border-default">
<p className="text-sm">
Action required: Set up a custom SMTP provider, further restrictions will be imposed for the
default email provider
@@ -46,7 +46,12 @@ const OrganizationDropdown = () => {
<div className="flex items-center">
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
<Button type="default" className="pr-2" iconRight={<ChevronsUpDown />} icon={<Boxes />}>
<Button
type="default"
className="pr-2 pl-2"
iconRight={<ChevronsUpDown />}
icon={<Boxes />}
>
{orgName}
{/* <div className="flex items-center space-x-2"> */}
{/* <p className={''}>{orgName}</p> */}
@@ -121,6 +121,7 @@ const ProjectDropdown = ({ isNewNav = false }: ProjectDropdownProps) => {
<PopoverTrigger_Shadcn_ asChild>
<Button
type="default"
size="tiny"
className="pr-2 justify-start [&_span]:w-full [&_span]:text-left"
iconRight={<ChevronsUpDown />}
block
@@ -14,6 +14,7 @@ import { useParams } from 'common'
import { generateStoragePageItemsMenu } from '../StorageLayout/StorageLayout.utils'
import { AnimatePresence, motion } from 'framer-motion'
import { ChevronRight } from 'lucide-react'
import { generateAdvisorsPageItemsMenu } from '../AdvisorsLayout/AdvisorsLayout.utils'
export function ProjectPageNavigation({ children, navKey }: PropsWithChildren<{ navKey: string }>) {
const params = useParams()
@@ -26,6 +27,7 @@ export function ProjectPageNavigation({ children, navKey }: PropsWithChildren<{
const postgresItemsMenu = generatePostgresItemsMenu(ref as string)
const authMenu = generateAuthPageMenu(ref as string)
const storagePageItemsMenu = generateStoragePageItemsMenu(ref as string)
const advisorsPageItemsMenu = generateAdvisorsPageItemsMenu(ref as string)
switch (navKey) {
case 'auth':
@@ -37,6 +39,9 @@ export function ProjectPageNavigation({ children, navKey }: PropsWithChildren<{
case 'storage':
items = storagePageItemsMenu
break
case 'advisors':
items = advisorsPageItemsMenu
break
default:
items = []
}
@@ -1,4 +1,6 @@
import { AppBannerWrapper } from 'components/interfaces/App'
import { AppDefaultNavigation } from 'components/interfaces/app-default-navigation'
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
import { SidebarOpenTopBanner } from 'components/interfaces/sidebar-open-top-banner'
import { PropsWithChildren } from 'react'
import { SidebarProvider } from 'ui'
@@ -12,16 +14,24 @@ export interface DefaultLayoutProps {
const DefaultLayout = ({ children }: PropsWithChildren<DefaultLayoutProps>) => {
return (
<>
<SidebarProvider>
<div className="flex flex-col h-screen w-screen">
{/* {IS_PLATFORM && <LayoutHeader />} */}
<SidebarOpenTopBanner />
<div className="flex h-full w-full flex-row grow overflow-y-auto">
<AppDefaultNavigation />
<div className="py-1.5 px-1.5 flex-grow w-full h-full">{children}</div>
<AppBannerContextProvider>
<SidebarProvider>
<div className="flex flex-col h-screen w-screen">
{/* Top Banner */}
<div className="flex-shrink-0">
<AppBannerWrapper />
<SidebarOpenTopBanner />
</div>
{/* Main Content Area */}
<div className="flex flex-1 w-full overflow-hidden">
{/* Sidebar */}
<AppDefaultNavigation />
{/* Main Content */}
<div className="flex-grow h-full overflow-y-auto">{children}</div>
</div>
</div>
</div>
</SidebarProvider>
</SidebarProvider>
</AppBannerContextProvider>
</>
)
}
@@ -0,0 +1,51 @@
import type { ProductMenuGroupItem } from 'components/ui/ProductMenu/ProductMenu.types'
import { Archive } from 'lucide-react'
export const generateFunctionsPageItemsMenu = (
ref: string,
flags?: {
pgNetExtensionExists: boolean
pitrEnabled: boolean
columnLevelPrivileges: boolean
}
): ProductMenuGroupItem[] => {
return [
{
name: 'Functions',
key: 'functions',
url: `/project/${ref}/functions`,
hasChild: true,
childId: 'functionSlug',
childIcon: (
<Archive size={12} strokeWidth={1.5} className={'text-foreground w-full h-full'} />
),
childItems: [
{
name: 'Overview',
key: 'functions',
url: `/project/${ref}/functions`,
},
{
name: 'Functions',
key: 'functions',
url: `/project/${ref}/functions`,
},
],
},
{
name: 'Image transformations',
key: 'image-transformations',
url: `/project/${ref}/storage/image-transformations`,
},
{
name: 'Uploads',
key: 'uploads',
url: `/project/${ref}/storage/uploads`,
},
{
name: 'S3',
key: 's3',
url: `/project/${ref}/storage/s3`,
},
]
}
@@ -73,8 +73,9 @@ const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => {
const filteredNavMenuItems = navMenuItems.filter((item) => !item.hidden)
return (
<div className="w-full">
{/* <AnimatePresence>
<div className="relative py-1.5 w-full h-full">
<div className="relative w-full h-full border-l border-t border-b rounded-tl-[7px] rounded-bl-[7px] bg-dash-sidebar overflow-y-auto">
{/* <AnimatePresence>
<motion.div
className="px-10"
initial={{ opacity: 0, y: -20 }}
@@ -95,8 +96,8 @@ const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => {
</motion.div>
</AnimatePresence> */}
{/* <ScaffoldDivider /> */}
{/* <motion.div
{/* <ScaffoldDivider /> */}
{/* <motion.div
layoutId="layout-header-bottom-border"
className="bg-border h-px w-full"
initial={false}
@@ -106,23 +107,23 @@ const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => {
}}
/> */}
{selectedOrganization && selectedOrganization?.managed_by !== 'supabase' && (
<ScaffoldContainer className="mt-8">
<Alert_Shadcn_ variant="default" className="flex items-center gap-4">
<PartnerIcon organization={selectedOrganization} showTooltip={false} size="medium" />
<AlertTitle_Shadcn_ className="flex-1">
This organization is managed by {PARTNER_TO_NAME[selectedOrganization.managed_by]}.
</AlertTitle_Shadcn_>
<Button type="default" iconRight={<ExternalLink />} asChild disabled={!isSuccess}>
<a href={data?.url} target="_blank" rel="noopener noreferrer">
Manage
</a>
</Button>
</Alert_Shadcn_>
</ScaffoldContainer>
)}
{children}
{selectedOrganization && selectedOrganization?.managed_by !== 'supabase' && (
<ScaffoldContainer className="mt-8">
<Alert_Shadcn_ variant="default" className="flex items-center gap-4">
<PartnerIcon organization={selectedOrganization} showTooltip={false} size="medium" />
<AlertTitle_Shadcn_ className="flex-1">
This organization is managed by {PARTNER_TO_NAME[selectedOrganization.managed_by]}.
</AlertTitle_Shadcn_>
<Button type="default" iconRight={<ExternalLink />} asChild disabled={!isSuccess}>
<a href={data?.url} target="_blank" rel="noopener noreferrer">
Manage
</a>
</Button>
</Alert_Shadcn_>
</ScaffoldContainer>
)}
<main className="h-full w-full">{children}</main>
</div>
</div>
)
}
@@ -178,14 +178,10 @@ const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayout
{/* {!hideIconBar && <NavigationBar />} */}
<div
className={cn(
'border-l w-full rounded-tl-[7px] rounded-bl-[7px] border-t border-b overflow-hidden flex-row grow overflow-y-auto'
'border-l w-full rounded-tl-[7px] rounded-bl-[7px] border-t border-b flex-row my-1.5'
)}
>
<ResizablePanelGroup
className="flex h-full w-full"
direction="horizontal"
autoSaveId="project-layout"
>
<ResizablePanelGroup className="" direction="horizontal" autoSaveId="project-layout">
{showProductMenu && productMenu && (
<ResizablePanel
order={1}
@@ -22,7 +22,7 @@ const RealtimeLayout = ({ title, children }: PropsWithChildren<RealtimeLayoutPro
<ProjectLayout
title={title}
product="Realtime"
productMenu={<ProductMenu page={page} menu={generateRealtimeMenu(project!)} />}
// productMenu={<ProductMenu page={page} menu={generateRealtimeMenu(project!)} />}
>
{children}
</ProjectLayout>
@@ -48,7 +48,11 @@ const StorageLayout = ({ title, children }: StorageLayoutProps) => {
}
return (
<ProjectLayout title={title || 'Storage'} product="Storage" productMenu={<StorageMenu />}>
<ProjectLayout
title={title || 'Storage'}
product="Storage"
// productMenu={<StorageMenu />}
>
<ProjectPageNavigation navKey="storage">{children}</ProjectPageNavigation>
</ProjectLayout>
)
@@ -23,4 +23,5 @@ export interface ProductMenuGroupItem {
childId?: string
childIcon?: ReactNode
childItems?: ProductMenuGroupItem[]
}
+1
View File
@@ -76,6 +76,7 @@ export const LOCAL_STORAGE_KEYS = {
AUTH_SMTP_CHANGES_WARNING: 'auth-smtp-changes-warning-dismissed',
AUTH_USERS_COLUMNS_CONFIGURATION: (ref: string) => `supabase-auth-users-columns-${ref}`,
STORAGE_BUCKETS_COLUMN_CONFIG: (ref: string) => `supabase-storage-buckets-columns-${ref}`,
}
export const OPT_IN_TAGS = {
+9 -13
View File
@@ -147,19 +147,15 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
enableSystem
disableTransitionOnChange
>
<AppBannerContextProvider>
<CommandProvider>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<StudioCommandMenu />
<GenerateSql />
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
<SonnerToaster position="top-right" />
</CommandProvider>
</AppBannerContextProvider>
<CommandProvider>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<StudioCommandMenu />
<GenerateSql />
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
<SonnerToaster position="top-right" />
</CommandProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
@@ -13,6 +13,8 @@ import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import type { NextPageWithLayout } from 'types'
import { LoadingLine } from 'ui'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
const ProjectLints: NextPageWithLayout = () => {
const project = useSelectedProject()
@@ -65,11 +67,11 @@ const ProjectLints: NextPageWithLayout = () => {
return (
<div className="h-full flex flex-col">
<FormHeader
{/* <FormHeader
className="py-4 px-6 !mb-0"
title="Performance Advisor"
docsUrl="https://supabase.com/docs/guides/database/database-linter"
/>
/> */}
<LintPageTabs
activeLints={activeLints}
isLoading={isLoading}
@@ -97,6 +99,12 @@ const ProjectLints: NextPageWithLayout = () => {
)
}
ProjectLints.getLayout = (page) => <AdvisorsLayout title="Linter">{page}</AdvisorsLayout>
ProjectLints.getLayout = (page) => (
<AppLayout>
<DefaultLayout>
<AdvisorsLayout title="Linter">{page}</AdvisorsLayout>
</DefaultLayout>
</AppLayout>
)
export default ProjectLints
@@ -14,6 +14,8 @@ import AdvisorsLayout from 'components/layouts/AdvisorsLayout/AdvisorsLayout'
import DatabaseSelector from 'components/ui/DatabaseSelector'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import type { NextPageWithLayout } from 'types'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
const PRESET_MAP = {
[QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING]: 'mostTimeConsuming',
@@ -46,19 +48,23 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
return (
<div className="h-full flex flex-col">
<FormHeader
{/* <FormHeader
className="py-4 px-6 !mb-0"
title="Query Performance"
docsUrl="https://supabase.com/docs/guides/platform/performance#examining-query-performance"
actions={<DatabaseSelector />}
/>
/> */}
<QueryPerformance queryHitRate={queryHitRate} queryPerformanceQuery={queryPerformanceQuery} />
</div>
)
}
QueryPerformanceReport.getLayout = (page) => (
<AdvisorsLayout title="Query performance">{page}</AdvisorsLayout>
<AppLayout>
<DefaultLayout>
<AdvisorsLayout title="Query performance">{page}</AdvisorsLayout>
</DefaultLayout>
</AppLayout>
)
export default QueryPerformanceReport
@@ -13,6 +13,8 @@ import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import type { NextPageWithLayout } from 'types'
import { LoadingLine } from 'ui'
import DefaultLayout from 'components/layouts/DefaultLayout'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
const ProjectLints: NextPageWithLayout = () => {
const project = useSelectedProject()
@@ -72,11 +74,11 @@ const ProjectLints: NextPageWithLayout = () => {
return (
<div className="h-full flex flex-col">
<FormHeader
{/* <FormHeader
className="py-4 px-6 !mb-0"
title="Security Advisor"
docsUrl="https://supabase.com/docs/guides/database/database-linter"
/>
/> */}
<LintPageTabs
activeLints={activeLints}
isLoading={isLoading}
@@ -109,6 +111,12 @@ const ProjectLints: NextPageWithLayout = () => {
)
}
ProjectLints.getLayout = (page) => <AdvisorsLayout title="Linter">{page}</AdvisorsLayout>
ProjectLints.getLayout = (page) => (
<AppLayout>
<DefaultLayout>
<AdvisorsLayout title="Linter">{page}</AdvisorsLayout>
</DefaultLayout>
</AppLayout>
)
export default ProjectLints
@@ -23,6 +23,8 @@ import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
import type { NextPageWithLayout } from 'types'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
const PageLayout: NextPageWithLayout = () => {
const { ref } = useParams()
@@ -96,6 +98,12 @@ const PageLayout: NextPageWithLayout = () => {
)
}
PageLayout.getLayout = (page) => <FunctionsLayout>{page}</FunctionsLayout>
PageLayout.getLayout = (page) => (
<AppLayout>
<DefaultLayout>
<FunctionsLayout>{page}</FunctionsLayout>
</DefaultLayout>
</AppLayout>
)
export default PageLayout
@@ -5,6 +5,8 @@ import { RealtimeInspector } from 'components/interfaces/Realtime/Inspector'
import RealtimeLayout from 'components/layouts/RealtimeLayout/RealtimeLayout'
import NoPermission from 'components/ui/NoPermission'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
export const InspectorPage: NextPageWithLayout = () => {
const canReadAPIKeys = useCheckPermissions(PermissionAction.READ, 'service_api_keys')
@@ -17,7 +19,11 @@ export const InspectorPage: NextPageWithLayout = () => {
}
InspectorPage.getLayout = (page) => (
<RealtimeLayout title="Realtime Inspector">{page}</RealtimeLayout>
<AppLayout>
<DefaultLayout>
<RealtimeLayout title="Realtime Inspector">{page}</RealtimeLayout>
</DefaultLayout>
</AppLayout>
)
export default InspectorPage
@@ -1,4 +1,5 @@
import { useParams } from 'common'
import { StorageBucketList } from 'components/interfaces/Storage/storage-bucket-list'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
@@ -21,22 +22,24 @@ const PageLayout: NextPageWithLayout = () => {
if (isError) <StorageBucketsError error={error as any} />
return (
<div className="storage-container flex flex-grow">
<ProductEmptyState
title="Storage"
infoButtonLabel="About storage"
infoButtonUrl="https://supabase.com/docs/guides/storage"
>
<p className="text-foreground-light text-sm">
Create buckets to store and serve any type of digital content.
</p>
<p className="text-foreground-light text-sm">
Make your buckets private or public depending on your security preference.
</p>
</ProductEmptyState>
</div>
)
// return (
// <div className="storage-container flex flex-grow">
// <ProductEmptyState
// title="Storage"
// infoButtonLabel="About storage"
// infoButtonUrl="https://supabase.com/docs/guides/storage"
// >
// <p className="text-foreground-light text-sm">
// Create buckets to store and serve any type of digital content.
// </p>
// <p className="text-foreground-light text-sm">
// Make your buckets private or public depending on your security preference.
// </p>
// </ProductEmptyState>
// </div>
// )
return <StorageBucketList />
}
PageLayout.getLayout = (page) => (
@@ -205,7 +205,7 @@ const Sidebar = React.forwardRef<
return (
<div
ref={ref}
className="group peer hidden md:block text-sidebar-foreground"
className="relative group peer hidden md:block text-sidebar-foreground"
data-state={state}
data-collapsible={state === 'collapsed' ? collapsible : ''}
data-variant={variant}
@@ -224,7 +224,10 @@ const Sidebar = React.forwardRef<
/>
<div
className={cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
'absolute', // removed fixed, and h-svh
'top-0',
'h-full',
'duration-200 inset-y-0 z-10 hidden w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',