diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx deleted file mode 100644 index 9ccd9c8d47d..00000000000 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ /dev/null @@ -1,492 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { IS_PLATFORM, useParams } from 'common' -import { ApiKeysTabContent } from 'components/interfaces/Connect/ApiKeysTabContent' -import { DatabaseConnectionString } from 'components/interfaces/Connect/DatabaseConnectionString' -import { McpTabContent } from 'components/interfaces/Connect/McpTabContent' -import Panel from 'components/ui/Panel' -import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' -import { BASE_PATH } from 'lib/constants' -import { ExternalLink } from 'lucide-react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' -import { useEffect, useMemo, useState } from 'react' -import { - Button, - cn, - Dialog, - DIALOG_PADDING_X, - DIALOG_PADDING_Y, - DialogContent, - DialogDescription, - DialogHeader, - DialogSectionSeparator, - DialogTitle, - Tabs_Shadcn_, - TabsContent_Shadcn_, - TabsList_Shadcn_, - TabsTrigger_Shadcn_, -} from 'ui' - -import { CONNECTION_TYPES, ConnectionType, FRAMEWORKS, MOBILES, ORMS } from './Connect.constants' -import { getContentFilePath, inferConnectTabFromParentKey } from './Connect.utils' -import { ConnectDropdown } from './ConnectDropdown' -import { ConnectTabContent } from './ConnectTabContent' -import { useProjectApiUrl } from '@/data/config/project-endpoint-query' - -export const Connect = () => { - const router = useRouter() - const { ref: projectRef } = useParams() - - const { - projectConnectionShowAppFrameworks: showAppFrameworks, - projectConnectionShowMobileFrameworks: showMobileFrameworks, - projectConnectionShowOrms: showOrms, - } = useIsFeatureEnabled([ - 'project_connection:show_app_frameworks', - 'project_connection:show_mobile_frameworks', - 'project_connection:show_orms', - ]) - - const connectionTypes = CONNECTION_TYPES.filter(({ key }) => { - if (key === 'frameworks') { - return showAppFrameworks - } - if (key === 'mobiles') { - return showMobileFrameworks - } - if (key === 'orms') { - return showOrms - } - return true - }) - - // helper to get the connection type object - function getConnectionObjectForTab(tab: string | null) { - switch (tab) { - case 'frameworks': - return FRAMEWORKS - case 'mobiles': - return MOBILES - case 'orms': - return ORMS - default: - return FRAMEWORKS - } - } - - const [open, setOpen] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) - const [tab, setTab] = useQueryState('connectTab', parseAsString.withDefault('direct')) - const [queryFramework, setQueryFramework] = useQueryState('framework', parseAsString) - const [queryUsing, setQueryUsing] = useQueryState('using', parseAsString) - const [queryWith, setQueryWith] = useQueryState('with', parseAsString) - const [, setQueryType] = useQueryState('type', parseAsString) - const [, setQuerySource] = useQueryState('source', parseAsString) - const [, setQueryMethod] = useQueryState('method', parseAsString) - - const [connectionObject, setConnectionObject] = useState(FRAMEWORKS) - const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs - const [selectedChild, setSelectedChild] = useState( - connectionObject.find((item) => item.key === selectedParent)?.children[0]?.key ?? '' - ) - const [selectedGrandchild, setSelectedGrandchild] = useState( - connectionObject - .find((item) => item.key === selectedParent) - ?.children.find((child) => child.key === selectedChild)?.children[0]?.key || '' - ) - - const { data: resolvedEndpoint } = useProjectApiUrl({ projectRef }) - - const { can: canReadAPIKeys } = useAsyncCheckPermissions( - PermissionAction.READ, - 'service_api_keys' - ) - - const handleParentChange = (value: string) => { - setSelectedParent(value) - setQueryFramework(value) - - const parent = connectionObject.find((item) => item.key === value) - const firstChild = parent?.children?.[0] - - if (firstChild) { - setSelectedChild(firstChild.key) - setQueryUsing(firstChild.key) - - const firstGrandchild = firstChild.children?.[0] - if (firstGrandchild) { - setSelectedGrandchild(firstGrandchild.key) - setQueryWith(firstGrandchild.key) - } else { - setSelectedGrandchild('') - setQueryWith(null) - } - } else { - setSelectedChild('') - setQueryUsing(null) - setSelectedGrandchild('') - setQueryWith(null) - } - } - - const handleChildChange = (value: string) => { - setSelectedChild(value) - setQueryUsing(value) - - const parent = connectionObject.find((item) => item.key === selectedParent) - const child = parent?.children.find((child) => child.key === value) - const firstGrandchild = child?.children?.[0] - - if (firstGrandchild) { - setSelectedGrandchild(firstGrandchild.key) - setQueryWith(firstGrandchild.key) - } else { - setSelectedGrandchild('') - setQueryWith(null) - } - } - - const handleGrandchildChange = (value: string) => { - setSelectedGrandchild(value) - if (value) { - setQueryWith(value) - } else { - setQueryWith(null) - } - } - - // reset the parent/child/grandchild when the connection type (tab) changes - function handleConnectionTypeChange(connections: ConnectionType[]) { - setSelectedParent(connections[0].key) - - if (connections[0]?.children.length > 0) { - setSelectedChild(connections[0].children[0].key) - - if (connections[0].children[0]?.children.length > 0) { - setSelectedGrandchild(connections[0].children[0].children[0].key) - } else { - setSelectedGrandchild('') - } - } else { - setSelectedChild('') - setSelectedGrandchild('') - } - } - - function handleConnectionType(type: string) { - setTab(type) - - if (type === 'frameworks') { - setConnectionObject(FRAMEWORKS) - handleConnectionTypeChange(FRAMEWORKS) - } - - if (type === 'mobiles') { - setConnectionObject(MOBILES) - handleConnectionTypeChange(MOBILES) - } - - if (type === 'orms') { - setConnectionObject(ORMS) - handleConnectionTypeChange(ORMS) - } - } - - const getChildOptions = () => { - const parent = connectionObject.find((item) => item.key === selectedParent) - if (parent && parent.children.length > 0) { - return parent.children - } - return [] - } - - const getGrandchildrenOptions = () => { - const parent = connectionObject.find((item) => item.key === selectedParent) - const subCategory = parent?.children.find((child) => child.key === selectedChild) - if (subCategory && subCategory.children.length > 0) { - return subCategory.children - } - return [] - } - - const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) - const { anonKey, publishableKey } = canReadAPIKeys - ? getKeys(apiKeys) - : { anonKey: null, publishableKey: null } - - const projectKeys = useMemo(() => { - return { - apiUrl: resolvedEndpoint ?? null, - anonKey: anonKey?.api_key ?? null, - publishableKey: publishableKey?.api_key ?? null, - } - }, [resolvedEndpoint, anonKey?.api_key, publishableKey?.api_key]) - - const filePath = getContentFilePath({ - connectionObject, - selectedParent, - selectedChild, - selectedGrandchild, - }) - - const resetQueryStates = () => { - setQueryFramework(null) - setQueryUsing(null) - setQueryWith(null) - setQueryType(null) - setQuerySource(null) - setQueryMethod(null) - } - - const handleDialogChange = (dialogOpen: boolean) => { - if (!dialogOpen) { - setTab(null) - resetQueryStates() - } - setOpen(dialogOpen) - } - - useEffect(() => { - if (!open) return - const noConnectTabInUrl = typeof router.query.connectTab === 'undefined' - const hasQuery = queryFramework || queryUsing || queryWith - const inferred = inferConnectTabFromParentKey(queryFramework) - - if (noConnectTabInUrl && hasQuery && inferred) { - setTab(inferred) - if (inferred === 'frameworks') setConnectionObject(FRAMEWORKS) - if (inferred === 'mobiles') setConnectionObject(MOBILES) - if (inferred === 'orms') setConnectionObject(ORMS) - } - }, [open, router.query.connectTab, queryFramework, queryUsing, queryWith, setTab]) - - useEffect(() => { - if (!open) return - - const newConnectionObject = getConnectionObjectForTab(tab) - setConnectionObject(newConnectionObject) - - const parent = - newConnectionObject.find((item) => item.key === queryFramework) ?? newConnectionObject[0] - setSelectedParent(parent?.key ?? '') - - if (queryFramework) { - if (parent?.key !== queryFramework) setQueryFramework(parent?.key ?? null) - } - - const child = - parent?.children.find((child) => child.key === queryUsing) ?? parent?.children?.[0] - setSelectedChild(child?.key ?? '') - - if (queryUsing) { - if (child?.key !== queryUsing) setQueryUsing(child?.key ?? null) - } - - const grandchild = - child?.children.find((child) => child.key === queryWith) ?? child?.children?.[0] - setSelectedGrandchild(grandchild?.key ?? '') - - if (queryWith) { - if (grandchild?.key !== queryWith) setQueryWith(grandchild?.key ?? null) - } - }, [ - open, - tab, - queryFramework, - setQueryFramework, - queryUsing, - setQueryUsing, - queryWith, - setQueryWith, - ]) - - return ( - - - - - Connect to your project - {connectionTypes.length === 1 ? ` via ${connectionTypes[0].label.toLowerCase()}` : null} - - - Get the connection strings and environment variables for your app. - - - - { - resetQueryStates() - handleConnectionType(value) - }} - > - {connectionTypes.length > 1 ? ( - - {connectionTypes.map((type) => ( - - {type.label} - - ))} - - ) : ( - - )} - - {connectionTypes.map((type) => { - const hasChildOptions = - (connectionObject.find((parent) => parent.key === selectedParent)?.children.length || - 0) > 0 - const hasGrandChildOptions = - (connectionObject - .find((parent) => parent.key === selectedParent) - ?.children.find((child) => child.key === selectedChild)?.children.length || 0) > 0 - - if (type.key === 'direct') { - return ( - -
- -
-
- ) - } - - if (type.key === 'mcp') { - return ( - - - - ) - } - - if (type.key === 'api-keys') { - return ( - - - - ) - } - - const connectionTabMap: Record< - string, - 'App Frameworks' | 'Mobile Frameworks' | 'ORMs' - > = { - frameworks: 'App Frameworks', - mobiles: 'Mobile Frameworks', - orms: 'ORMs', - } - const connectionTab = connectionTabMap[type.key] || 'App Frameworks' - const selectedFrameworkOrTool = - connectionObject.find((item) => item.key === selectedParent)?.label || '' - - return ( - -
-
- - {selectedParent && hasChildOptions && ( - - )} - {selectedChild && hasGrandChildOptions && ( - - )} -
- {connectionObject.find((item) => item.key === selectedParent)?.guideLink && ( - - )} -
-

- Add the following files below to your application -

- - {IS_PLATFORM && ( - -

- View your publishable and secret API keys from the project{' '} - - API settings page - -

-

- To learn more about the new API keys, read the{' '} - - documentation - -

- - } - href={`${BASE_PATH}/project/${projectRef}/settings/api-keys`} - buttonText="View API keys" - /> - )} -
- ) - })} -
-
-
- ) -} diff --git a/apps/studio/components/interfaces/ProjectHome/ConnectSection.config.tsx b/apps/studio/components/interfaces/ProjectHome/ConnectSection.config.tsx index 4b2e7196e69..975dc314f88 100644 --- a/apps/studio/components/interfaces/ProjectHome/ConnectSection.config.tsx +++ b/apps/studio/components/interfaces/ProjectHome/ConnectSection.config.tsx @@ -1,9 +1,7 @@ import { Box, Cable, Database, KeyRound, Sparkles } from 'lucide-react' import type { ReactNode } from 'react' -import type { ConnectMode } from '../ConnectSheet/Connect.types' -// Temporary: experiment variants for the connectSection A/B test. Remove after the experiment. -export type ConnectSectionVariant = 'connect' | 'getting-started' +import type { ConnectMode } from '../ConnectSheet/Connect.types' export type ConnectAction = { id: ConnectMode | 'api_keys' diff --git a/apps/studio/components/interfaces/ProjectHome/ConnectSection.tsx b/apps/studio/components/interfaces/ProjectHome/ConnectSection.tsx index df723aa0094..aa74633ace0 100644 --- a/apps/studio/components/interfaces/ProjectHome/ConnectSection.tsx +++ b/apps/studio/components/interfaces/ProjectHome/ConnectSection.tsx @@ -9,14 +9,10 @@ import { useEffect, useRef } from 'react' import { Card, CardContent, cn } from 'ui' import { useAvailableConnectModes } from '../ConnectSheet/useAvailableConnectModes' -import { CONNECT_ACTIONS, type ConnectSectionVariant } from './ConnectSection.config' +import { CONNECT_ACTIONS } from './ConnectSection.config' import { useAppStateSnapshot } from '@/state/app-state' -interface ConnectSectionProps { - variant: ConnectSectionVariant -} - -export const ConnectSection = ({ variant }: ConnectSectionProps) => { +export const ConnectSection = () => { const router = useRouter() const { data: selectedProject } = useSelectedProjectQuery() const { setConnectSheetSource } = useAppStateSnapshot() @@ -37,8 +33,8 @@ export const ConnectSection = ({ variant }: ConnectSectionProps) => { if (!IS_PLATFORM) return if (hasTrackedExposure.current) return hasTrackedExposure.current = true - track('home_connect_section_exposed', { variant }) - }, [variant, track]) + track('home_connect_section_exposed') + }, [track]) const handleActionClick = (action: (typeof CONNECT_ACTIONS)[number]) => { track('home_connect_action_clicked', { mode: action.id }) diff --git a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.tsx b/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.tsx deleted file mode 100644 index 12ce5856838..00000000000 --- a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { Check, ChevronLeft, ChevronRight } from 'lucide-react' -import Image from 'next/image' -import Link from 'next/link' -import { useEffect, useState } from 'react' - -import { BASE_PATH } from 'lib/constants' -import { Badge, Button, Card, CardContent, cn, IconDiscord } from 'ui' -import { GettingStartedAction, GettingStartedStep } from './GettingStarted.types' - -// Determine action type for tracking -const getActionType = (action: GettingStartedAction): 'primary' | 'ai_assist' | 'external_link' => { - // Check if it's an AI assist action (has AiIconAnimation or "Do it for me"/"Generate" labels) - if ( - action.label?.toLowerCase().includes('do it for me') || - action.label?.toLowerCase().includes('generate') || - action.label?.toLowerCase().includes('create policies for me') - ) { - return 'ai_assist' - } - // Check if it's an external link (href that doesn't start with /project/) - if (action.href && !action.href.startsWith('/project/')) { - return 'external_link' - } - return 'primary' -} - -export interface GettingStartedProps { - steps: GettingStartedStep[] - onStepClick: ({ - stepIndex, - stepTitle, - actionType, - wasCompleted, - }: { - stepIndex: number - stepTitle: string - actionType: 'primary' | 'ai_assist' | 'external_link' - wasCompleted: boolean - }) => void -} - -export function GettingStarted({ steps, onStepClick }: GettingStartedProps) { - const allStepsComplete = steps.length > 0 && steps.every((step) => step.status === 'complete') - const [activeStepKey, setActiveStepKey] = useState(null) - - useEffect(() => { - if (steps.length === 0) { - setActiveStepKey(null) - return - } - - // Check if the current active step key is still valid in the new steps array - const isActiveStepValid = activeStepKey && steps.some((step) => step.key === activeStepKey) - - // If no step is selected or the active step is no longer valid (e.g., after tab switch) - if (!isActiveStepValid) { - // If all steps are complete, don't select any step - if (allStepsComplete) { - setActiveStepKey(null) - } else { - // Select the first incomplete step, or the first step if all are complete - const firstIncompleteStep = steps.find((step) => step.status !== 'complete') - setActiveStepKey(firstIncompleteStep?.key ?? steps[0]?.key ?? null) - } - } - }, [steps, allStepsComplete, activeStepKey]) - - const activeStep = activeStepKey ? steps.find((step) => step.key === activeStepKey) : null - const activeStepIndex = activeStep ? steps.findIndex((step) => step.key === activeStep.key) : -1 - const previousStep = activeStepIndex > 0 ? steps[activeStepIndex - 1] : null - const nextStep = - activeStepIndex > -1 && activeStepIndex < steps.length - 1 ? steps[activeStepIndex + 1] : null - - const handleSelectPrevious = () => { - if (previousStep) { - setActiveStepKey(previousStep.key) - } - } - - const handleSelectNext = () => { - if (nextStep) { - setActiveStepKey(nextStep.key) - } - } - - const showCongratulations = allStepsComplete && !activeStep - - return ( - -
- - - - {showCongratulations ? ( -
-
-
- -
-
-

All steps complete

-
-

- Drop into our Discord community to share your progress and learn from fellow - developers. -

- - -
-
- ) : ( - <> -
- - {activeStepIndex + 1} - -
- - -
-
-
- {activeStep?.image ? ( - {activeStep.title} - ) : ( -
- Supabase Grafana - Supabase Grafana -
- )} -
-
-
- {activeStep && ( - <> -
-

{activeStep.title}

- - {activeStep.status} - -
-

- {activeStep.description} -

-
- {activeStep.actions.map((action, i) => { - if (action.component) { - return
{action.component}
- } - - const actionType = getActionType(action) - - if (action.href) { - return ( - - ) - } - - return ( - - ) - })} -
- - )} -
- - )} - -
- - ) -} diff --git a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.types.ts b/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.types.ts deleted file mode 100644 index e1a6ca55ea8..00000000000 --- a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.types.ts +++ /dev/null @@ -1,24 +0,0 @@ -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/ProjectHome/GettingStarted/GettingStarted.utils.tsx b/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.utils.tsx deleted file mode 100644 index 5632a2c4f29..00000000000 --- a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStarted.utils.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import { DOCS_URL } from 'lib/constants' -import { BarChart3, Code, Database, GitBranch, Shield, Table, Upload, UserPlus } from 'lucide-react' -import { AiIconAnimation } from 'ui' -import { CodeBlock } from 'ui-patterns/CodeBlock' - -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, - 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: `${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: 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: `${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: 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: 'connect-app', - status: hasAppConnected ? 'complete' : 'incomplete', - title: 'Connect your application', - icon: , - description: - 'Your project is ready; open the Connect sheet to grab connection details and setup guidance.', - 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: `${DOCS_URL}/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: `${DOCS_URL}/guides/storage/buckets/creating-buckets?queryGroups=language&language=sql`, - variant: 'default', - }, - { - label: 'Upload a file', - href: `${DOCS_URL}/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: `${DOCS_URL}/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, - 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: 'connect-app', - status: hasAppConnected ? 'complete' : 'incomplete', - title: 'Connect your application', - icon: , - description: - 'Your project is ready; open the Connect sheet to grab connection details and setup guidance.', - 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: `${DOCS_URL}/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/files`, 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', - }, - ], - }, - ] -} diff --git a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStartedSection.tsx b/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStartedSection.tsx deleted file mode 100644 index 5eada2fc073..00000000000 --- a/apps/studio/components/interfaces/ProjectHome/GettingStarted/GettingStartedSection.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import { IS_PLATFORM, useParams } from 'common' -import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' -import { BASE_PATH } from 'lib/constants' -import { useTrack } from 'lib/telemetry/track' -import { Code, Table2 } from 'lucide-react' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' -import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state' -import { Button, Card, CardContent, ToggleGroup, ToggleGroupItem } from 'ui' - -import { GettingStarted } from './GettingStarted' -import { - GettingStartedAction, - GettingStartedState, - GettingStartedStep, -} from './GettingStarted.types' -import { getCodeWorkflowSteps, getNoCodeWorkflowSteps } from './GettingStarted.utils' -import { useGettingStartedProgress } from './useGettingStartedProgress' -import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton' - -interface GettingStartedSectionProps { - value: GettingStartedState - onChange: (v: GettingStartedState) => void -} - -export function GettingStartedSection({ value, onChange }: GettingStartedSectionProps) { - const { ref } = useParams() - const track = useTrack() - const aiSnap = useAiAssistantStateSnapshot() - const { openSidebar } = useSidebarManagerSnapshot() - - const workflow: 'no-code' | 'code' | null = value === 'code' || value === 'no-code' ? value : null - const [previousWorkflow, setPreviousWorkflow] = useState<'no-code' | 'code' | null>(null) - - const statuses = useGettingStartedProgress() - - const openAiChat = useCallback( - (name: string, initialInput: string) => { - openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) - aiSnap.newChat({ name, initialInput }) - }, - [aiSnap, openSidebar] - ) - - const connectActions: GettingStartedAction[] = useMemo(() => { - return [ - { - label: 'Connect', - component: , - }, - ] - }, []) - - const codeSteps: GettingStartedStep[] = useMemo( - () => - getCodeWorkflowSteps({ - ref, - openAiChat, - connectActions, - statuses, - }), - [connectActions, openAiChat, ref, statuses] - ) - - const noCodeSteps: GettingStartedStep[] = useMemo( - () => - getNoCodeWorkflowSteps({ - ref, - openAiChat, - connectActions, - statuses, - }), - [connectActions, openAiChat, ref, statuses] - ) - - const steps = workflow === 'code' ? codeSteps : workflow === 'no-code' ? noCodeSteps : [] - - const hasTrackedExposure = useRef(false) - - useEffect(() => { - if (!IS_PLATFORM) return - if (hasTrackedExposure.current) return - - hasTrackedExposure.current = true - - track('home_getting_started_section_exposed', { - workflow: workflow === 'no-code' ? 'no_code' : workflow === 'code' ? 'code' : null, - }) - }, [workflow, track]) - - return ( -
-
-

Getting started

-
- { - if (v) { - const newWorkflow = v as 'no-code' | 'code' - setPreviousWorkflow(workflow) - onChange(newWorkflow) - track('home_getting_started_workflow_clicked', { - workflow: newWorkflow === 'no-code' ? 'no_code' : 'code', - is_switch: previousWorkflow !== null, - }) - } - }} - > - - - No-code - - - - Code - - - -
-
- - {steps.length === 0 ? ( - -
- Supabase Grafana - Supabase Grafana -
-
- -
-

- 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. -

-
-
- - -
-
- - ) : ( - { - if (workflow) { - track('home_getting_started_step_clicked', { - workflow: workflow === 'no-code' ? 'no_code' : 'code', - step_number: stepIndex + 1, - step_title: stepTitle, - action_type: actionType, - was_completed: wasCompleted, - }) - } - }} - /> - )} -
- ) -} diff --git a/apps/studio/components/interfaces/ProjectHome/GettingStarted/useGettingStartedProgress.ts b/apps/studio/components/interfaces/ProjectHome/GettingStarted/useGettingStartedProgress.ts deleted file mode 100644 index 62c2d7b464b..00000000000 --- a/apps/studio/components/interfaces/ProjectHome/GettingStarted/useGettingStartedProgress.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { useMemo } from 'react' - -import { useProjectLogStatsQuery } from 'data/analytics/project-log-stats-query' -import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useUsersCountQuery } from 'data/auth/users-count-query' -import { useContentInfiniteQuery } from 'data/content/content-infinite-query' -import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query' -import { useMigrationsQuery } from 'data/database/migrations-query' -import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' -import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' -import { useTablesQuery } from 'data/tables/tables-query' -import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' -import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { PROJECT_STATUS } from 'lib/constants' - -type GettingStartedStatuses = { - hasTables: boolean - hasCliSetup: boolean - hasSampleData: boolean - hasRlsPolicies: boolean - hasAppConnected: boolean - hasFirstUser: boolean - hasStorageObjects: boolean - hasEdgeFunctions: boolean - hasReports: boolean - hasGitHubConnection: boolean -} - -export const useGettingStartedProgress = (): GettingStartedStatuses => { - const { data: project } = useSelectedProjectQuery() - const { data: organization } = useSelectedOrganizationQuery() - - const projectRef = project?.ref - const connectionString = project?.connectionString - const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - - const { data: tablesData } = useTablesQuery( - { projectRef, connectionString, schema: 'public' }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: storageTablesData } = useTablesQuery( - { projectRef, connectionString, schema: 'storage' }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: policiesData } = useDatabasePoliciesQuery( - { projectRef, connectionString, schema: 'public' }, - { enabled: !!projectRef && !!connectionString && isProjectActive } - ) - - const { data: authConfig } = useAuthConfigQuery( - { projectRef }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: usersCountData } = useUsersCountQuery( - { projectRef, connectionString }, - { enabled: !!projectRef && !!connectionString && isProjectActive } - ) - - const { data: edgeFunctionsData } = useEdgeFunctionsQuery( - { projectRef }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: reportsData } = useContentInfiniteQuery( - { projectRef, type: 'report', limit: 1 }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: migrationsData } = useMigrationsQuery( - { projectRef, connectionString }, - { enabled: !!projectRef && !!connectionString && isProjectActive } - ) - - const { data: usageStatsData } = useProjectLogStatsQuery( - { projectRef, interval: '1day' }, - { enabled: !!projectRef && isProjectActive } - ) - - const { data: githubConnections } = useGitHubConnectionsQuery( - { organizationId: organization?.id }, - { enabled: !!projectRef && !!organization?.id } - ) - - const statuses = useMemo(() => { - const hasTables = (tablesData?.length ?? 0) > 0 - const hasCliSetup = (migrationsData?.length ?? 0) > 0 - const hasSampleData = (tablesData ?? []).some( - (table) => Number(table?.live_rows_estimate ?? 0) > 0 - ) - const hasRlsPolicies = (policiesData?.length ?? 0) > 0 - const allowSignupsEnabled = authConfig ? !authConfig.DISABLE_SIGNUP : false - const emailProviderEnabled = !!authConfig?.EXTERNAL_EMAIL_ENABLED - const hasFirstUser = !!usersCountData && !usersCountData.is_estimate && usersCountData.count > 0 - const hasStorageObjects = (storageTablesData ?? []).some( - (table) => table.name === 'objects' && Number(table?.live_rows_estimate ?? 0) > 0 - ) - const hasEdgeFunctions = (edgeFunctionsData?.length ?? 0) > 0 - const hasReports = (reportsData?.pages?.[0]?.content?.length ?? 0) > 0 - const hasGitHubConnection = - githubConnections?.some((connection) => connection.project.ref === projectRef) ?? false - const hasAppConnected = - usageStatsData?.result?.some((row) => { - const totals = [ - row.total_auth_requests, - row.total_storage_requests, - row.total_rest_requests, - row.total_realtime_requests, - ] - return totals.some((value) => (value ?? 0) > 0) - }) ?? false - - return { - hasTables, - hasCliSetup, - hasSampleData, - hasRlsPolicies, - hasAppConnected, - hasFirstUser, - hasStorageObjects, - hasEdgeFunctions, - hasReports, - hasGitHubConnection, - } - }, [ - authConfig, - edgeFunctionsData, - githubConnections, - migrationsData, - policiesData, - reportsData, - storageTablesData, - tablesData, - usageStatsData, - usersCountData, - projectRef, - ]) - - return statuses -} - -export type { GettingStartedStatuses } diff --git a/apps/studio/components/interfaces/ProjectHome/Home.tsx b/apps/studio/components/interfaces/ProjectHome/Home.tsx index 6d84a1d0fc6..03c13ee156a 100644 --- a/apps/studio/components/interfaces/ProjectHome/Home.tsx +++ b/apps/studio/components/interfaces/ProjectHome/Home.tsx @@ -8,7 +8,6 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import dayjs from 'dayjs' import { useLocalStorage } from 'hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { usePHFlag } from 'hooks/ui/useFlag' import { PROJECT_STATUS } from 'lib/constants' import { useTrack } from 'lib/telemetry/track' import { useEffect, useRef } from 'react' @@ -17,11 +16,8 @@ import { cn } from 'ui' import { AdvisorSection } from './AdvisorSection' import { ConnectSection } from './ConnectSection' -import type { ConnectSectionVariant } from './ConnectSection.config' import { CustomReportSection } from './CustomReportSection' -import { type GettingStartedState } from './GettingStarted/GettingStarted.types' -import { GettingStartedSection } from './GettingStarted/GettingStartedSection' -import { DEFAULT_SECTION_ORDER, getSectionVisibility, mergeSectionOrder } from './Home.utils' +import { DEFAULT_SECTION_ORDER, mergeSectionOrder } from './Home.utils' import { ProjectUsageSection as ProjectUsageSectionV2 } from './ProjectUsageSection' export const ProjectHome = () => { @@ -31,7 +27,6 @@ export const ProjectHome = () => { const track = useTrack() const showHomepageUsageV2 = useFlag('newHomepageUsageV2') - const connectSectionVariant = usePHFlag('connectSection') const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day')) @@ -44,11 +39,6 @@ export const ProjectHome = () => { DEFAULT_SECTION_ORDER ) - const [gettingStartedState, setGettingStartedState] = useLocalStorage( - `home-getting-started-${project?.ref || 'default'}`, - 'empty' - ) - const UsageSection = showHomepageUsageV2 ? ProjectUsageSectionV2 : ProjectUsageSectionV1 const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })) @@ -82,11 +72,11 @@ export const ProjectHome = () => { setSectionOrder(mergeSectionOrder) }, [setSectionOrder]) - const { showConnectSection, showGettingStarted } = getSectionVisibility({ - connectSectionVariant, - isMatureProject, - hasProject: !!project, - gettingStartedState, + const showConnectSection = !isMatureProject && !!project + + const renderOrder = mergeSectionOrder(sectionOrder).filter((id) => { + if (id === 'connect') return showConnectSection + return true }) return ( @@ -103,15 +93,8 @@ export const ProjectHome = () => { - { - if (id === 'connect') return showConnectSection - if (id === 'getting-started') return showGettingStarted - return true - })} - strategy={verticalListSortingStrategy} - > - {sectionOrder.map((id) => { + + {renderOrder.map((id) => { if (IS_PLATFORM && id === 'usage') { return (
@@ -124,17 +107,7 @@ export const ProjectHome = () => { if (id === 'connect' && showConnectSection) { return ( - - - ) - } - if (id === 'getting-started' && showGettingStarted) { - return ( - - + ) } diff --git a/apps/studio/components/interfaces/ProjectHome/Home.utils.test.ts b/apps/studio/components/interfaces/ProjectHome/Home.utils.test.ts index f4f00e86a1e..3fced1672c3 100644 --- a/apps/studio/components/interfaces/ProjectHome/Home.utils.test.ts +++ b/apps/studio/components/interfaces/ProjectHome/Home.utils.test.ts @@ -1,17 +1,16 @@ import { describe, expect, it } from 'vitest' -import { mergeSectionOrder, getSectionVisibility } from './Home.utils' +import { mergeSectionOrder } from './Home.utils' describe('mergeSectionOrder', () => { it('returns stored order unchanged when it matches defaults', () => { - const stored = ['connect', 'getting-started', 'usage', 'advisor', 'custom-report'] + const stored = ['connect', 'usage', 'advisor', 'custom-report'] expect(mergeSectionOrder(stored)).toBe(stored) }) it('inserts missing sections at their default-relative position', () => { expect(mergeSectionOrder(['usage', 'advisor', 'custom-report'])).toEqual([ 'connect', - 'getting-started', 'usage', 'advisor', 'custom-report', @@ -19,10 +18,9 @@ describe('mergeSectionOrder', () => { }) it('preserves user reordering while inserting missing sections', () => { - expect(mergeSectionOrder(['getting-started', 'advisor', 'usage', 'custom-report'])).toEqual([ - 'connect', - 'getting-started', + expect(mergeSectionOrder(['advisor', 'usage', 'custom-report'])).toEqual([ 'advisor', + 'connect', 'usage', 'custom-report', ]) @@ -31,74 +29,15 @@ describe('mergeSectionOrder', () => { it('strips unknown sections from stored order', () => { expect(mergeSectionOrder(['usage', 'deleted-section', 'advisor', 'custom-report'])).toEqual([ 'connect', - 'getting-started', 'usage', 'advisor', 'custom-report', ]) }) -}) -describe('getSectionVisibility', () => { - const base = { - connectSectionVariant: 'connect' as const, - isMatureProject: false, - hasProject: true, - gettingStartedState: 'empty' as const, - } - - it('shows connect section for connect variant on new project', () => { - expect(getSectionVisibility(base)).toEqual({ - showConnectSection: true, - showGettingStarted: false, - }) - }) - - it('shows getting started for non-connect variant', () => { - expect(getSectionVisibility({ ...base, connectSectionVariant: 'getting-started' })).toEqual({ - showConnectSection: false, - showGettingStarted: true, - }) - }) - - it('shows neither when flag is unresolved', () => { - expect(getSectionVisibility({ ...base, connectSectionVariant: undefined })).toEqual({ - showConnectSection: false, - showGettingStarted: false, - }) - }) - - it('shows neither for mature projects', () => { - expect(getSectionVisibility({ ...base, isMatureProject: true })).toEqual({ - showConnectSection: false, - showGettingStarted: false, - }) - }) - - it('hides getting started when user dismissed it', () => { + it('strips legacy getting-started from stored order', () => { expect( - getSectionVisibility({ - ...base, - connectSectionVariant: 'getting-started', - gettingStartedState: 'hidden', - }) - ).toEqual({ - showConnectSection: false, - showGettingStarted: false, - }) - }) - - it('shows getting started when flag resolved to false (control group)', () => { - expect(getSectionVisibility({ ...base, connectSectionVariant: false })).toEqual({ - showConnectSection: false, - showGettingStarted: true, - }) - }) - - it('shows neither when project is missing', () => { - expect(getSectionVisibility({ ...base, hasProject: false })).toEqual({ - showConnectSection: false, - showGettingStarted: false, - }) + mergeSectionOrder(['connect', 'getting-started', 'usage', 'advisor', 'custom-report']) + ).toEqual(['connect', 'usage', 'advisor', 'custom-report']) }) }) diff --git a/apps/studio/components/interfaces/ProjectHome/Home.utils.ts b/apps/studio/components/interfaces/ProjectHome/Home.utils.ts index 713ab06bcb0..35eb9c87b77 100644 --- a/apps/studio/components/interfaces/ProjectHome/Home.utils.ts +++ b/apps/studio/components/interfaces/ProjectHome/Home.utils.ts @@ -1,13 +1,4 @@ -import type { GettingStartedState } from './GettingStarted/GettingStarted.types' -import type { ConnectSectionVariant } from './ConnectSection.config' - -export const DEFAULT_SECTION_ORDER = [ - 'connect', - 'getting-started', - 'usage', - 'advisor', - 'custom-report', -] +export const DEFAULT_SECTION_ORDER = ['connect', 'usage', 'advisor', 'custom-report'] /** * Reconciles a stored section order with the canonical list. @@ -33,23 +24,3 @@ export function mergeSectionOrder(stored: string[]): string[] { } return merged } - -// Temporary: getSectionVisibility and related types support the connectSection -// experiment (connect vs getting-started). Remove after the experiment concludes. -interface SectionVisibilityInput { - connectSectionVariant: ConnectSectionVariant | false | undefined - isMatureProject: boolean - hasProject: boolean - gettingStartedState: GettingStartedState -} - -export function getSectionVisibility(input: SectionVisibilityInput) { - const { connectSectionVariant, isMatureProject, hasProject, gettingStartedState } = input - const canShowEither = connectSectionVariant !== undefined && !isMatureProject && hasProject - - return { - showConnectSection: canShowEither && connectSectionVariant === 'connect', - showGettingStarted: - canShowEither && connectSectionVariant !== 'connect' && gettingStartedState !== 'hidden', - } -} diff --git a/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx index efee91c7fb8..7fb67146ab5 100644 --- a/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx +++ b/apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx @@ -3,7 +3,6 @@ import { useIsBranching2Enabled, useIsFloatingMobileToolbarEnabled, } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import { Connect } from 'components/interfaces/Connect/Connect' import { ConnectButton } from 'components/interfaces/ConnectButton/ConnectButton' import { ConnectSheet } from 'components/interfaces/ConnectSheet/ConnectSheet' import { LocalDropdown } from 'components/interfaces/LocalDropdown' @@ -23,7 +22,6 @@ import { AnimatePresence, motion } from 'framer-motion' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { usePHFlag } from 'hooks/ui/useFlag' import { IS_PLATFORM } from 'lib/constants' import { ChevronLeft } from 'lucide-react' import Link from 'next/link' @@ -37,7 +35,6 @@ import { FeedbackDropdown } from './FeedbackDropdown/FeedbackDropdown' import { HomeIcon } from './HomeIcon' import { LocalVersionPopover } from './LocalVersionPopover' import { MergeRequestButton } from './MergeRequestButton' -import type { ConnectSectionVariant } from '@/components/interfaces/ProjectHome/ConnectSection.config' const LayoutHeaderDivider = ({ className, ...props }: React.HTMLProps) => ( @@ -78,9 +75,6 @@ export const LayoutHeader = ({ const gitlessBranching = useIsBranching2Enabled() const showFloatingMobileToolbar = useIsFloatingMobileToolbarEnabled() - const connectSectionVariant = usePHFlag('connectSection') - const isConnectSheetEnabled = connectSectionVariant === 'connect' - const [commandMenuEnabled] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.HOTKEY_COMMAND_MENU, true) const isAccountPage = router.pathname.startsWith('/account') @@ -292,7 +286,7 @@ export const LayoutHeader = ({
- {isConnectSheetEnabled ? : } + ) } diff --git a/apps/studio/data/config/project-endpoint-query.ts b/apps/studio/data/config/project-endpoint-query.ts index 9311f7f5ff8..0a1a3fda878 100644 --- a/apps/studio/data/config/project-endpoint-query.ts +++ b/apps/studio/data/config/project-endpoint-query.ts @@ -8,7 +8,7 @@ export const useProjectApiUrl = ( { projectRef }: { projectRef?: string }, { enabled = true }: { enabled?: boolean } = {} ) => { - const { data } = useProjectAddonsQuery({ projectRef }) + const { data } = useProjectAddonsQuery({ projectRef }, { enabled }) const hasCustomDomainsAddon = !!data?.selected_addons.find((x) => x.type === 'custom_domain') const { diff --git a/apps/studio/state/app-state.ts b/apps/studio/state/app-state.ts index 984f5864703..82740a3e25c 100644 --- a/apps/studio/state/app-state.ts +++ b/apps/studio/state/app-state.ts @@ -8,7 +8,6 @@ const getInitialState = () => { showProjectApiDocs: false, showCreateBranchModal: false, showAiSettingsModal: false, - showConnectDialog: false, ongoingQueriesPanelOpen: false, mobileMenuOpen: false, showSidebar: true, diff --git a/e2e/studio/features/connect.spec.ts b/e2e/studio/features/connect.spec.ts index fe9ff2f207a..893657634fb 100644 --- a/e2e/studio/features/connect.spec.ts +++ b/e2e/studio/features/connect.spec.ts @@ -1,51 +1,41 @@ import { expect } from '@playwright/test' + import { test } from '../utils/test.js' import { toUrl } from '../utils/to-url.js' test.describe('Connect', async () => { - test('Connect dialog opens when showConnect=true query param is present', async ({ - page, - ref, - }) => { + test('ConnectSheet opens when showConnect=true query param is present', async ({ page, ref }) => { // Navigate to project page with showConnect=true query param await page.goto(toUrl(`/project/${ref}?showConnect=true`)) - // Wait for the page to load - await expect(page.getByRole('heading', { level: 1 })).toBeVisible({ timeout: 30000 }) - - // Check that either the Connect dialog or ConnectSheet is visible - // The Connect component renders a Dialog with title "Connect to your project" - // The ConnectSheet component renders a Sheet with title "Connect to your project" - await expect( - page.getByRole('heading', { name: 'Connect to your project' }) - ).toBeVisible({ timeout: 30000 }) + // Check that the ConnectSheet is visible + await expect(page.getByRole('heading', { name: 'Connect to your project' })).toBeVisible({ + timeout: 30000, + }) }) - test('Connect dialog closes when dismissed', async ({ page, ref }) => { + test('ConnectSheet closes when dismissed', async ({ page, ref }) => { // Navigate to project page with showConnect=true query param await page.goto(toUrl(`/project/${ref}?showConnect=true`)) - // Wait for the page to load - await expect(page.getByRole('heading', { level: 1 })).toBeVisible({ timeout: 30000 }) + // Wait for the ConnectSheet to be visible + await expect(page.getByRole('heading', { name: 'Connect to your project' })).toBeVisible({ + timeout: 30000, + }) - // Wait for the Connect dialog/sheet to be visible - await expect( - page.getByRole('heading', { name: 'Connect to your project' }) - ).toBeVisible({ timeout: 30000 }) - - // Close the dialog by pressing Escape + // Close the sheet by pressing Escape await page.keyboard.press('Escape') - // Verify the dialog is no longer visible - await expect( - page.getByRole('heading', { name: 'Connect to your project' }) - ).not.toBeVisible({ timeout: 10000 }) + // Verify the sheet is no longer visible + await expect(page.getByRole('heading', { name: 'Connect to your project' })).not.toBeVisible({ + timeout: 10000, + }) // Verify the query param is removed from the URL await expect(page).not.toHaveURL(/showConnect=true/) }) - test('Connect button in header opens the Connect dialog', async ({ page, ref }) => { + test('Connect button in header opens the ConnectSheet', async ({ page, ref }) => { // Navigate to project page without the query param await page.goto(toUrl(`/project/${ref}`)) @@ -55,10 +45,10 @@ test.describe('Connect', async () => { // Click the Connect button in the header await page.getByRole('button', { name: 'Connect' }).click() - // Verify the Connect dialog/sheet opens - await expect( - page.getByRole('heading', { name: 'Connect to your project' }) - ).toBeVisible({ timeout: 30000 }) + // Verify the ConnectSheet opens + await expect(page.getByRole('heading', { name: 'Connect to your project' })).toBeVisible({ + timeout: 30000, + }) // Verify the URL has the showConnect query param await expect(page).toHaveURL(/showConnect=true/) diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index e3a2e68d4c4..8f8914b8915 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1778,62 +1778,6 @@ export interface DpaPdfOpenedEvent { } } -/** - * User selected a workflow in the Getting Started section of HomeV2. - * - * @group Events - * @source studio - * @page /project/{ref} - */ -export interface HomeGettingStartedWorkflowClickedEvent { - action: 'home_getting_started_workflow_clicked' - properties: { - /** - * The workflow selected by the user - */ - workflow: 'code' | 'no_code' - /** - * Whether this is switching from another workflow - */ - is_switch: boolean - } - groups: TelemetryGroups -} - -/** - * User clicked on a step in the Getting Started section of HomeV2. - * - * @group Events - * @source studio - * @page /project/{ref} - */ -export interface HomeGettingStartedStepClickedEvent { - action: 'home_getting_started_step_clicked' - properties: { - /** - * The workflow type (code or no-code) - */ - workflow: 'code' | 'no_code' - /** - * The step number (1-based index) - */ - step_number: number - /** - * The title of the step - */ - step_title: string - /** - * The action type of the button clicked - */ - action_type: 'primary' | 'ai_assist' | 'external_link' - /** - * Whether the step was already completed - */ - was_completed: boolean - } - groups: TelemetryGroups -} - /** * User clicked on an activity stat in HomeV2. * @@ -1944,50 +1888,6 @@ export interface HomeCustomReportBlockRemovedEvent { groups: TelemetryGroups } -/** - * User dismissed the Getting Started section in HomeV2. - * - * @group Events - * @source studio - * @page /project/{ref} - */ -export interface HomeGettingStartedClosedEvent { - action: 'home_getting_started_closed' - properties: { - /** - * The current workflow when dismissed - */ - workflow: 'code' | 'no_code' - /** - * Number of steps completed when dismissed - */ - steps_completed: number - /** - * Total number of steps in the workflow - */ - total_steps: number - } - groups: TelemetryGroups -} - -/** - * Getting Started section was shown to the user in HomeV2. - * - * @group Events - * @source studio - * @page /project/{ref} - */ -export interface HomeGettingStartedSectionExposedEvent { - action: 'home_getting_started_section_exposed' - properties: { - /** - * The current workflow shown (null if choosing workflow) - */ - workflow: 'code' | 'no_code' | null - } - groups: TelemetryGroups -} - /** * User was exposed to the HomeV2 experiment (shown the new home page). * @@ -2007,7 +1907,7 @@ export interface HomeNewExperimentExposedEvent { } /** - * Connect section was shown to the user as part of the connectSection experiment. + * Connect section was shown to the user on the project homepage. * * @group Events * @source studio @@ -2015,12 +1915,6 @@ export interface HomeNewExperimentExposedEvent { */ export interface HomeConnectSectionExposedEvent { action: 'home_connect_section_exposed' - properties: { - /** - * The experiment variant shown to the user - */ - variant: 'connect' | 'getting-started' - } groups: TelemetryGroups } @@ -3174,10 +3068,6 @@ export type TelemetryEvent = | BranchSelectorCreateClickedEvent | BranchSelectorManageClickedEvent | DpaPdfOpenedEvent - | HomeGettingStartedWorkflowClickedEvent - | HomeGettingStartedStepClickedEvent - | HomeGettingStartedClosedEvent - | HomeGettingStartedSectionExposedEvent | HomeNewExperimentExposedEvent | HomeConnectSectionExposedEvent | HomeConnectActionClickedEvent