Files
Pamela ChiaandAli Waseem 578a73f966 feat(studio): connectSection experiment — replace Getting Started with Connect section (#43629)
## Summary

Re-ports PR #43119 against the current `ProjectHome` codebase (the
original PR targeted `HomeNew/` which was removed during the `homeNew`
graduation in #43437). Also unifies the `connectSheet` and
`connectSection` feature flags into a single `connectSection` flag — the
`connectSheet` flag is removed entirely, so both the ConnectSheet
(header) and ConnectSection (homepage row) are controlled by one
experiment.

- Adds `connectSection` PostHog experiment flag that controls two
things:
1. Swaps the Getting Started section for a Connect section on the
project homepage for new projects (< 10 days old)
2. Swaps the legacy Connect dialog for the new ConnectSheet panel in the
header
- **Control** (`getting-started`): existing Getting Started section +
legacy Connect dialog in header
- **Treatment** (`connect`): new 4-tile Connect section + ConnectSheet
in header
- `undefined` (loading): neither section renders, avoiding flash
- Tiles filtered by the same `useIsFeatureEnabled` flags as ConnectSheet
(`show_app_frameworks`, `show_mobile_frameworks`, `show_orms`)
- Connect tile clicks tracked via `home_connect_action_clicked`; section
render tracked via `home_connect_section_exposed`; sheet opens tracked
via `connect_sheet_opened` with source attribution (`header_button` or
`connect_section`)

## Changes

- `packages/common/telemetry-constants.ts` — new
`home_connect_section_exposed`, `home_connect_action_clicked`, and
`connect_sheet_opened` event types
- `ConnectSheet/ConnectSheet.tsx` — read `connectTab` query param and
sync to active mode on open; `handleModeChange` keeps param in sync on
tab switch; fire `connect_sheet_opened` event with source attribution on
open
- `ConnectButton/ConnectButton.tsx` — set `connectSource=header_button`
query param on click
- `ProjectHome/ConnectSection.tsx` — new component (4-tile connect
card); set `connectSource=connect_section` on tile click
- `ProjectHome/Home.tsx` — experiment flag wiring
- `LayoutHeader/LayoutHeader.tsx` — read `connectSection` flag instead
of `connectSheet` to toggle ConnectSheet vs legacy Connect dialog

## Test plan

To test on the Vercel preview, set `connectSection=connect` to 100% in
PostHog (or override via cookie `ph_override_connectSection=connect`).
No separate `connectSheet` flag is needed — `connectSection` controls
both features.
- [x] Treatment (`connect` variant) — "Get connected" section renders on
new project, Getting Started hidden
- [x] Control (`getting-started`) — Getting Started renders, Connect
section hidden
- [x] Mature project (> 10 days) — neither section regardless of flag
- [x] Clicking each tile opens ConnectSheet on the correct tab
(Framework / Direct / ORM / MCP)
- [x] Switching tabs inside sheet updates `connectTab` URL param
- [x] Closing sheet clears `connectTab` param
- [x] Direct URL deep-link (`?showConnect=true&connectTab=orm`) opens
sheet on correct tab
- [x] Dark mode — background gradient renders correctly
- [x] Light mode — background switches to light gradient
- [x] Responsive layout — 4 cols (xl), 2×2 (md), stacked (mobile)
- [x] Telemetry — `home_connect_section_exposed` fires once on load;
`home_connect_action_clicked` fires with correct `mode`
- [x] Treatment — header Connect button opens ConnectSheet (not legacy
Connect dialog)
- [x] Control — header Connect button opens legacy Connect dialog (not
ConnectSheet)
- [x] Telemetry — clicking a ConnectSection tile fires
`connect_sheet_opened` with `source: 'connect_section'`
- [x] Telemetry — clicking header Connect button fires
`connect_sheet_opened` with `source: 'header_button'`

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-03-13 11:50:07 +09:00

130 lines
4.3 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
import { useEffect, useMemo, useRef } from 'react'
import { cn, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from 'ui'
import type { ConnectMode, ProjectKeys } from './Connect.types'
import { CONNECT_MODES } from './Connect.types'
import { ConnectConfigSection, ModeSelector } from './ConnectConfigSection'
import { ConnectStepsSection } from './ConnectStepsSection'
import { useConnectState } from './useConnectState'
import { useAvailableConnectModes } from './useAvailableConnectModes'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useAppStateSnapshot } from '@/state/app-state'
import { useTrack } from 'lib/telemetry/track'
function isConnectMode(value: string): value is ConnectMode {
return CONNECT_MODES.some((mode) => mode === value)
}
export const ConnectSheet = () => {
const { ref: projectRef } = useParams()
const availableModeIds = useAvailableConnectModes()
const [showConnect, setShowConnect] = useQueryState(
'showConnect',
parseAsBoolean.withDefault(false)
)
const [connectTab, setConnectTab] = useQueryState('connectTab', parseAsString)
const { connectSheetSource, setConnectSheetSource } = useAppStateSnapshot()
const track = useTrack()
const prevShowConnect = useRef(false)
const { state, activeFields, resolvedSteps, schema, getFieldOptions, setMode, updateField } =
useConnectState()
useEffect(() => {
const justOpened = showConnect && !prevShowConnect.current
prevShowConnect.current = showConnect
if (!justOpened) return
track('connect_sheet_opened', { source: connectSheetSource })
setConnectSheetSource('header_button')
if (connectTab && isConnectMode(connectTab) && availableModeIds.includes(connectTab)) {
setMode(connectTab)
}
}, [
showConnect,
connectSheetSource,
connectTab,
availableModeIds,
track,
setConnectSheetSource,
setMode,
])
const handleOpenChange = (sheetOpen: boolean) => {
if (!sheetOpen) {
setConnectTab(null)
}
setShowConnect(sheetOpen)
}
const { data: endpoint = '' } = useProjectApiUrl({ projectRef }, { enabled: showConnect })
const { can: canReadAPIKeys } = useAsyncCheckPermissions(
PermissionAction.READ,
'service_api_keys'
)
const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
const { anonKey, publishableKey } = canReadAPIKeys
? getKeys(apiKeys)
: { anonKey: null, publishableKey: null }
const projectKeys: ProjectKeys = useMemo(() => {
return {
apiUrl: endpoint,
anonKey: anonKey?.api_key ?? null,
publishableKey: publishableKey?.api_key ?? null,
}
}, [endpoint, anonKey?.api_key, publishableKey?.api_key])
const availableModes = useMemo(
() => schema.modes.filter((m) => availableModeIds.includes(m.id)),
[schema.modes, availableModeIds]
)
const handleModeChange = (mode: ConnectMode) => {
setMode(mode)
setConnectTab(mode)
}
return (
<Sheet open={showConnect} onOpenChange={handleOpenChange}>
<SheetContent size="lg" className="flex flex-col gap-0 p-0 space-y-0" tabIndex={undefined}>
<SheetHeader className={cn('text-left border-b shrink-0 py-6 px-8')}>
<SheetTitle>Connect to your project</SheetTitle>
<SheetDescription>Choose how you want to use Supabase</SheetDescription>
</SheetHeader>
<div className="flex flex-1 flex-col overflow-y-auto divide-y">
<div className="p-8">
<ModeSelector
modes={availableModes}
selected={state.mode}
onChange={handleModeChange}
/>
</div>
<div className="border-b p-8">
<ConnectConfigSection
state={state}
activeFields={activeFields}
onFieldChange={updateField}
getFieldOptions={getFieldOptions}
/>
</div>
<ConnectStepsSection steps={resolvedSteps} state={state} projectKeys={projectKeys} />
</div>
</SheetContent>
</Sheet>
)
}