mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
## 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>
168 lines
6.7 KiB
TypeScript
168 lines
6.7 KiB
TypeScript
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
|
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
|
|
import { IS_PLATFORM, useFlag, useParams } from 'common'
|
|
import { ProjectUsageSection as ProjectUsageSectionV1 } from 'components/interfaces/Home/ProjectUsageSection'
|
|
import { SortableSection } from 'components/interfaces/ProjectHome/SortableSection'
|
|
import { TopSection } from 'components/interfaces/ProjectHome/TopSection'
|
|
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'
|
|
import { useAppStateSnapshot } from 'state/app-state'
|
|
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, mergeSectionOrder, getSectionVisibility } from './Home.utils'
|
|
import { ProjectUsageSection as ProjectUsageSectionV2 } from './ProjectUsageSection'
|
|
|
|
export const ProjectHome = () => {
|
|
const { enableBranching } = useParams()
|
|
const snap = useAppStateSnapshot()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const track = useTrack()
|
|
|
|
const showHomepageUsageV2 = useFlag('newHomepageUsageV2')
|
|
const connectSectionVariant = usePHFlag<ConnectSectionVariant | false>('connectSection')
|
|
|
|
const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day'))
|
|
|
|
const hasShownEnableBranchingModalRef = useRef(false)
|
|
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
|
|
const isComingUp = project?.status === PROJECT_STATUS.COMING_UP
|
|
|
|
const [sectionOrder, setSectionOrder] = useLocalStorage<string[]>(
|
|
`home-section-order-${project?.ref || 'default'}`,
|
|
DEFAULT_SECTION_ORDER
|
|
)
|
|
|
|
const [gettingStartedState, setGettingStartedState] = useLocalStorage<GettingStartedState>(
|
|
`home-getting-started-${project?.ref || 'default'}`,
|
|
'empty'
|
|
)
|
|
|
|
const UsageSection = showHomepageUsageV2 ? ProjectUsageSectionV2 : ProjectUsageSectionV1
|
|
|
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
|
|
|
|
const handleDragEnd = (event: DragEndEvent) => {
|
|
const { active, over } = event
|
|
if (!over || active.id === over.id) return
|
|
setSectionOrder((items) => {
|
|
const oldIndex = items.indexOf(String(active.id))
|
|
const newIndex = items.indexOf(String(over.id))
|
|
if (oldIndex === -1 || newIndex === -1) return items
|
|
|
|
track('home_section_rows_moved', {
|
|
section_moved: String(active.id),
|
|
old_position: oldIndex,
|
|
new_position: newIndex,
|
|
})
|
|
|
|
return arrayMove(items, oldIndex, newIndex)
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (enableBranching && !hasShownEnableBranchingModalRef.current) {
|
|
hasShownEnableBranchingModalRef.current = true
|
|
snap.setShowCreateBranchModal(true)
|
|
}
|
|
}, [enableBranching, snap])
|
|
|
|
useEffect(() => {
|
|
setSectionOrder(mergeSectionOrder)
|
|
}, [setSectionOrder])
|
|
|
|
const { showConnectSection, showGettingStarted } = getSectionVisibility({
|
|
connectSectionVariant,
|
|
isMatureProject,
|
|
hasProject: !!project,
|
|
gettingStartedState,
|
|
})
|
|
|
|
return (
|
|
<div className="w-full h-full">
|
|
<ScaffoldContainer size="large" className={cn(isPaused && 'h-full')}>
|
|
<ScaffoldSection
|
|
isFullWidth
|
|
className={cn(isPaused ? 'h-full flex justify-center !p-0' : 'pt-16 pb-0')}
|
|
>
|
|
<TopSection />
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
{!isPaused && (
|
|
<ScaffoldContainer size="large">
|
|
<ScaffoldSection isFullWidth className="gap-16 pb-32">
|
|
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
|
<SortableContext
|
|
items={sectionOrder.filter((id) => {
|
|
if (id === 'connect') return showConnectSection
|
|
if (id === 'getting-started') return showGettingStarted
|
|
return true
|
|
})}
|
|
strategy={verticalListSortingStrategy}
|
|
>
|
|
{sectionOrder.map((id) => {
|
|
if (IS_PLATFORM && id === 'usage') {
|
|
return (
|
|
<div key={id} className={cn(isComingUp && 'opacity-60 pointer-events-none')}>
|
|
<SortableSection id={id}>
|
|
<UsageSection />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (id === 'connect' && showConnectSection) {
|
|
return (
|
|
<SortableSection key={id} id={id}>
|
|
<ConnectSection variant={connectSectionVariant as ConnectSectionVariant} />
|
|
</SortableSection>
|
|
)
|
|
}
|
|
if (id === 'getting-started' && showGettingStarted) {
|
|
return (
|
|
<SortableSection key={id} id={id}>
|
|
<GettingStartedSection
|
|
value={gettingStartedState}
|
|
onChange={setGettingStartedState}
|
|
/>
|
|
</SortableSection>
|
|
)
|
|
}
|
|
if (id === 'advisor') {
|
|
return (
|
|
<div key={id} className={cn(isComingUp && 'opacity-60 pointer-events-none')}>
|
|
<SortableSection id={id}>
|
|
<AdvisorSection showEmptyState={isComingUp} />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (id === 'custom-report') {
|
|
return (
|
|
<div key={id} className={cn(isComingUp && 'opacity-60 pointer-events-none')}>
|
|
<SortableSection id={id}>
|
|
<CustomReportSection />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
})}
|
|
</SortableContext>
|
|
</DndContext>
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|