diff --git a/apps/studio/components/grid/SupabaseGrid.tsx b/apps/studio/components/grid/SupabaseGrid.tsx
index b468f085d191d..44eaa5de10fe4 100644
--- a/apps/studio/components/grid/SupabaseGrid.tsx
+++ b/apps/studio/components/grid/SupabaseGrid.tsx
@@ -16,7 +16,7 @@ import { Shortcuts } from './components/common/Shortcuts'
import { Footer } from './components/footer/Footer'
import { Grid } from './components/grid/Grid'
import { Header, HeaderProps } from './components/header/Header'
-import { RowContextMenu } from './components/menu'
+import { RowContextMenu } from './components/menu/RowContextMenu'
import { GridProps } from './types'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
diff --git a/apps/studio/components/grid/components/menu/RowContextMenu.tsx b/apps/studio/components/grid/components/menu/RowContextMenu.tsx
index 4460cc0314019..285bc1ecaa0d3 100644
--- a/apps/studio/components/grid/components/menu/RowContextMenu.tsx
+++ b/apps/studio/components/grid/components/menu/RowContextMenu.tsx
@@ -1,4 +1,4 @@
-import { Clipboard, Edit, Trash } from 'lucide-react'
+import { Copy, Edit, Trash } from 'lucide-react'
import { useCallback } from 'react'
import { Item, ItemParams, Menu } from 'react-contexify'
import { toast } from 'sonner'
@@ -14,7 +14,7 @@ export type RowContextMenuProps = {
rows: SupaRow[]
}
-const RowContextMenu = ({ rows }: RowContextMenuProps) => {
+export const RowContextMenu = ({ rows }: RowContextMenuProps) => {
const tableEditorSnap = useTableEditorStateSnapshot()
const snap = useTableEditorTableStateSnapshot()
@@ -68,11 +68,11 @@ const RowContextMenu = ({ rows }: RowContextMenuProps) => {
return (
)
}
-export default RowContextMenu
diff --git a/apps/studio/components/grid/components/menu/index.ts b/apps/studio/components/grid/components/menu/index.ts
index cd53af00a3334..a09f947f19c04 100644
--- a/apps/studio/components/grid/components/menu/index.ts
+++ b/apps/studio/components/grid/components/menu/index.ts
@@ -1,2 +1 @@
export { default as ColumnMenu } from './ColumnMenu'
-export { default as RowContextMenu } from './RowContextMenu'
diff --git a/apps/studio/components/interfaces/Auth/Users/Users.utils.tsx b/apps/studio/components/interfaces/Auth/Users/Users.utils.tsx
index ca935717c1d48..e67174d8bef86 100644
--- a/apps/studio/components/interfaces/Auth/Users/Users.utils.tsx
+++ b/apps/studio/components/interfaces/Auth/Users/Users.utils.tsx
@@ -1,5 +1,5 @@
import dayjs from 'dayjs'
-import { Clipboard, Trash, UserIcon } from 'lucide-react'
+import { Copy, Trash, UserIcon } from 'lucide-react'
import { Column, useRowSelection } from 'react-data-grid'
import { User } from 'data/auth/users-infinite-query'
@@ -386,7 +386,7 @@ export const formatUserColumns = ({
copyToClipboard(value)
}}
>
-
:
}
+ icon={copied ?
:
}
onClick={() => {
if (tables) {
copyToClipboard(tablesToSQL(tables))
diff --git a/apps/studio/components/interfaces/Functions/CommandRender.tsx b/apps/studio/components/interfaces/Functions/CommandRender.tsx
index 8285ce8b3c2b0..df92d2c8fed91 100644
--- a/apps/studio/components/interfaces/Functions/CommandRender.tsx
+++ b/apps/studio/components/interfaces/Functions/CommandRender.tsx
@@ -1,4 +1,4 @@
-import { Check, Clipboard } from 'lucide-react'
+import { Check, Copy } from 'lucide-react'
import { forwardRef, useState } from 'react'
import { cn, copyToClipboard } from 'ui'
@@ -45,7 +45,7 @@ const Command = ({ item }: any) => {
{isCopied ? (
) : (
-
+
)}
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
index b7a152514bebb..9056d0f55ccb5 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
@@ -1,5 +1,5 @@
import dayjs from 'dayjs'
-import { Check, Clipboard } from 'lucide-react'
+import { Check, Copy } from 'lucide-react'
import { useRouter } from 'next/router'
import { useState } from 'react'
@@ -66,7 +66,7 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP
) : (
)}
diff --git a/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx b/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx
index 65858938c2637..8d2e466e4b091 100644
--- a/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx
+++ b/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx
@@ -2,6 +2,7 @@ import { BarChart, Shield } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import { useParams } from 'common'
+import LintDetail from 'components/interfaces/Linter/LintDetail'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import {
createLintSummaryPrompt,
@@ -28,7 +29,6 @@ import {
} from 'ui'
import { Row } from 'ui-patterns'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
-import LintDetail from 'components/interfaces/Linter/LintDetail'
export const AdvisorSection = () => {
const { ref: projectRef } = useParams()
diff --git a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx
index 8737860d1dd96..ad0e12284ad4a 100644
--- a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx
+++ b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx
@@ -50,9 +50,11 @@ export function CustomReportSection() {
reportContent
)
- useEffect(() => {
- if (reportContent) setEditableReport(reportContent)
- }, [reportContent])
+ const { can: canCreateReport } = useAsyncCheckPermissions(
+ PermissionAction.CREATE,
+ 'user_content',
+ { resource: { type: 'report', owner_id: profile?.id }, subject: { id: profile?.id } }
+ )
const { can: canUpdateReport } = useAsyncCheckPermissions(
PermissionAction.UPDATE,
@@ -67,12 +69,6 @@ export function CustomReportSection() {
}
)
- const { can: canCreateReport } = useAsyncCheckPermissions(
- PermissionAction.CREATE,
- 'user_content',
- { resource: { type: 'report', owner_id: profile?.id }, subject: { id: profile?.id } }
- )
-
const { mutate: upsertContent } = useContentUpsertMutation()
const persistReport = useCallback(
@@ -275,10 +271,14 @@ export function CustomReportSection() {
const layout = useMemo(() => editableReport?.layout ?? [], [editableReport])
+ useEffect(() => {
+ if (reportContent) setEditableReport(reportContent)
+ }, [reportContent])
+
return (
-
At a glance
+
Reports
{canUpdateReport || canCreateReport ? (
- {(() => {
- if (layout.length === 0) {
- return (
-
- {canUpdateReport || canCreateReport ? (
-
}>
- Add your first chart
-
- }
- side="bottom"
- align="center"
- autoFocus
- />
- ) : (
-
No charts set up yet in report
- )}
-
- )
- }
- return (
-
+ Build a custom report
+
+ Keep track of your most important metrics
+
+ {canUpdateReport || canCreateReport ? (
+ }>
+ Add your first block
+
+ }
+ side="bottom"
+ align="center"
+ autoFocus
+ />
+ ) : (
+ No charts set up yet in report
+ )}
+
+ ) : (
+
+ String(x.id))}
+ strategy={rectSortingStrategy}
>
- String(x.id))}
- strategy={rectSortingStrategy}
- >
-
- {layout.map((item) => (
-
-
- handleUpdateChart(item.id, config)}
- />
-
-
- ))}
-
-
-
- )
- })()}
+
+ {layout.map((item) => (
+
+
+ handleUpdateChart(item.id, config)}
+ />
+
+
+ ))}
+
+
+
+ )}
)
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx
index b07d0819f5685..fbdfe7aa41f0f 100644
--- a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx
+++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx
@@ -2,10 +2,9 @@ import { Check, ChevronLeft, ChevronRight } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useEffect, useState } from 'react'
-
+import { cn, Button, Card, CardContent, Badge } from 'ui'
+import { GettingStartedStep, GettingStartedAction } from './GettingStarted.types'
import { BASE_PATH } from 'lib/constants'
-import { Badge, Button, Card, CardContent, cn } from 'ui'
-import { GettingStartedAction, GettingStartedStep } from './GettingStartedSection'
// Determine action type for tracking
const getActionType = (action: GettingStartedAction): 'primary' | 'ai_assist' | 'external_link' => {
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.types.ts b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.types.ts
new file mode 100644
index 0000000000000..e1a6ca55ea82b
--- /dev/null
+++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.types.ts
@@ -0,0 +1,24 @@
+import { ComponentProps, ReactNode } from 'react'
+
+import { Button } from 'ui'
+
+export type GettingStartedAction = {
+ label: string
+ href?: string
+ variant?: ComponentProps
['type']
+ icon?: ReactNode
+ component?: ReactNode
+ onClick?: () => void
+}
+
+export type GettingStartedStep = {
+ key: string
+ status: 'complete' | 'incomplete'
+ icon?: ReactNode
+ title: string
+ description: string
+ image?: string
+ actions: GettingStartedAction[]
+}
+
+export type GettingStartedState = 'empty' | 'code' | 'no-code' | 'hidden'
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.utils.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.utils.tsx
new file mode 100644
index 0000000000000..a5eb527201034
--- /dev/null
+++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.utils.tsx
@@ -0,0 +1,412 @@
+import {
+ BarChart3,
+ Code,
+ Database,
+ GitBranch,
+ Shield,
+ Table,
+ Upload,
+ User,
+ UserPlus,
+} from 'lucide-react'
+
+import { FRAMEWORKS } from 'components/interfaces/Connect/Connect.constants'
+import { AiIconAnimation, CodeBlock } from 'ui'
+import type { GettingStartedAction, GettingStartedStep } from './GettingStarted.types'
+import type { GettingStartedStatuses } from './useGettingStartedProgress'
+
+type BuildStepsBaseArgs = {
+ ref: string | undefined
+ openAiChat: (name: string, initialInput: string) => void
+ connectActions: GettingStartedAction[]
+ statuses: GettingStartedStatuses
+}
+
+type BuildCodeStepsArgs = BuildStepsBaseArgs
+
+type BuildNoCodeStepsArgs = BuildStepsBaseArgs
+
+export const getCodeWorkflowSteps = ({
+ ref,
+ openAiChat,
+ connectActions,
+ statuses,
+}: BuildCodeStepsArgs): GettingStartedStep[] => {
+ const {
+ hasTables,
+ hasCliSetup,
+ hasSampleData,
+ hasRlsPolicies,
+ hasConfiguredAuth,
+ hasAppConnected,
+ hasFirstUser,
+ hasStorageObjects,
+ hasEdgeFunctions,
+ hasReports,
+ hasGitHubConnection,
+ } = statuses
+
+ return [
+ {
+ key: 'install-cli',
+ status: hasCliSetup ? 'complete' : 'incomplete',
+ title: 'Install the Supabase CLI',
+ icon: ,
+ description:
+ 'To get started, install the Supabase CLI—our command-line toolkit for managing projects locally, handling migrations, and seeding data—using the npm command below to add it to your workspace.',
+ actions: [
+ {
+ label: 'Install via npm',
+ component: (
+
+ npm install supabase --save-dev
+
+ ),
+ },
+ ],
+ },
+ {
+ key: 'design-db',
+ status: hasTables ? 'complete' : 'incomplete',
+ title: 'Design your database schema',
+ icon: ,
+ description:
+ 'Next, create a schema file that defines the structure of your database, either following our declarative schema guide or asking the AI assistant to generate one for you.',
+ actions: [
+ {
+ label: 'Create schema file',
+ href: 'https://supabase.com/docs/guides/local-development/declarative-database-schemas',
+ variant: 'default',
+ },
+ {
+ label: 'Generate it',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Design my database',
+ 'Help me create a schema file for my database. We will be using Supabase declarative schemas which you can learn about by searching docs for declarative schema.'
+ ),
+ },
+ ],
+ },
+ {
+ key: 'add-data',
+ status: hasSampleData ? 'complete' : 'incomplete',
+ title: 'Seed your database with data',
+ icon: ,
+ description:
+ 'Now, create a seed file to populate your database with initial data, using the docs for guidance or letting the AI assistant draft realistic inserts.',
+ actions: [
+ {
+ label: 'Create a seed file',
+ href: 'https://supabase.com/docs/guides/local-development/seeding-your-database',
+ variant: 'default',
+ },
+ {
+ label: 'Generate data',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Generate seed data',
+ 'Generate SQL INSERT statements for realistic seed data that I can run via the Supabase CLI.'
+ ),
+ },
+ ],
+ },
+ {
+ key: 'add-rls-policies',
+ status: hasRlsPolicies ? 'complete' : 'incomplete',
+ title: 'Secure your data with RLS policies',
+ icon: ,
+ description:
+ "Let's secure your data by enabling Row Level Security (per-row access rules that decide who can read or write specific records) and defining policies in a migration file, either configuring them manually or letting the AI assistant draft policies for your tables.",
+ actions: [
+ {
+ label: 'Create a migration file',
+ href: `/project/${ref}/auth/policies`,
+ variant: 'default',
+ },
+ {
+ label: 'Create policies for me',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Generate RLS policies',
+ 'Generate RLS policies for my existing tables in the public schema and guide me through the process of adding them as migration files to my codebase '
+ ),
+ },
+ ],
+ },
+ {
+ key: 'setup-auth',
+ status: hasConfiguredAuth ? 'complete' : 'incomplete',
+ title: 'Allow user signups',
+ icon: ,
+ description:
+ "It's time to configure your authentication providers and settings for Supabase Auth, so jump into the configuration page and tailor the providers you need.",
+ actions: [{ label: 'Configure', href: `/project/${ref}/auth/providers`, variant: 'default' }],
+ },
+ {
+ key: 'connect-app',
+ status: hasAppConnected ? 'complete' : 'incomplete',
+ title: 'Connect your application',
+ icon: ,
+ description:
+ 'Your project is ready; use the framework selector to preview starter code and launch the Connect flow with the client library you prefer.',
+ actions: connectActions,
+ },
+ {
+ key: 'signup-first-user',
+ status: hasFirstUser ? 'complete' : 'incomplete',
+ title: 'Sign up your first user',
+ icon: ,
+ description:
+ 'Test your authentication setup by creating the first user account, following the docs if you need a step-by-step walkthrough.',
+ actions: [
+ {
+ label: 'Read docs',
+ href: 'https://supabase.com/docs/guides/auth',
+ variant: 'default',
+ },
+ ],
+ },
+ {
+ key: 'upload-file',
+ status: hasStorageObjects ? 'complete' : 'incomplete',
+ title: 'Upload a file',
+ icon: ,
+ description:
+ 'Integrate file storage by creating a bucket via SQL and uploading a file using our client libraries.',
+ actions: [
+ {
+ label: 'Create a bucket via SQL',
+ href: 'https://supabase.com/docs/guides/storage/buckets/creating-buckets?queryGroups=language&language=sql',
+ variant: 'default',
+ },
+ {
+ label: 'Upload a file',
+ href: 'https://supabase.com/docs/guides/storage/uploads/standard-uploads',
+ variant: 'default',
+ },
+ ],
+ },
+ {
+ key: 'create-edge-function',
+ status: hasEdgeFunctions ? 'complete' : 'incomplete',
+ title: 'Deploy an Edge Function',
+ icon: ,
+ description:
+ 'Add server-side logic by creating and deploying your first Edge Function—a lightweight TypeScript or JavaScript function that runs close to your users—then revisit the list to monitor and iterate on it.',
+ actions: [
+ {
+ label: 'Create and deploy via CLI',
+ href: `https://supabase.com/docs/guides/functions/quickstart`,
+ variant: 'default',
+ },
+ { label: 'View functions', href: `/project/${ref}/functions`, variant: 'default' },
+ ],
+ },
+ {
+ key: 'monitor-progress',
+ status: hasReports ? 'complete' : 'incomplete',
+ title: "Monitor your project's usage",
+ icon: ,
+ description:
+ "Track your project's activity by creating custom reports for API, database, and auth events right from the reports dashboard.",
+ actions: [{ label: 'Reports', href: `/project/${ref}/reports`, variant: 'default' }],
+ },
+ {
+ key: 'connect-github',
+ status: hasGitHubConnection ? 'complete' : 'incomplete',
+ title: 'Connect to GitHub',
+ icon: ,
+ description:
+ 'Link this project to a GitHub repository to keep production in sync and spin up preview branches from pull requests.',
+ actions: [
+ {
+ label: 'Connect to GitHub',
+ href: `/project/${ref}/settings/integrations`,
+ variant: 'default',
+ },
+ ],
+ },
+ ]
+}
+
+export const getNoCodeWorkflowSteps = ({
+ ref,
+ openAiChat,
+ connectActions,
+ statuses,
+}: BuildNoCodeStepsArgs): GettingStartedStep[] => {
+ const {
+ hasTables,
+ hasSampleData,
+ hasRlsPolicies,
+ hasConfiguredAuth,
+ hasAppConnected,
+ hasFirstUser,
+ hasStorageObjects,
+ hasEdgeFunctions,
+ hasReports,
+ hasGitHubConnection,
+ } = statuses
+
+ return [
+ {
+ key: 'design-db',
+ status: hasTables ? 'complete' : 'incomplete',
+ title: 'Create your first table',
+ icon: ,
+ description:
+ "To kick off your new project, let's start by creating your very first database table using either the table editor or the AI assistant to shape the structure for you.",
+ actions: [
+ { label: 'Create a table', href: `/project/${ref}/editor`, variant: 'default' },
+ {
+ label: 'Do it for me',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Design my database',
+ 'I want to design my database schema. Please propose tables, relationships, and SQL to create them for my app. Ask clarifying questions if needed.'
+ ),
+ },
+ ],
+ },
+ {
+ key: 'add-data',
+ status: hasSampleData ? 'complete' : 'incomplete',
+ title: 'Add sample data',
+ icon: ,
+ description:
+ "Next, let's add some sample data that you can play with once you connect your app, either by inserting rows yourself or letting the AI assistant craft realistic examples.",
+ actions: [
+ { label: 'Add data', href: `/project/${ref}/editor`, variant: 'default' },
+ {
+ label: 'Do it for me',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Generate sample data',
+ 'Generate SQL INSERT statements to add realistic sample data to my existing tables. Use safe defaults and avoid overwriting data.'
+ ),
+ },
+ ],
+ },
+ {
+ key: 'add-rls-policies',
+ status: hasRlsPolicies ? 'complete' : 'incomplete',
+ title: 'Secure your data with Row Level Security',
+ icon: ,
+ description:
+ "Now that you have some data, let's secure it by enabling Row Level Security (row-specific access rules that control who can view or modify records) and creating policies yourself or with help from the AI assistant.",
+ actions: [
+ {
+ label: 'Create a policy',
+ href: `/project/${ref}/auth/policies`,
+ variant: 'default',
+ },
+ {
+ label: 'Do it for me',
+ variant: 'default',
+ icon: ,
+ onClick: () =>
+ openAiChat(
+ 'Generate RLS policies',
+ 'Generate RLS policies for my existing tables in the public schema. '
+ ),
+ },
+ ],
+ },
+ {
+ key: 'setup-auth',
+ status: hasConfiguredAuth ? 'complete' : 'incomplete',
+ title: 'Allow user signups',
+ icon: ,
+ description:
+ "It's time to set up authentication so you can start signing up users, configuring providers and settings from the auth dashboard.",
+ actions: [
+ { label: 'Configure auth', href: `/project/${ref}/auth/providers`, variant: 'default' },
+ ],
+ },
+ {
+ key: 'connect-app',
+ status: hasAppConnected ? 'complete' : 'incomplete',
+ title: 'Connect your application',
+ icon: ,
+ description:
+ 'Your project is ready; use the framework selector to preview starter code and launch the Connect flow to wire up your app.',
+ actions: connectActions,
+ },
+ {
+ key: 'signup-first-user',
+ status: hasFirstUser ? 'complete' : 'incomplete',
+ title: 'Sign up your first user',
+ icon: ,
+ description:
+ 'Test your authentication by signing up your first user, referencing the docs if you need sample flows or troubleshooting tips.',
+ actions: [
+ {
+ label: 'Read docs',
+ href: 'https://supabase.com/docs/guides/auth',
+ variant: 'default',
+ },
+ ],
+ },
+ {
+ key: 'upload-file',
+ status: hasStorageObjects ? 'complete' : 'incomplete',
+ title: 'Upload a file',
+ icon: ,
+ description:
+ "Let's add file storage to your app by creating a bucket and uploading your first file from the buckets dashboard.",
+ actions: [{ label: 'Buckets', href: `/project/${ref}/storage/buckets`, variant: 'default' }],
+ },
+ {
+ key: 'create-edge-function',
+ status: hasEdgeFunctions ? 'complete' : 'incomplete',
+ title: 'Add server-side logic',
+ icon: ,
+ description:
+ "Extend your app's functionality by creating an Edge Function—a lightweight serverless function that executes close to your users—for server-side logic directly from the functions page.",
+ actions: [
+ {
+ label: 'Create a function',
+ href: `/project/${ref}/functions/new`,
+ variant: 'default',
+ },
+ ],
+ },
+ {
+ key: 'monitor-progress',
+ status: hasReports ? 'complete' : 'incomplete',
+ title: "Monitor your project's health",
+ icon: ,
+ description:
+ "Keep an eye on your project's performance and usage by setting up custom reports from the reports dashboard.",
+ actions: [{ label: 'Create a report', href: `/project/${ref}/reports`, variant: 'default' }],
+ },
+ {
+ key: 'connect-github',
+ status: hasGitHubConnection ? 'complete' : 'incomplete',
+ title: 'Connect to GitHub',
+ icon: ,
+ description:
+ 'Connect your project to GitHub to automatically create preview branches and sync production changes.',
+ actions: [
+ {
+ label: 'Connect to GitHub',
+ href: `/project/${ref}/settings/integrations`,
+ variant: 'default',
+ },
+ ],
+ },
+ ]
+}
+
+export const DEFAULT_FRAMEWORK_KEY = FRAMEWORKS[0]?.key ?? 'nextjs'
diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx
index 4771b751fb664..ba121daa17079 100644
--- a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx
+++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx
@@ -1,99 +1,48 @@
-import {
- BarChart3,
- Code,
- Database,
- GitBranch,
- Shield,
- Table,
- Table2,
- Upload,
- User,
- UserPlus,
-} from 'lucide-react'
+import { Code, Table2 } from 'lucide-react'
import { useRouter } from 'next/router'
import { useCallback, useMemo, useState } from 'react'
import { useParams } from 'common'
import { FRAMEWORKS } from 'components/interfaces/Connect/Connect.constants'
-import { useBranchesQuery } from 'data/branches/branches-query'
-import { useTablesQuery } from 'data/tables/tables-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { BASE_PATH, DOCS_URL } from 'lib/constants'
+import { BASE_PATH } from 'lib/constants'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
-import {
- AiIconAnimation,
- Button,
- Card,
- CardContent,
- CodeBlock,
- ToggleGroup,
- ToggleGroupItem,
-} from 'ui'
+import { Button, Card, CardContent, ToggleGroup, ToggleGroupItem } from 'ui'
import { FrameworkSelector } from './FrameworkSelector'
import { GettingStarted } from './GettingStarted'
+import {
+ GettingStartedAction,
+ GettingStartedState,
+ GettingStartedStep,
+} from './GettingStarted.types'
+import {
+ DEFAULT_FRAMEWORK_KEY,
+ getCodeWorkflowSteps,
+ getNoCodeWorkflowSteps,
+} from './GettingStarted.utils'
+import { useGettingStartedProgress } from './useGettingStartedProgress'
-export type GettingStartedAction = {
- label: string
- href?: string
- onClick?: () => void
- variant?: React.ComponentProps['type']
- icon?: React.ReactNode
- component?: React.ReactNode
-}
-
-export type GettingStartedStep = {
- key: string
- status: 'complete' | 'incomplete'
- icon?: React.ReactNode
- title: string
- description: string
- image?: string
- actions: GettingStartedAction[]
-}
-
-export type GettingStartedState = 'empty' | 'code' | 'no-code' | 'hidden'
-
-export function GettingStartedSection({
- value,
- onChange,
-}: {
+interface GettingStartedSectionProps {
value: GettingStartedState
onChange: (v: GettingStartedState) => void
-}) {
+}
+
+export function GettingStartedSection({ value, onChange }: GettingStartedSectionProps) {
+ const router = useRouter()
+ const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
- const { ref } = useParams()
- const aiSnap = useAiAssistantStateSnapshot()
- const router = useRouter()
const { mutate: sendEvent } = useSendEventMutation()
+ const aiSnap = useAiAssistantStateSnapshot()
- // Local state for framework selector preview
- const [selectedFramework, setSelectedFramework] = useState(FRAMEWORKS[0]?.key ?? 'nextjs')
+ const [selectedFramework, setSelectedFramework] = useState(DEFAULT_FRAMEWORK_KEY)
const workflow: 'no-code' | 'code' | null = value === 'code' || value === 'no-code' ? value : null
const [previousWorkflow, setPreviousWorkflow] = useState<'no-code' | 'code' | null>(null)
- const { data: tablesData } = useTablesQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- schema: 'public',
- })
-
- const tablesCount = Math.max(0, tablesData?.length ?? 0)
- const { data: branchesData } = useBranchesQuery({
- projectRef: project?.parent_project_ref ?? project?.ref,
- })
- const isDefaultProject = project?.parent_project_ref === undefined
- const hasNonDefaultBranch =
- (branchesData ?? []).some((b) => !b.is_default) || isDefaultProject === false
+ const statuses = useGettingStartedProgress()
- const selectedFrameworkMeta = useMemo(
- () => FRAMEWORKS.find((item) => item.key === selectedFramework),
- [selectedFramework]
- )
-
- // Helpers
const openAiChat = useCallback(
(name: string, initialInput: string) => aiSnap.newChat({ name, open: true, initialInput }),
[aiSnap]
@@ -133,354 +82,29 @@ export function GettingStartedSection({
onClick: openConnect,
},
],
- [openConnect, openAiChat, selectedFramework, selectedFrameworkMeta?.label]
+ [openConnect, selectedFramework]
)
const codeSteps: GettingStartedStep[] = useMemo(
- () => [
- {
- key: 'install-cli',
- status: 'incomplete',
- title: 'Install the Supabase CLI',
- icon: ,
- description:
- 'To get started, install the Supabase CLI—our command-line toolkit for managing projects locally, handling migrations, and seeding data—using the npm command below to add it to your workspace.',
- actions: [
- {
- label: 'Install via npm',
- component: (
-
- npm install supabase --save-dev
-
- ),
- },
- ],
- },
- {
- key: 'design-db',
- status: tablesCount > 0 ? 'complete' : 'incomplete',
- title: 'Design your database schema',
- icon: ,
- image: `${BASE_PATH}/img/getting-started/declarative-schemas.png`,
- description:
- 'Next, create a schema file that defines the structure of your database, either following our declarative schema guide or asking the AI assistant to generate one for you.',
- actions: [
- {
- label: 'Create schema file',
- href: `${DOCS_URL}/guides/local-development/declarative-database-schemas`,
- variant: 'default',
- },
- {
- label: 'Generate it',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Design my database',
- 'Help me create a schema file for my database. We will be using Supabase declarative schemas which you can learn about by searching docs for declarative schema.'
- ),
- },
- ],
- },
- {
- key: 'add-data',
- status: 'incomplete',
- title: 'Seed your database with data',
- icon: ,
- description:
- 'Now, create a seed file to populate your database with initial data, using the docs for guidance or letting the AI assistant draft realistic inserts.',
- actions: [
- {
- label: 'Create a seed file',
- href: `${DOCS_URL}/guides/local-development/seeding-your-database`,
- variant: 'default',
- },
- {
- label: 'Generate data',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Generate seed data',
- 'Generate SQL INSERT statements for realistic seed data that I can run via the Supabase CLI.'
- ),
- },
- ],
- },
- {
- key: 'add-rls-policies',
- status: 'incomplete',
- title: 'Secure your data with RLS policies',
- icon: ,
- description:
- "Let's secure your data by enabling Row Level Security (per-row access rules that decide who can read or write specific records) and defining policies in a migration file, either configuring them manually or letting the AI assistant draft policies for your tables.",
- actions: [
- {
- label: 'Create a migration file',
- href: `/project/${ref}/auth/policies`,
- variant: 'default',
- },
- {
- label: 'Create policies for me',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Generate RLS policies',
- 'Generate RLS policies for my existing tables in the public schema and guide me through the process of adding them as migration files to my codebase '
- ),
- },
- ],
- },
- {
- key: 'setup-auth',
- status: 'incomplete',
- title: 'Configure authentication',
- icon: ,
- description:
- "It's time to configure your authentication providers and settings for Supabase Auth, so jump into the configuration page and tailor the providers you need.",
- actions: [
- { label: 'Configure', href: `/project/${ref}/auth/providers`, variant: 'default' },
- ],
- },
- {
- key: 'connect-app',
- status: 'incomplete',
- title: 'Connect your application',
- icon: ,
- description:
- 'Your project is ready; use the framework selector to preview starter code and launch the Connect flow with the client library you prefer.',
- actions: connectActions,
- },
- {
- key: 'signup-first-user',
- status: 'incomplete',
- title: 'Sign up your first user',
- icon: ,
- description:
- 'Test your authentication setup by creating the first user account, following the docs if you need a step-by-step walkthrough.',
- actions: [
- {
- label: 'Read docs',
- href: `${DOCS_URL}/guides/auth`,
- variant: 'default',
- },
- ],
- },
- {
- key: 'upload-file',
- status: 'incomplete',
- title: 'Upload a file',
- icon: ,
- description:
- 'Integrate file storage by creating a bucket and uploading a file, starting from the buckets dashboard linked below.',
- actions: [
- { label: 'Buckets', href: `/project/${ref}/storage/buckets`, variant: 'default' },
- ],
- },
- {
- key: 'create-edge-function',
- status: 'incomplete',
- title: 'Deploy an Edge Function',
- icon: ,
- description:
- 'Add server-side logic by creating and deploying your first Edge Function—a lightweight TypeScript or JavaScript function that runs close to your users—then revisit the list to monitor and iterate on it.',
- actions: [
- {
- label: 'Create a function',
- href: `/project/${ref}/functions/new`,
- variant: 'default',
- },
- { label: 'View functions', href: `/project/${ref}/functions`, variant: 'default' },
- ],
- },
- {
- key: 'monitor-progress',
- status: 'incomplete',
- title: "Monitor your project's usage",
- icon: ,
- description:
- "Track your project's activity by creating custom reports for API, database, and auth events right from the reports dashboard.",
- actions: [{ label: 'Reports', href: `/project/${ref}/reports`, variant: 'default' }],
- },
- {
- key: 'create-first-branch',
- status: hasNonDefaultBranch ? 'complete' : 'incomplete',
- title: 'Connect to GitHub',
- icon: ,
- description:
- 'Streamline your development workflow by connecting your project to GitHub, using the integrations page to automate branch management.',
- actions: [
- {
- label: 'Connect to GitHub',
- href: `/project/${ref}/settings/integrations`,
- variant: 'default',
- },
- ],
- },
- ],
- [tablesCount, ref, openAiChat, connectActions, hasNonDefaultBranch]
+ () =>
+ getCodeWorkflowSteps({
+ ref,
+ openAiChat,
+ connectActions,
+ statuses,
+ }),
+ [connectActions, openAiChat, ref, statuses]
)
const noCodeSteps: GettingStartedStep[] = useMemo(
- () => [
- {
- key: 'design-db',
- status: tablesCount > 0 ? 'complete' : 'incomplete',
- title: 'Create your first table',
- icon: ,
- image: `${BASE_PATH}/img/getting-started/sample.png`,
- description:
- "To kick off your new project, let's start by creating your very first database table using either the table editor or the AI assistant to shape the structure for you.",
- actions: [
- { label: 'Create a table', href: `/project/${ref}/editor`, variant: 'default' },
- {
- label: 'Do it for me',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Design my database',
- 'I want to design my database schema. Please propose tables, relationships, and SQL to create them for my app. Ask clarifying questions if needed.'
- ),
- },
- ],
- },
- {
- key: 'add-data',
- status: 'incomplete',
- title: 'Add sample data',
- icon: ,
- description:
- "Next, let's add some sample data that you can play with once you connect your app, either by inserting rows yourself or letting the AI assistant craft realistic examples.",
- actions: [
- { label: 'Add data', href: `/project/${ref}/editor`, variant: 'default' },
- {
- label: 'Do it for me',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Generate sample data',
- 'Generate SQL INSERT statements to add realistic sample data to my existing tables. Use safe defaults and avoid overwriting data.'
- ),
- },
- ],
- },
- {
- key: 'add-rls-policies',
- status: 'incomplete',
- title: 'Secure your data with Row Level Security',
- icon: ,
- description:
- "Now that you have some data, let's secure it by enabling Row Level Security (row-specific access rules that control who can view or modify records) and creating policies yourself or with help from the AI assistant.",
- actions: [
- {
- label: 'Create a policy',
- href: `/project/${ref}/auth/policies`,
- variant: 'default',
- },
- {
- label: 'Do it for me',
- variant: 'default',
- icon: ,
- onClick: () =>
- openAiChat(
- 'Generate RLS policies',
- 'Generate RLS policies for my existing tables in the public schema. '
- ),
- },
- ],
- },
- {
- key: 'setup-auth',
- status: 'incomplete',
- title: 'Set up authentication',
- icon: ,
- description:
- "It's time to set up authentication so you can start signing up users, configuring providers and settings from the auth dashboard.",
- actions: [
- {
- label: 'Configure auth',
- href: `/project/${ref}/auth/providers`,
- variant: 'default',
- },
- ],
- },
- {
- key: 'connect-app',
- status: 'incomplete',
- title: 'Connect your application',
- icon: ,
- description:
- 'Your project is ready; use the framework selector to preview starter code and launch the Connect flow to wire up your app.',
- actions: connectActions,
- },
- {
- key: 'signup-first-user',
- status: 'incomplete',
- title: 'Sign up your first user',
- icon: ,
- description:
- 'Test your authentication by signing up your first user, referencing the docs if you need sample flows or troubleshooting tips.',
- actions: [
- {
- label: 'Read docs',
- href: `${DOCS_URL}/guides/auth`,
- variant: 'default',
- },
- ],
- },
- {
- key: 'upload-file',
- status: 'incomplete',
- title: 'Upload a file',
- icon: ,
- description:
- "Let's add file storage to your app by creating a bucket and uploading your first file from the buckets dashboard.",
- actions: [
- { label: 'Buckets', href: `/project/${ref}/storage/buckets`, variant: 'default' },
- ],
- },
- {
- key: 'create-edge-function',
- status: 'incomplete',
- title: 'Add server-side logic',
- icon: ,
- description:
- "Extend your app's functionality by creating an Edge Function—a lightweight serverless function that executes close to your users—for server-side logic directly from the functions page.",
- actions: [
- {
- label: 'Create a function',
- href: `/project/${ref}/functions/new`,
- variant: 'default',
- },
- ],
- },
- {
- key: 'monitor-progress',
- status: 'incomplete',
- title: "Monitor your project's health",
- icon: ,
- description:
- "Keep an eye on your project's performance and usage by setting up custom reports from the reports dashboard.",
- actions: [
- { label: 'Create a report', href: `/project/${ref}/reports`, variant: 'default' },
- ],
- },
- {
- key: 'create-first-branch',
- status: hasNonDefaultBranch ? 'complete' : 'incomplete',
- title: 'Create a branch to test changes',
- icon: ,
- description:
- 'Safely test changes by creating a preview branch before deploying to production, using the branches view to spin one up.',
- actions: [
- { label: 'Create a branch', href: `/project/${ref}/branches`, variant: 'default' },
- ],
- },
- ],
- [tablesCount, ref, openAiChat, connectActions, hasNonDefaultBranch]
+ () =>
+ getNoCodeWorkflowSteps({
+ ref,
+ openAiChat,
+ connectActions,
+ statuses,
+ }),
+ [connectActions, openAiChat, ref, statuses]
)
const steps = workflow === 'code' ? codeSteps : workflow === 'no-code' ? noCodeSteps : []
@@ -519,7 +143,7 @@ export function GettingStartedSection({
className="text-xs gap-2 h-auto"
>
- Code
+ No-code
- No-code
+ Code
}
- arrow={