mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 22:59:11 +03:00
moar
This commit is contained in:
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[]
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)]',
|
||||
|
||||
Reference in new issue
Block a user