diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx
index bb793a16ac1..61e1d483f12 100644
--- a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx
+++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx
@@ -2,7 +2,7 @@ import type { PostgresSchema } from '@supabase/postgres-meta'
import { toPng, toSvg } from 'html-to-image'
import { Check, Clipboard, Download, Loader2 } from 'lucide-react'
import { useTheme } from 'next-themes'
-import { useEffect, useMemo, useRef, useState } from 'react'
+import { useEffect, useMemo, useState } from 'react'
import ReactFlow, { Background, BackgroundVariant, MiniMap, useReactFlow } from 'reactflow'
import 'reactflow/dist/style.css'
import { toast } from 'sonner'
@@ -30,12 +30,7 @@ import { getGraphDataFromTables, getLayoutedElementsViaDagre } from './Schemas.u
import { TableNode } from './SchemaTableNode'
// [Joshen] Persisting logic: Only save positions to local storage WHEN a node is moved OR when explicitly clicked to reset layout
-interface SchemaGraphProps {
- hideUI?: boolean
- onLoad?: () => void
-}
-
-export const SchemaGraph = ({ hideUI = false, onLoad }: SchemaGraphProps) => {
+export const SchemaGraph = () => {
const { ref } = useParams()
const { resolvedTheme } = useTheme()
const { data: project } = useSelectedProjectQuery()
@@ -93,8 +88,6 @@ export const SchemaGraph = ({ hideUI = false, onLoad }: SchemaGraphProps) => {
{}
)
- const hasFiredOnLoadRef = useRef(false)
-
const resetLayout = () => {
const nodes = reactFlowInstance.getNodes()
const edges = reactFlowInstance.getEdges()
@@ -186,110 +179,94 @@ export const SchemaGraph = ({ hideUI = false, onLoad }: SchemaGraphProps) => {
getGraphDataFromTables(ref as string, schema, tables).then(({ nodes, edges }) => {
reactFlowInstance.setNodes(nodes)
reactFlowInstance.setEdges(edges)
- setTimeout(() => {
- reactFlowInstance.fitView({}) // it needs to happen during next event tick
- if (!hasFiredOnLoadRef.current) {
- hasFiredOnLoadRef.current = true
- onLoad?.()
- }
- })
+ setTimeout(() => reactFlowInstance.fitView({})) // it needs to happen during next event tick
})
}
}, [isSuccessTables, isSuccessSchemas, tables, resolvedTheme])
- // Fire onLoad even if there are no tables (successful load but empty state)
- useEffect(() => {
- if (isSuccessTables && isSuccessSchemas && tables.length === 0 && !hasFiredOnLoadRef.current) {
- hasFiredOnLoadRef.current = true
- onLoad?.()
- }
- }, [isSuccessTables, isSuccessSchemas, tables])
-
return (
<>
- {!hideUI && (
-
- {isLoadingSchemas && (
-
- )}
+
+ {isLoadingSchemas && (
+
+ )}
- {isErrorSchemas && (
-
- )}
+ {isErrorSchemas && (
+
+ )}
- {isSuccessSchemas && (
- <>
-
-
-
:
}
- onClick={() => {
- if (tables) {
- copyToClipboard(tablesToSQL(tables))
- setCopied(true)
- }
- }}
- tooltip={{
- content: {
- side: 'bottom',
- text: (
-
-
Note
-
- This schema is for context or debugging only. Table order and
- constraints may be invalid. Not meant to be run as-is.
-
-
- ),
- },
- }}
- >
- Copy as SQL
-
-
-
- }
- tooltip={{ content: { side: 'bottom', text: 'Download current view' } }}
- />
-
-
- downloadImage('png')}>
- Download as PNG
-
- downloadImage('svg')}>
- Download as SVG
-
-
-
-
- Auto layout
-
-
- >
- )}
-
- )}
+ {isSuccessSchemas && (
+ <>
+
+
+
:
}
+ onClick={() => {
+ if (tables) {
+ copyToClipboard(tablesToSQL(tables))
+ setCopied(true)
+ }
+ }}
+ tooltip={{
+ content: {
+ side: 'bottom',
+ text: (
+
+
Note
+
+ This schema is for context or debugging only. Table order and constraints
+ may be invalid. Not meant to be run as-is.
+
+
+ ),
+ },
+ }}
+ >
+ Copy as SQL
+
+
+
+ }
+ tooltip={{ content: { side: 'bottom', text: 'Download current view' } }}
+ />
+
+
+ downloadImage('png')}>
+ Download as PNG
+
+ downloadImage('svg')}>
+ Download as SVG
+
+
+
+
+ Auto layout
+
+
+ >
+ )}
+
{isLoadingTables && (
@@ -303,7 +280,7 @@ export const SchemaGraph = ({ hideUI = false, onLoad }: SchemaGraphProps) => {
)}
{isSuccessTables && (
<>
- {tables.length === 0 && !hideUI ? (
+ {tables.length === 0 ? (
{
}}
nodeTypes={nodeTypes}
fitView
- fitViewOptions={{
- padding: hideUI ? 48 : 0,
- }}
minZoom={0.8}
maxZoom={1.8}
proOptions={{ hideAttribution: true }}
@@ -341,22 +315,18 @@ export const SchemaGraph = ({ hideUI = false, onLoad }: SchemaGraphProps) => {
>
- {!hideUI && (
- <>
-
-
- >
- )}
+
+
)}
diff --git a/apps/studio/components/interfaces/HomeNew/AdvisorWidget.tsx b/apps/studio/components/interfaces/HomeNew/AdvisorWidget.tsx
index f11c359b257..3aab8001a82 100644
--- a/apps/studio/components/interfaces/HomeNew/AdvisorWidget.tsx
+++ b/apps/studio/components/interfaces/HomeNew/AdvisorWidget.tsx
@@ -1,16 +1,28 @@
import { BarChart, Shield } from 'lucide-react'
-import Link from 'next/link'
-import { useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { useParams } from 'common'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
-import { EntityTypeIcon, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
+import { LintCategoryBadge, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
-import { AiIconAnimation, Button, Card, CardContent, CardHeader, CardTitle } from 'ui'
+import {
+ AiIconAnimation,
+ Button,
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetSection,
+ SheetTitle,
+} from 'ui'
import { Row } from 'ui-patterns'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
+import LintDetail from 'components/interfaces/Linter/LintDetail'
// Returns the first sentence of the provided text. Uses Intl.Segmenter when available
// and falls back to a regex that avoids splitting on intra-word dots (e.g. `public.messages`).
@@ -45,6 +57,8 @@ export const AdvisorWidget = () => {
const { data: lints, isLoading: isLoadingLints } = useProjectLintsQuery({ projectRef })
const snap = useAiAssistantStateSnapshot()
+ const [selectedLint, setSelectedLint] = useState
(null)
+
const securityLints = useMemo(
() => (lints ?? []).filter((lint: Lint) => lint.categories.includes('SECURITY')),
[lints]
@@ -113,54 +127,77 @@ export const AdvisorWidget = () => {
) : combinedIssues.length > 0 ? (
-
- {combinedIssues.map((lint) => {
- console.log(lint)
- const advisor = getAdvisorPath(lint)
- return (
-
-
-
- {lint.categories[0] === 'SECURITY' ? (
-
- ) : (
-
- )}
- {lint.categories[0]}
-
- }
- onClick={(e) => {
- e.stopPropagation()
- e.preventDefault()
- snap.newChat({
- name: 'Summarize lint',
- open: true,
- initialInput: `Summarize the issue and suggest fixes for the following lint item:
+ <>
+
+ {combinedIssues.map((lint) => {
+ const advisor = getAdvisorPath(lint)
+ return (
+ {
+ setSelectedLint(lint)
+ }}
+ >
+
+
+ {lint.categories[0] === 'SECURITY' ? (
+
+ ) : (
+
+ )}
+ {lint.categories[0]}
+
+ }
+ onClick={(e) => {
+ e.stopPropagation()
+ e.preventDefault()
+ snap.newChat({
+ name: 'Summarize lint',
+ open: true,
+ initialInput: `Summarize the issue and suggest fixes for the following lint item:
Title: ${lintInfoMap.find((item) => item.name === lint.name)?.title ?? lint.title}
Entity: ${(lint.metadata && (lint.metadata.entity || (lint.metadata.schema && lint.metadata.name && `${lint.metadata.schema}.${lint.metadata.name}`))) ?? 'N/A'}
Schema: ${lint.metadata?.schema ?? 'N/A'}
Issue Details: ${lint.detail ? lint.detail.replace(/\`/g, '`') : 'N/A'}
Description: ${lint.description ? lint.description.replace(/\`/g, '`') : 'N/A'}`,
- })
- }}
- tooltip={{
- content: { side: 'bottom', text: 'Help me fix this issue' },
- }}
- />
-
-
- {lint.detail ? getFirstSentence(lint.detail) : lint.title}
-
-
- )
- })}
-
+ })
+ }}
+ tooltip={{
+ content: { side: 'bottom', text: 'Help me fix this issue' },
+ }}
+ />
+
+
+ {lint.detail ? getFirstSentence(lint.detail) : lint.title}
+
+
+ )
+ })}
+
+ {selectedLint && (
+ setSelectedLint(null)}>
+
+
+
+
+ {lintInfoMap.find((item) => item.name === selectedLint.name)?.title}
+
+
+
+
+
+ {selectedLint && projectRef && (
+
+ )}
+
+
+
+ )}
+ >
) : (
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted.tsx
index 94dc18d425a..1b99b5e8979 100644
--- a/apps/studio/components/interfaces/HomeNew/GettingStarted.tsx
+++ b/apps/studio/components/interfaces/HomeNew/GettingStarted.tsx
@@ -27,6 +27,7 @@ export interface GettingStartedProps {
className?: string
headerRight?: React.ReactNode
emptyContent?: React.ReactNode
+ onDismiss?: () => void
}
export function GettingStarted({
@@ -34,6 +35,7 @@ export function GettingStarted({
className,
headerRight,
emptyContent,
+ onDismiss,
}: GettingStartedProps) {
return (
@@ -41,7 +43,7 @@ export function GettingStarted({
Getting started
{headerRight}
-
+
Dismiss
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStartedSection.tsx b/apps/studio/components/interfaces/HomeNew/GettingStartedSection.tsx
index 4183d49eff1..3354d45756f 100644
--- a/apps/studio/components/interfaces/HomeNew/GettingStartedSection.tsx
+++ b/apps/studio/components/interfaces/HomeNew/GettingStartedSection.tsx
@@ -33,7 +33,15 @@ import {
} from 'ui'
import { BASE_PATH } from 'lib/constants'
-export default function GettingStartedSection() {
+type GettingStartedState = 'empty' | 'code' | 'no-code' | 'hidden'
+
+export default function GettingStartedSection({
+ value,
+ onChange,
+}: {
+ value: GettingStartedState
+ onChange: (v: GettingStartedState) => void
+}) {
const { data: project } = useSelectedProjectQuery()
const { ref } = useParams()
const aiSnap = useAiAssistantStateSnapshot()
@@ -41,7 +49,7 @@ export default function GettingStartedSection() {
// Local state for framework selector preview
const [selectedFramework, setSelectedFramework] = useState(FRAMEWORKS[0]?.key ?? 'nextjs')
- const [workflow, setWorkflow] = useState<'no-code' | 'code' | null>(null)
+ const workflow: 'no-code' | 'code' | null = value === 'code' || value === 'no-code' ? value : null
const { data: tablesData } = useTablesQuery({
projectRef: project?.ref,
@@ -68,6 +76,7 @@ export default function GettingStartedSection() {
description: 'Install the Supabase CLI for local development, migrations, and seeding.',
actions: [
{
+ label: 'Install via npm',
component: (
npm install supabase --save-dev
@@ -466,11 +475,12 @@ export default function GettingStartedSection() {
return (
onChange('hidden')}
headerRight={
setWorkflow((v as 'no-code' | 'code') ?? null)}
+ onValueChange={(v) => v && onChange(v as 'no-code' | 'code')}
>
-
+
Choose a preferred workflow
-
+
With Supabase, you have the flexibility to adopt a workflow that works for you. You
can do everything via the dashboard, or manage your entire project within your own
codebase.
-
+
setWorkflow('no-code')}
- className="block gap-2 h-auto p-8 w-80 text-left justify-start bg-background "
+ onClick={() => onChange('no-code')}
+ className="block gap-2 h-auto p-4 md:p-8 max-w-80 text-left justify-start bg-background "
>
@@ -533,8 +543,8 @@ export default function GettingStartedSection() {
setWorkflow('code')}
- className="bg-background block gap-2 h-auto p-8 w-80 text-left justify-start"
+ onClick={() => onChange('code')}
+ className="bg-background block gap-2 h-auto p-4 md:p-8 max-w-80 text-left justify-start"
>
diff --git a/apps/studio/components/interfaces/HomeNew/ProjectUsage.tsx b/apps/studio/components/interfaces/HomeNew/ProjectUsage.tsx
index 921bb85e703..dc54a2294c9 100644
--- a/apps/studio/components/interfaces/HomeNew/ProjectUsage.tsx
+++ b/apps/studio/components/interfaces/HomeNew/ProjectUsage.tsx
@@ -90,6 +90,16 @@ const ProjectUsage = () => {
}
}, [selectedInterval]) // Only recalculate when interval changes
+ // Compute the previous period range for deltas
+ const { previousStart, previousEnd } = useMemo(() => {
+ const currentStart = dayjs(timestampStart)
+ const currentEnd = dayjs(timestampEnd)
+ const durationMs = currentEnd.diff(currentStart)
+ const prevEnd = currentStart
+ const prevStart = currentStart.subtract(durationMs, 'millisecond')
+ return { previousStart: prevStart.toISOString(), previousEnd: prevEnd.toISOString() }
+ }, [timestampStart, timestampEnd])
+
// Fetch per-service event chart data
const dbStats = useProjectUsageStats({
projectRef: projectRef as string,
@@ -126,6 +136,42 @@ const ProjectUsage = () => {
timestampEnd,
})
+ // Fetch previous period data for deltas
+ const dbStatsPrev = useProjectUsageStats({
+ projectRef: projectRef as string,
+ table: LogsTableName.EDGE,
+ timestampStart: previousStart,
+ timestampEnd: previousEnd,
+ })
+
+ const fnEdgeStatsPrev = useProjectUsageStats({
+ projectRef: projectRef as string,
+ table: LogsTableName.FN_EDGE,
+ timestampStart: previousStart,
+ timestampEnd: previousEnd,
+ })
+
+ const authStatsPrev = useProjectUsageStats({
+ projectRef: projectRef as string,
+ table: LogsTableName.AUTH,
+ timestampStart: previousStart,
+ timestampEnd: previousEnd,
+ })
+
+ const storageStatsPrev = useProjectUsageStats({
+ projectRef: projectRef as string,
+ table: LogsTableName.EDGE,
+ timestampStart: previousStart,
+ timestampEnd: previousEnd,
+ })
+
+ const realtimeStatsPrev = useProjectUsageStats({
+ projectRef: projectRef as string,
+ table: LogsTableName.REALTIME,
+ timestampStart: previousStart,
+ timestampEnd: previousEnd,
+ })
+
// Transform eventChartData into LogsBarChart format
type LogsBarChartDatum = {
timestamp: string
@@ -243,8 +289,39 @@ const ProjectUsage = () => {
const totalErrors = enabledServices.reduce((sum, s) => sum + (s.err || 0), 0)
const errorRate = totalRequests > 0 ? (totalErrors / totalRequests) * 100 : 0
- const totalRequestsChangePct = 0
- const errorRateChangePct = 0
+ // Compute previous totals for enabled services
+ const prevServiceTotals = [
+ { enabled: true, stats: dbStatsPrev },
+ { enabled: true, stats: fnEdgeStatsPrev },
+ { enabled: authEnabled, stats: authStatsPrev },
+ { enabled: storageEnabled, stats: storageStatsPrev },
+ { enabled: true, stats: realtimeStatsPrev },
+ ].map((entry) => {
+ const data = toLogsBarChartData(entry.stats.eventChartData)
+ return {
+ enabled: entry.enabled,
+ total: sumTotal(data || []),
+ err: sumErrors(data || []),
+ }
+ })
+
+ const enabledPrev = prevServiceTotals.filter((s) => s.enabled)
+ const prevTotalRequests = enabledPrev.reduce((sum, s) => sum + (s.total || 0), 0)
+ const prevTotalErrors = enabledPrev.reduce((sum, s) => sum + (s.err || 0), 0)
+ const prevErrorRate = prevTotalRequests > 0 ? (prevTotalErrors / prevTotalRequests) * 100 : 0
+
+ const totalRequestsChangePct =
+ prevTotalRequests === 0
+ ? totalRequests > 0
+ ? 100
+ : 0
+ : ((totalRequests - prevTotalRequests) / prevTotalRequests) * 100
+ const errorRateChangePct =
+ prevErrorRate === 0
+ ? errorRate > 0
+ ? 100
+ : 0
+ : ((errorRate - prevErrorRate) / prevErrorRate) * 100
const formatDelta = (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(1)}%`
const totalDeltaClass = totalRequestsChangePct >= 0 ? 'text-brand' : 'text-destructive'
const errorDeltaClass = errorRateChangePct <= 0 ? 'text-brand' : 'text-destructive'
@@ -325,14 +402,34 @@ const ProjectUsage = () => {
{enabledServices.map((s) => (
-
+
+
+
+
+
+ {s.href ? {s.title} : s.title}
+
+
+
{(s.total || 0).toLocaleString()}
+
+
+
+
+
+
{(s.warn || 0).toLocaleString()}
+
+
+
+
{(s.err || 0).toLocaleString()}
+
+
+
{
)
}
export default ProjectUsage
-
-const PanelHeader = ({
- icon,
- title,
- href,
- total,
- warn,
- err,
-}: {
- icon?: React.ReactNode
- title: string
- href?: string
- total?: number
- warn?: number
- err?: number
-}) => (
-
-
-
-
-
- {href ? {title} : title}
-
-
-
{(total || 0).toLocaleString()}
-
-
-
-
-
-
{(warn || 0).toLocaleString()}
-
-
-
-
{(err || 0).toLocaleString()}
-
-
-
-)
diff --git a/apps/studio/components/interfaces/HomeNew/TopSection.tsx b/apps/studio/components/interfaces/HomeNew/TopSection.tsx
index cf1068d244e..9ebc7fdcb84 100644
--- a/apps/studio/components/interfaces/HomeNew/TopSection.tsx
+++ b/apps/studio/components/interfaces/HomeNew/TopSection.tsx
@@ -9,6 +9,7 @@ import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import { InlineLink } from 'components/ui/InlineLink'
import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner'
import { cn, Badge, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
+import InstanceConfiguration from '../Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration'
interface TopSectionProps {
projectName: string
@@ -30,78 +31,66 @@ const TopSection = ({
organization,
isPaused,
}: TopSectionProps) => {
- const [graphLoaded, setGraphLoaded] = useState(false)
-
return (
-
-
-
-
-
-
- {!isMainBranch && (
-
- {parentProject?.name}
-
- )}
-
{projectName}
-
-
- {isOrioleDb && (
-
-
- OrioleDB
-
-
- This project is using Postgres with OrioleDB which is currently in preview and
- not suitable for production workloads. View our{' '}
-
- documentation
- {' '}
- for all limitations.
-
-
- )}
-
-
+
+
+
+
+
+ {!isMainBranch && (
+
+ {parentProject?.name}
+
+ )}
+
{projectName}
-
-
+
+ {isOrioleDb && (
+
+
+ OrioleDB
+
+
+ This project is using Postgres with OrioleDB which is currently in preview and
+ not suitable for production workloads. View our{' '}
+
+ documentation
+ {' '}
+ for all limitations.
+
+
+ )}
+
-
-
-
- setGraphLoaded(true)} />
-
-
-
-
-
+
+
+
-
- {isPaused &&
}
+
+ {isPaused &&
}
)
}
diff --git a/apps/studio/components/interfaces/Linter/LintDetail.tsx b/apps/studio/components/interfaces/Linter/LintDetail.tsx
new file mode 100644
index 00000000000..777baf24850
--- /dev/null
+++ b/apps/studio/components/interfaces/Linter/LintDetail.tsx
@@ -0,0 +1,84 @@
+import Link from 'next/link'
+import ReactMarkdown from 'react-markdown'
+
+import { lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
+import { EntityTypeIcon, LintCTA, LintCategoryBadge, LintEntity } from './Linter.utils'
+import { Lint } from 'data/lint/lint-query'
+import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
+import { AiIconAnimation, Button } from 'ui'
+import { ExternalLink } from 'lucide-react'
+
+interface LintDetailProps {
+ lint: Lint
+ projectRef: string
+}
+
+const LintDetail = ({ lint, projectRef }: LintDetailProps) => {
+ const snap = useAiAssistantStateSnapshot()
+
+ return (
+
+
Entity
+
+
+
+
+
+
Issue
+
+ {lint.detail.replace(/\`/g, '`')}
+
+
Description
+
+ {lint.description.replace(/\`/g, '`')}
+
+
+
Resolve
+
+ }
+ onClick={() => {
+ snap.newChat({
+ name: 'Summarize lint',
+ open: true,
+ initialInput: `Summarize the issue and suggest fixes: ${
+ lintInfoMap.find((item) => item.name === lint.name)?.title
+ }
+ \nEntity: ${
+ (lint.metadata &&
+ (lint.metadata.entity ||
+ (lint.metadata.schema &&
+ lint.metadata.name &&
+ `${lint.metadata.schema}.${lint.metadata.name}`))) ??
+ ''
+ }
+ \nSchema: ${lint.metadata?.schema ?? ''}
+ \nIssue: ${lint.detail.replace(/\\`/g, '`')}
+ \nDescription: ${lint.description.replace(/\\`/g, '`')}\n`,
+ })
+ }}
+ >
+ Ask Assistant
+
+
+
+ item.name === lint.name)?.docsLink ||
+ 'https://supabase.com/docs/guides/database/database-linter'
+ }
+ target="_blank"
+ rel="noreferrer"
+ className="no-underline"
+ >
+
+ Learn more
+
+
+
+
+
+ )
+}
+
+export default LintDetail
diff --git a/apps/studio/components/interfaces/Linter/LinterDataGrid.tsx b/apps/studio/components/interfaces/Linter/LinterDataGrid.tsx
index a8c8873ecb3..00e7d43d5ce 100644
--- a/apps/studio/components/interfaces/Linter/LinterDataGrid.tsx
+++ b/apps/studio/components/interfaces/Linter/LinterDataGrid.tsx
@@ -1,17 +1,19 @@
-import { ExternalLink, X } from 'lucide-react'
-import Link from 'next/link'
+import { X } from 'lucide-react'
import { useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import ReactMarkdown from 'react-markdown'
import { useParams } from 'common'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
-import { LintEntity, NoIssuesFound, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
+import {
+ LintCategoryBadge,
+ LintEntity,
+ NoIssuesFound,
+ lintInfoMap,
+} from 'components/interfaces/Linter/Linter.utils'
import { Lint } from 'data/lint/lint-query'
import { useRouter } from 'next/router'
-import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import {
- AiIconAnimation,
Button,
ResizableHandle,
ResizablePanel,
@@ -23,7 +25,8 @@ import {
cn,
} from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
-import { EntityTypeIcon, LintCTA, LintCategoryBadge } from './Linter.utils'
+import { EntityTypeIcon } from './Linter.utils'
+import LintDetail from './LintDetail'
interface LinterDataGridProps {
isLoading: boolean
@@ -43,7 +46,6 @@ const LinterDataGrid = ({
const gridRef = useRef
(null)
const { ref } = useParams()
const router = useRouter()
- const snap = useAiAssistantStateSnapshot()
const [view, setView] = useState<'details' | 'suggestion'>('details')
@@ -181,109 +183,18 @@ const LinterDataGrid = ({
<>
- }
- onClick={handleSidepanelClose}
- />
-
- {
- setView(value)
- }}
- >
-
-
- Overview
-
-
-
- {selectedLint && (
-
-
-
- {lintInfoMap.find((item) => item.name === selectedLint.name)?.title}
-
-
-
-
-
-
-
-
Issue
-
- {selectedLint.detail.replace(/\\`/g, '`')}
-
-
-
-
Description
-
- {selectedLint.description.replace(/\\`/g, '`')}
-
-
-
-
-
Resolve
-
- }
- onClick={() => {
- snap.newChat({
- name: 'Summarize lint',
- open: true,
- initialInput: `Summarize the issue and suggest fixes: ${lintInfoMap.find((item) => item.name === selectedLint.name)?.title}
- \nEntity: ${(selectedLint.metadata && (selectedLint.metadata.entity || (selectedLint.metadata.schema && selectedLint.metadata.name && `${selectedLint.metadata.schema}.${selectedLint.metadata.name}`))) ?? ''}
- \nSchema: ${selectedLint.metadata?.schema ?? ''}
- \nIssue: ${selectedLint.detail.replace(/\\`/g, '`')}
- \nDescription: ${selectedLint.description.replace(/\\`/g, '`')}\n`,
- })
- }}
- >
- Ask Assistant
-
-
-
- item.name === selectedLint.name)
- ?.docsLink ||
- 'https://supabase.com/docs/guides/database/database-linter'
- }
- target="_blank"
- rel="noreferrer"
- className="no-underline"
- >
-
- Learn more
-
-
-
-
-
-
-
- )}
-
-
+
+
+
+ {lintInfoMap.find((item) => item.name === selectedLint.name)?.title}
+
+
+
+
} onClick={handleSidepanelClose} />
+
+
+
+
>
)}
diff --git a/apps/studio/pages/project/[ref]/home.tsx b/apps/studio/pages/project/[ref]/home.tsx
index 8d5f9430b88..55f6797e22b 100644
--- a/apps/studio/pages/project/[ref]/home.tsx
+++ b/apps/studio/pages/project/[ref]/home.tsx
@@ -1,5 +1,5 @@
import dayjs from 'dayjs'
-import { useEffect, useRef, useState } from 'react'
+import { useEffect, useRef } from 'react'
import { useParams } from 'common'
import { useBranchesQuery } from 'data/branches/branches-query'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
@@ -12,14 +12,7 @@ import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import type { NextPageWithLayout } from 'types'
-import {
- DndContext,
- DragEndEvent,
- DragStartEvent,
- PointerSensor,
- useSensor,
- useSensors,
-} from '@dnd-kit/core'
+import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable'
import DefaultLayout from 'components/layouts/DefaultLayout'
@@ -30,6 +23,7 @@ import AdvisorSection from 'components/interfaces/HomeNew/AdvisorSection'
import CustomReportSection from 'components/interfaces/HomeNew/CustomReportSection'
import SortableSection from 'components/interfaces/HomeNew/SortableSection'
import { ProjectUsageSection } from 'components/interfaces/HomeNew/ProjectUsageSection'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
const Home: NextPageWithLayout = () => {
const { data: project } = useSelectedProjectQuery()
@@ -70,12 +64,11 @@ const Home: NextPageWithLayout = () => {
['getting-started', 'usage', 'advisor', 'custom-report']
)
- const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
- const [activeId, setActiveId] = useState(null)
+ const [gettingStartedState, setGettingStartedState] = useLocalStorage<
+ 'empty' | 'code' | 'no-code' | 'hidden'
+ >(`home-getting-started-${project?.ref || 'default'}`, 'empty')
- const handleDragStart = (event: DragStartEvent) => {
- setActiveId(String(event.active.id))
- }
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
@@ -86,32 +79,43 @@ const Home: NextPageWithLayout = () => {
if (oldIndex === -1 || newIndex === -1) return items
return arrayMove(items, oldIndex, newIndex)
})
- setActiveId(null)
}
return (
-
+
+
+
+
+
{!isPaused && (
-
-
-
-
+
+
+
+ id !== 'getting-started' || gettingStartedState !== 'hidden'
+ )}
+ strategy={verticalListSortingStrategy}
+ >
{sectionOrder.map((id) => {
if (id === 'getting-started') {
- return (
+ return gettingStartedState === 'hidden' ? null : (
-
+
)
}
@@ -140,8 +144,8 @@ const Home: NextPageWithLayout = () => {
})}
-
-
+
+
)}
)