mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
## Summary
The `homeNew` PostHog experiment has concluded. This PR graduates it by
making the new homepage (`ProjectHome`, formerly `HomeV2`) the permanent
default for all users, and removes all dead code from the old
experiment.
## Changes
- Remove `homeNew` PostHog feature flag checks and `home_new` experiment
exposure tracking from 3 files
- Rename `HomeNew/` → `ProjectHome/` directory and `HomeV2` →
`ProjectHome` export
- Delete old `Home/Home.tsx` component (shared components like
`ProjectList/` are kept — still used by org pages)
- Delete `pages/project/[ref]/building.tsx` and add a server-side
redirect from `/project/:ref/building` → `/project/:ref` to prevent 404s
during rollout (old cached JS bundles may still route to `/building`)
- Simplify `ContentWrapper` building-state logic in `ProjectLayout` —
always redirect building projects to home, always suppress building
interstitial on home page
- Always route to `/project/{ref}` after project creation (remove
`/building` path)
- Update all Observability imports from `HomeNew` → `ProjectHome`
## Self-hosted behavior change
Self-hosted Studio previously showed the old `Home` component (client
libraries + example projects) since PostHog flags don't load. This PR
changes self-hosted to show `ProjectHome` (TopSection with service
status + instance diagram, advisor, custom reports). All sections query
backend APIs that exist on self-hosted. E2E tests pass against the
self-hosted build.
## Testing
- [x] `pnpm turbo run build --filter=studio` passes
- [x] No remaining references to `homeNew`, `home_new`, or `HomeNew` in
codebase
- [x] No broken imports to deleted files
- [x] Self-hosted E2E tests pass (145 passed, 1 flaky, 4 skipped)
- [x] `/building` redirect added to both platform and self-hosted config
blocks
**Quick test:**
1. Navigate to any project homepage — should render the ProjectHome
component
2. Create a new project — should redirect to `/project/{ref}` (not
`/building`)
3. Visit a project in `COMING_UP` state on a non-home route — should
redirect to home
4. Visit `/project/{ref}/building` directly — should 302 redirect to
`/project/{ref}`
## Linear
- fixes GROWTH-671
148 lines
5.8 KiB
TypeScript
148 lines
5.8 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 { 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 { CustomReportSection } from './CustomReportSection'
|
|
import { type GettingStartedState } from './GettingStarted/GettingStarted.types'
|
|
import { GettingStartedSection } from './GettingStarted/GettingStartedSection'
|
|
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 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'}`,
|
|
['getting-started', 'usage', 'advisor', 'custom-report']
|
|
)
|
|
|
|
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])
|
|
|
|
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) => id !== 'getting-started' || gettingStartedState !== 'hidden'
|
|
)}
|
|
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 === 'getting-started' &&
|
|
!isMatureProject &&
|
|
project &&
|
|
gettingStartedState !== 'hidden'
|
|
) {
|
|
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>
|
|
)
|
|
}
|