diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/TableReplicationRow.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/TableReplicationRow.tsx index 51bbe46529b..17f61d935c7 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/TableReplicationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/TableReplicationRow.tsx @@ -47,7 +47,7 @@ export const TableReplicationRow = ({ className="px-1.5" icon={} tooltip={{ - content: { side: 'bottom', text: 'Open in Table Editor' }, + content: { side: 'bottom', text: 'Table Editor' }, }} > } + variant="default" disabled={!canUpdateColumns} onClick={() => onAddColumn()} tooltip={{ diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailColumnsTab.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailColumnsTab.tsx new file mode 100644 index 00000000000..28d8a5de0e8 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailColumnsTab.tsx @@ -0,0 +1,22 @@ +import { ColumnList } from './ColumnList' +import type { SafePostgresColumn } from '@/lib/postgres-types' + +interface TableDetailColumnsTabProps { + onAddColumn: () => void + onEditColumn: (column: SafePostgresColumn) => void + onDeleteColumn: (column: SafePostgresColumn) => void +} + +export function TableDetailColumnsTab({ + onAddColumn, + onEditColumn, + onDeleteColumn, +}: TableDetailColumnsTabProps) { + return ( + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailDataApiSection.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailDataApiSection.tsx new file mode 100644 index 00000000000..61f47b2770f --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailDataApiSection.tsx @@ -0,0 +1,41 @@ +import { Card, CardContent } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + +import { + ApiAccessToggle, + useTableApiAccessHandlerWithHistory, +} from '@/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ApiAccessToggle' +import type { TableLike } from '@/data/table-editor/table-editor-types' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' + +interface TableDetailDataApiSectionProps { + table: TableLike +} + +export function TableDetailDataApiSection({ table }: TableDetailDataApiSectionProps) { + const { data: project } = useSelectedProjectQuery() + const handler = useTableApiAccessHandlerWithHistory({ + type: 'edit', + schemaName: table.schema, + tableName: table.name, + }) + + if (handler.isPending) { + return + } + + return ( + + + + + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailDeleteTableSection.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailDeleteTableSection.tsx new file mode 100644 index 00000000000..d86a93b0e98 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailDeleteTableSection.tsx @@ -0,0 +1,38 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { Alert, AlertDescription, AlertTitle, Button, CriticalIcon } from 'ui' + +import type { TableLike } from '@/data/table-editor/table-editor-types' +import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { useTableEditorStateSnapshot } from '@/state/table-editor' + +interface TableDetailDeleteTableSectionProps { + table: TableLike +} + +export function TableDetailDeleteTableSection({ table }: TableDetailDeleteTableSectionProps) { + const snap = useTableEditorStateSnapshot() + const { can: canDeleteTables } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'tables' + ) + + return ( + + + Once this table is deleted, it can no longer be restored + + Deleting {table.schema}.{table.name} permanently + removes the table and its data. Use cascade if dependent objects must be removed too. + + + + + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailIndexAdvisorSection.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailIndexAdvisorSection.tsx new file mode 100644 index 00000000000..c9176f56f3b --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailIndexAdvisorSection.tsx @@ -0,0 +1,92 @@ +import { useParams } from 'common' +import Link from 'next/link' +import { useState } from 'react' +import { Button, Card, CardContent } from 'ui' +import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + +import { + TableIndexAdvisorProvider, + useTableIndexAdvisor, +} from '@/components/grid/context/TableIndexAdvisorContext' +import { EnableIndexAdvisorDialog } from '@/components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton' +import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus' +import { useTrack } from '@/lib/telemetry/track' +import type { TableLike } from '@/data/table-editor/table-editor-types' + +interface TableDetailIndexAdvisorSectionProps { + table: TableLike +} + +function TableDetailIndexAdvisorContent() { + const track = useTrack() + const { ref } = useParams() + const [enableDialogOpen, setEnableDialogOpen] = useState(false) + const { isIndexAdvisorAvailable, isIndexAdvisorEnabled } = useIndexAdvisorStatus() + const { columnsWithSuggestions, isLoading } = useTableIndexAdvisor() + + if (!isIndexAdvisorAvailable) return null + + if (!isIndexAdvisorEnabled) { + return ( + <> + + + + + + ) + } + + if (isLoading) { + return + } + + const suggestionCount = columnsWithSuggestions.length + + return ( + 0 + ? `${suggestionCount} column${suggestionCount === 1 ? '' : 's'} with index recommendations based on query patterns.` + : 'Enabled. Recommendations will appear as query patterns are analyzed.' + } + layout="flex-row-reverse" + > + + + ) +} + +export function TableDetailIndexAdvisorSection({ table }: TableDetailIndexAdvisorSectionProps) { + const { isIndexAdvisorAvailable } = useIndexAdvisorStatus() + + if (!isIndexAdvisorAvailable) return null + + return ( + + + + + + + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailOverview.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailOverview.tsx deleted file mode 100644 index 2b2606ac622..00000000000 --- a/apps/studio/components/interfaces/Database/Tables/TableDetailOverview.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Check, X } from 'lucide-react' -import type { ReactNode } from 'react' - -import type { Entity } from '@/data/table-editor/table-editor-types' -import { isTableLike } from '@/data/table-editor/table-editor-types' - -interface TableDetailOverviewProps { - entity: Entity - realtimeEnabled: boolean -} - -function OverviewItem({ label, children }: { label: string; children: ReactNode }) { - return ( -
- {label} -
{children}
-
- ) -} - -export function TableDetailOverview({ entity, realtimeEnabled }: TableDetailOverviewProps) { - const isTable = isTableLike(entity) - - return ( -
-
- - {entity.schema} - - - {isTable && ( - -
- {entity.rls_enabled ? ( - <> - - Enabled - - ) : ( - <> - - Disabled - - )} -
-
- )} - - {isTable && ( - -
- {realtimeEnabled ? ( - <> - - Enabled - - ) : ( - <> - - Disabled - - )} -
-
- )} - - {isTable && entity.size !== undefined && ( - - {entity.size} - - )} - - {entity.comment !== undefined && entity.comment !== null && entity.comment !== '' && ( - - {entity.comment} - - )} -
-
- ) -} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailPageContent.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailPageContent.tsx deleted file mode 100644 index a4f168795e3..00000000000 --- a/apps/studio/components/interfaces/Database/Tables/TableDetailPageContent.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useParams } from 'common' -import { PageContainer } from 'ui-patterns/PageContainer' -import { - PageSection, - PageSectionContent, - PageSectionMeta, - PageSectionSummary, - PageSectionTitle, -} from 'ui-patterns/PageSection' -import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' -import { useSnapshot } from 'valtio' - -import { ColumnList } from './ColumnList' -import { TableDetailOverview } from './TableDetailOverview' -import { warehouseDemoStore } from '@/components/interfaces/Database/Warehouse/warehouseDemoStore' -import { WarehouseSyncChip } from '@/components/interfaces/Database/Warehouse/WarehouseSyncChip' -import { WarehouseTableStoragePanel } from '@/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel' -import DeleteConfirmationDialogs from '@/components/interfaces/TableGridEditor/DeleteConfirmationDialogs' -import { SidePanelEditor } from '@/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor' -import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' -import { useDatabasePublicationsQuery } from '@/data/database-publications/database-publications-query' -import { useTableEditorQuery } from '@/data/table-editor/table-editor-query' -import { isTableLike } from '@/data/table-editor/table-editor-types' -import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' -import { useTableEditorStateSnapshot } from '@/state/table-editor' -import { TableEditorTableStateContextProvider } from '@/state/table-editor-table' - -export function TableDetailPageContent() { - const snap = useTableEditorStateSnapshot() - const { id: _id, ref } = useParams() - const id = _id ? Number(_id) : undefined - - const { data: project } = useSelectedProjectQuery() - const { data: selectedTable, isPending: isLoading } = useTableEditorQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - id, - }) - - const { data: publications } = useDatabasePublicationsQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - }) - const realtimePublication = (publications ?? []).find( - (publication) => publication.name === 'supabase_realtime' - ) - const realtimeEnabled = - selectedTable?.id !== undefined && - (realtimePublication?.tables ?? []).some((table) => table.id === selectedTable.id) - - const warehouseSnap = useSnapshot(warehouseDemoStore) - const tableKey = - selectedTable?.schema !== undefined && selectedTable?.name !== undefined - ? `${selectedTable.schema}.${selectedTable.name}` - : undefined - const warehouseState = tableKey ? warehouseSnap.tables[tableKey] : undefined - const storageSyncError = warehouseState?.syncState === 'error' - - return ( - <> - : (selectedTable?.name ?? '')} - breadcrumbs={[ - { - label: 'Tables', - href: `/project/${ref}/database/tables`, - }, - ]} - size="large" - > - - {selectedTable !== undefined && !isLoading && ( - - )} - - {tableKey && selectedTable !== undefined && isTableLike(selectedTable) && ( - - - -
- Storage - {storageSyncError && } -
-
-
- - - -
- )} - - - - - Columns - - - - - - -
-
- - {project?.ref !== undefined && selectedTable !== undefined && isTableLike(selectedTable) && ( - - - - - )} - - ) -} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailPoliciesTab.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailPoliciesTab.tsx new file mode 100644 index 00000000000..5c90b51c8e6 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailPoliciesTab.tsx @@ -0,0 +1,99 @@ +import { useMemo, useState } from 'react' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + +import { Policies } from '@/components/interfaces/Auth/Policies/Policies' +import { PoliciesDataProvider } from '@/components/interfaces/Auth/Policies/PoliciesDataContext' +import { PolicyEditorPanel } from '@/components/interfaces/Auth/Policies/PolicyEditorPanel' +import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils' +import { getExposedSchemas } from '@/components/layouts/ProjectNeedsSecuring/ProjectNeedsSecuring.utils' +import AlertError from '@/components/ui/AlertError' +import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query' +import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query' +import type { TableLike } from '@/data/table-editor/table-editor-types' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' + +interface TableDetailPoliciesTabProps { + table: TableLike +} + +export function TableDetailPoliciesTab({ table }: TableDetailPoliciesTabProps) { + const { data: project } = useSelectedProjectQuery() + const isSchemaLocked = useIsProtectedSchema({ schema: table.schema }).isSchemaLocked + + const [showPolicyEditor, setShowPolicyEditor] = useState(false) + const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState() + + const { data: dbSchema } = useProjectPostgrestConfigQuery( + { projectRef: project?.ref }, + { select: ({ db_schema }) => db_schema } + ) + const exposedSchemas = useMemo(() => getExposedSchemas(dbSchema), [dbSchema]) + + const { + data: policies, + isPending: isLoadingPolicies, + isError: isPoliciesError, + error: policiesError, + } = useDatabasePoliciesQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const visibleTableIds = useMemo(() => new Set([table.id]), [table.id]) + + const policyTable = useMemo( + () => ({ + id: table.id, + schema: table.schema, + name: table.name, + rls_enabled: table.rls_enabled, + }), + [table] + ) + + if (isLoadingPolicies) { + return + } + + if (isPoliciesError) { + return + } + + return ( + <> + + { + setSelectedPolicyToEdit(undefined) + setShowPolicyEditor(true) + }} + onSelectEditPolicy={(policy) => { + setSelectedPolicyToEdit(policy) + setShowPolicyEditor(true) + }} + /> + + + setShowPolicyEditor(false)} + authContext="database" + /> + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableDetailSettingsTab.tsx b/apps/studio/components/interfaces/Database/Tables/TableDetailSettingsTab.tsx new file mode 100644 index 00000000000..986b370fec7 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Tables/TableDetailSettingsTab.tsx @@ -0,0 +1,121 @@ +import { Realtime } from 'icons' +import { useState } from 'react' +import { Button, Card, CardContent } from 'ui' +import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' +import { + PageSection, + PageSectionContent, + PageSectionMeta, + PageSectionSummary, + PageSectionTitle, +} from 'ui-patterns/PageSection' + +import { TableDetailDataApiSection } from '@/components/interfaces/Database/Tables/TableDetailDataApiSection' +import { TableDetailDeleteTableSection } from '@/components/interfaces/Database/Tables/TableDetailDeleteTableSection' +import { TableDetailIndexAdvisorSection } from '@/components/interfaces/Database/Tables/TableDetailIndexAdvisorSection' +import { WarehouseTableStoragePanel } from '@/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel' +import { RealtimeToggleDialog } from '@/components/interfaces/TableGridEditor/RealtimeToggleDialog' +import { useIsTableRealtimeEnabled } from '@/data/database-publications/database-publications-query' +import { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants' +import type { TableLike } from '@/data/table-editor/table-editor-types' +import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' + +interface TableDetailSettingsTabProps { + table: TableLike +} + +export function TableDetailSettingsTab({ table }: TableDetailSettingsTabProps) { + const [realtimeDialogOpen, setRealtimeDialogOpen] = useState(false) + const { realtimeAll: isRealtimeFeatureEnabled } = useIsFeatureEnabled(['realtime:all']) + const isRealtimeEnabled = useIsTableRealtimeEnabled({ id: table.id }) + const showStorage = table.entity_type === ENTITY_TYPE.TABLE + const tableKey = `${table.schema}.${table.name}` + + return ( + <> +
+ {showStorage && ( + + + + Storage + + + + + + + )} + + {isRealtimeFeatureEnabled && ( + + + + Realtime + + + + + + + + + + + + + )} + + + + + Index Advisor + + + + + + + + + + + Data API access + + + + + + + + + + + Delete table + + + + + + +
+ + + + ) +} diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index 60f83a86df0..ae1035ef3e3 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -4,6 +4,7 @@ import { useParams } from 'common' import { noop } from 'lodash' import { Check, + ChevronRight, Columns2, Copy, Edit, @@ -50,7 +51,7 @@ import { useSnapshot } from 'valtio' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import { - formatWarehouseSize, + getWarehouseStorageSummaryLabel, warehouseDemoStore, type WarehouseMode, } from '../Warehouse/warehouseDemoStore' @@ -560,7 +561,7 @@ export const TableList = ({ return ( event.stopPropagation()} className="inline-flex max-w-full items-center gap-2 rounded-sm hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter" > - {mode === 'warehouse_backed' - ? `${warehouseSize} · Moved` - : `${x.size ?? '—'} · Copy · ${warehouseSize}`} + {storageSummary} {showSyncChip && ( @@ -673,8 +676,9 @@ export const TableList = ({
event.stopPropagation()} + onKeyDown={(event) => event.stopPropagation()} > @@ -730,7 +734,7 @@ export const TableList = ({ }} > -

Edit table

+

Edit definitions

+
diff --git a/apps/studio/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel.tsx b/apps/studio/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel.tsx index 25744da0750..e7d4b570d22 100644 --- a/apps/studio/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel.tsx +++ b/apps/studio/components/interfaces/Database/Warehouse/WarehouseTableStoragePanel.tsx @@ -95,19 +95,10 @@ export function WarehouseTableStoragePanel({ {mode === 'has_warehouse_copy' && ( <> {state.syncState && ( - + )} - {state.lastSyncedAt !== undefined && ( - - - - )} {state.lagSeconds !== undefined && ( {state.lagSeconds}s @@ -165,15 +156,39 @@ export function WarehouseTableStoragePanel({ )} {mode === 'has_warehouse_copy' && ( - +
+ + + +
)} {mode === 'warehouse_backed' && ( + )} + + + + )} + + + {navigationItems.length > 1 && ( + + + {navigationItems.map((item) => { + const isActive = router.asPath.split('?')[0] === item.href + return ( + + {item.label} + + ) + })} + + + )} + + + + {children} + + + + {project?.ref !== undefined && selectedTable !== undefined && isTable && ( + + { + router.push(`/project/${ref}/database/tables`) + }} + /> + + + )} + + ) +} diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx index 6474dc81091..eb5d78d343c 100644 --- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx @@ -356,7 +356,7 @@ export const EntityListItem = ({ }} > - Edit table + Edit definitions { - return +const TableDetailColumnsPage: NextPageWithLayout = () => { + const snap = useTableEditorStateSnapshot() + const { id: _id } = useParams() + const id = _id ? Number(_id) : undefined + const { data: project } = useSelectedProjectQuery() + const { data: selectedTable, isPending } = useTableEditorQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + id, + }) + + if (isPending) { + return + } + + if (selectedTable === undefined) { + return null + } + + if (isTableLike(selectedTable)) { + return ( + + ) + } + + return ( + + ) } -DatabaseTableDetailPage.getLayout = (page) => ( +TableDetailColumnsPage.getLayout = (page) => ( - {page} + {page} ) -export default DatabaseTableDetailPage +export default TableDetailColumnsPage diff --git a/apps/studio/pages/project/[ref]/database/tables/[id]/policies.tsx b/apps/studio/pages/project/[ref]/database/tables/[id]/policies.tsx new file mode 100644 index 00000000000..28ce6ea8727 --- /dev/null +++ b/apps/studio/pages/project/[ref]/database/tables/[id]/policies.tsx @@ -0,0 +1,39 @@ +import { useParams } from 'common' +import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' + +import { TableDetailPoliciesTab } from '@/components/interfaces/Database/Tables/TableDetailPoliciesTab' +import { TableDetailLayout } from '@/components/layouts/DatabaseLayout/TableDetailLayout' +import DefaultLayout from '@/components/layouts/DefaultLayout' +import { useTableEditorQuery } from '@/data/table-editor/table-editor-query' +import { isTableLike } from '@/data/table-editor/table-editor-types' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import type { NextPageWithLayout } from '@/types' + +const TableDetailPoliciesPage: NextPageWithLayout = () => { + const { id: _id } = useParams() + const id = _id ? Number(_id) : undefined + const { data: project } = useSelectedProjectQuery() + const { data: selectedTable, isPending } = useTableEditorQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + id, + }) + + if (isPending) { + return + } + + if (selectedTable === undefined || !isTableLike(selectedTable)) { + return null + } + + return +} + +TableDetailPoliciesPage.getLayout = (page) => ( + + {page} + +) + +export default TableDetailPoliciesPage diff --git a/apps/studio/pages/project/[ref]/database/tables/[id]/settings.tsx b/apps/studio/pages/project/[ref]/database/tables/[id]/settings.tsx new file mode 100644 index 00000000000..5c1c8e41d67 --- /dev/null +++ b/apps/studio/pages/project/[ref]/database/tables/[id]/settings.tsx @@ -0,0 +1,39 @@ +import { useParams } from 'common' +import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' + +import { TableDetailSettingsTab } from '@/components/interfaces/Database/Tables/TableDetailSettingsTab' +import { TableDetailLayout } from '@/components/layouts/DatabaseLayout/TableDetailLayout' +import DefaultLayout from '@/components/layouts/DefaultLayout' +import { useTableEditorQuery } from '@/data/table-editor/table-editor-query' +import { isTableLike } from '@/data/table-editor/table-editor-types' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import type { NextPageWithLayout } from '@/types' + +const TableDetailSettingsPage: NextPageWithLayout = () => { + const { id: _id } = useParams() + const id = _id ? Number(_id) : undefined + const { data: project } = useSelectedProjectQuery() + const { data: selectedTable, isPending } = useTableEditorQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + id, + }) + + if (isPending) { + return + } + + if (selectedTable === undefined || !isTableLike(selectedTable)) { + return null + } + + return +} + +TableDetailSettingsPage.getLayout = (page) => ( + + {page} + +) + +export default TableDetailSettingsPage diff --git a/apps/studio/pages/project/[ref]/database/tables/[id]/storage.tsx b/apps/studio/pages/project/[ref]/database/tables/[id]/storage.tsx index 82678b8de00..357ba0d3abe 100644 --- a/apps/studio/pages/project/[ref]/database/tables/[id]/storage.tsx +++ b/apps/studio/pages/project/[ref]/database/tables/[id]/storage.tsx @@ -2,7 +2,7 @@ import { useParams } from 'common' import { useRouter } from 'next/router' import { useEffect } from 'react' -import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout' +import { TableDetailLayout } from '@/components/layouts/DatabaseLayout/TableDetailLayout' import DefaultLayout from '@/components/layouts/DefaultLayout' import type { NextPageWithLayout } from '@/types' @@ -12,7 +12,7 @@ const TableStorageRedirect: NextPageWithLayout = () => { useEffect(() => { if (ref && id) { - router.replace(`/project/${ref}/database/tables/${id}`) + router.replace(`/project/${ref}/database/tables/${id}/settings`) } }, [ref, id, router]) @@ -21,7 +21,7 @@ const TableStorageRedirect: NextPageWithLayout = () => { TableStorageRedirect.getLayout = (page) => ( - {page} + {page} )