chore(studio): graduate homeNew experiment (#43437)

## 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
This commit is contained in:
Pamela Chia authored and GitHub committed 2026-03-10 17:03:58 +09:00
1 parent fc7fb50aa2
commit 01c178e159
36 files changed
+73 -451

No files matched your search

-1
View File
@@ -326,7 +326,6 @@
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1,
"components/interfaces/Functions/EdgeFunctionsListItem.tsx": 2,
"components/interfaces/Functions/FunctionsNav.tsx": 1,
"components/interfaces/Home/Home.tsx": 6,
"components/interfaces/Home/ProjectList/ProjectList.tsx": 1,
"components/interfaces/Home/ProjectUsage.tsx": 1,
"components/interfaces/Home/ServiceStatus.tsx": 1,
@@ -1,286 +0,0 @@
import dayjs from 'dayjs'
import Link from 'next/link'
import { useEffect, useRef } from 'react'
import { useParams } from 'common'
import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget'
import { ClientLibrary } from 'components/interfaces/Home/ClientLibrary'
import { ExampleProject } from 'components/interfaces/Home/ExampleProject'
import { EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants'
import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel'
import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection'
import { ServiceStatus } from 'components/interfaces/Home/ServiceStatus'
import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState'
import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import { InlineLink } from 'components/ui/InlineLink'
import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner'
import { useBranchesQuery } from 'data/branches/branches-query'
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useTablesQuery } from 'data/tables/tables-query'
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { DOCS_URL, IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import {
Badge,
cn,
Tabs_Shadcn_,
TabsContent_Shadcn_,
TabsList_Shadcn_,
TabsTrigger_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
export const Home = () => {
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const { data: parentProject } = useProjectDetailQuery({ ref: project?.parent_project_ref })
const isOrioleDb = useIsOrioleDb()
const snap = useAppStateSnapshot()
const { ref, enableBranching } = useParams()
const { projectHomepageExampleProjects, projectHomepageClientLibraries: clientLibraries } =
useCustomContent(['project_homepage:example_projects', 'project_homepage:client_libraries'])
const {
projectHomepageShowInstanceSize: showInstanceSize,
projectHomepageShowExamples: showExamples,
} = useIsFeatureEnabled(['project_homepage:show_instance_size', 'project_homepage:show_examples'])
const hasShownEnableBranchingModalRef = useRef(false)
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
const isNewProject = dayjs(project?.inserted_at).isAfter(dayjs().subtract(2, 'day'))
useEffect(() => {
if (enableBranching && !hasShownEnableBranchingModalRef.current) {
hasShownEnableBranchingModalRef.current = true
snap.setShowCreateBranchModal(true)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableBranching])
const { data: tablesData, isPending: isLoadingTables } = useTablesQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: 'public',
})
const { data: functionsData, isPending: isLoadingFunctions } = useEdgeFunctionsQuery({
projectRef: project?.ref,
})
const { data: replicasData, isPending: isLoadingReplicas } = useReadReplicasQuery({
projectRef: project?.ref,
})
const { data: branches } = useBranchesQuery({
projectRef: project?.parent_project_ref ?? project?.ref,
})
const mainBranch = branches?.find((branch) => branch.is_default)
const currentBranch = branches?.find((branch) => branch.project_ref === project?.ref)
const isMainBranch = currentBranch?.name === mainBranch?.name
let projectName = 'Welcome to your project'
if (currentBranch && !isMainBranch) {
projectName = currentBranch?.name
} else if (project?.name) {
projectName = project?.name
}
const tablesCount = Math.max(0, tablesData?.length ?? 0)
const functionsCount = Math.max(0, functionsData?.length ?? 0)
// [Joshen] JFYI minus 1 as the replicas endpoint returns the primary DB minimally
const replicasCount = Math.max(0, (replicasData?.length ?? 1) - 1)
if (isPaused) {
return (
<div className="w-full h-full flex items-center justify-center">
<ProjectPausedState />
</div>
)
}
return (
<div className="w-full px-4">
<div className={cn('py-16 ', !isPaused && 'border-b border-muted ')}>
<div className="mx-auto max-w-7xl flex flex-col gap-y-4">
<div className="flex flex-col md:flex-row md:items-center gap-6 justify-between w-full">
<div className="flex flex-col md:flex-row md:items-end gap-3 w-full">
<div>
{!isMainBranch && (
<Link
href={`/project/${parentProject?.ref}`}
className="text-sm text-foreground-light"
>
{parentProject?.name}
</Link>
)}
<h1 className="text-3xl">{projectName}</h1>
</div>
<div className="flex items-center gap-x-2 mb-1">
{isOrioleDb && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning">OrioleDB</Badge>
</TooltipTrigger>
<TooltipContent side="bottom" align="start" className="max-w-80 text-center">
This project is using Postgres with OrioleDB which is currently in preview and
not suitable for production workloads. View our{' '}
<InlineLink href={`${DOCS_URL}/guides/database/orioledb`}>
documentation
</InlineLink>{' '}
for all limitations.
</TooltipContent>
</Tooltip>
)}
{showInstanceSize && (
<ComputeBadgeWrapper
projectRef={project?.ref}
slug={organization?.slug}
cloudProvider={project?.cloud_provider}
computeSize={project?.infra_compute_size}
/>
)}
</div>
</div>
<div className="flex items-center">
{project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && (
<div className="flex items-center gap-x-6">
<div className="flex flex-col gap-y-1">
<Link
href={`/project/${ref}/editor`}
className="transition text-foreground-light hover:text-foreground text-sm"
>
Tables
</Link>
{isLoadingTables ? (
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
) : (
<p className="text-2xl tabular-nums">{tablesCount}</p>
)}
</div>
<div className="flex flex-col gap-y-1">
<Link
href={`/project/${ref}/functions`}
className="transition text-foreground-light hover:text-foreground text-sm"
>
Functions
</Link>
{isLoadingFunctions ? (
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
) : (
<p className="text-2xl tabular-nums">{functionsCount}</p>
)}
</div>
{IS_PLATFORM && (
<div className="flex flex-col gap-y-1">
<Link
href={`/project/${ref}/settings/infrastructure`}
className="transition text-foreground-light hover:text-foreground text-sm"
>
Replicas
</Link>
{isLoadingReplicas ? (
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
) : (
<p className="text-2xl tabular-nums">{replicasCount}</p>
)}
</div>
)}
</div>
)}
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && (
<div className="ml-6 border-l flex items-center w-[145px] justify-end">
<ServiceStatus />
</div>
)}
</div>
</div>
<ProjectUpgradeFailedBanner />
</div>
</div>
<>
<div className="py-16 border-b border-muted">
<div className="mx-auto max-w-7xl space-y-16 @container">
{IS_PLATFORM && project?.status !== PROJECT_STATUS.INACTIVE && (
<>{isNewProject ? <NewProjectPanel /> : <ProjectUsageSection />}</>
)}
{!isNewProject && project?.status !== PROJECT_STATUS.INACTIVE && <AdvisorWidget />}
</div>
</div>
<div className="bg-surface-100/5 py-16">
<div className="mx-auto max-w-7xl space-y-16 @container">
{project?.status !== PROJECT_STATUS.INACTIVE && (
<>
<div className="space-y-8">
<h2 className="text-lg">Client libraries</h2>
<div className="grid grid-cols-2 gap-x-8 gap-y-8 md:gap-12 mb-12 md:grid-cols-3">
{clientLibraries!.map((library) => (
// [Alaister]: Looks like the useCustomContent has wonky types. I'll look at a fix later.
<ClientLibrary key={(library as any).language} {...(library as any)} />
))}
</div>
</div>
{showExamples && (
<div className="flex flex-col gap-y-8">
<h4 className="text-lg">Example projects</h4>
{!!projectHomepageExampleProjects ? (
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
{/* [Alaister]: Looks like the useCustomContent has wonky types. I'll look at a fix later. */}
{(projectHomepageExampleProjects as any)
.sort((a: any, b: any) => a.title.localeCompare(b.title))
.map((project: any) => (
<ExampleProject key={project.url} {...project} />
))}
</div>
) : (
<div className="flex justify-center">
<Tabs_Shadcn_ defaultValue="app" className="w-full">
<TabsList_Shadcn_ className="flex gap-4 mb-8">
<TabsTrigger_Shadcn_ value="app">App Frameworks</TabsTrigger_Shadcn_>
<TabsTrigger_Shadcn_ value="mobile">
Mobile Frameworks
</TabsTrigger_Shadcn_>
</TabsList_Shadcn_>
<TabsContent_Shadcn_ value="app">
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
{EXAMPLE_PROJECTS.filter((project) => project.type === 'app')
.sort((a, b) => a.title.localeCompare(b.title))
.map((project) => (
<ExampleProject key={project.url} {...project} />
))}
</div>
</TabsContent_Shadcn_>
<TabsContent_Shadcn_ value="mobile">
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
{EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile')
.sort((a, b) => a.title.localeCompare(b.title))
.map((project) => (
<ExampleProject key={project.url} {...project} />
))}
</div>
</TabsContent_Shadcn_>
</Tabs_Shadcn_>
</div>
)}
</div>
)}
</>
)}
</div>
</div>
</>
</div>
)
}
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import {
calculateErrorRate,
calculateSuccessRate,
@@ -3,8 +3,8 @@ import {
sumErrors,
sumTotal,
sumWarnings,
} from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
} from '../ProjectHome/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { useServiceHealthMetrics } from './useServiceHealthMetrics'
export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, Loading } from 'ui'
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
const colorClassMap: Record<string, string> = {
@@ -4,7 +4,7 @@ import { Badge, Card, CardContent, Loading, Tooltip, TooltipContent, TooltipTrig
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
import { ButtonTooltip } from '../../ui/ButtonTooltip'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
type ServiceConfig = {
@@ -1,7 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import { get } from 'data/fetchers'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { generateRegexpWhere } from '../Reports/Reports.constants'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
type PostgrestMetricsVariables = {
projectRef: string
@@ -4,15 +4,15 @@ import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted
import useTimeseriesUnixToIso from 'hooks/analytics/useTimeseriesUnixToIso'
import { useMemo } from 'react'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { LogsTableName } from '../Settings/Logs/Logs.constants'
import { genChartQuery } from '../Settings/Logs/Logs.utils'
import {
type RawChartData,
calculateAggregatedMetrics,
calculateDateRange,
calculateHealthMetrics,
transformToBarChartData,
type RawChartData,
} from './useServiceHealthMetrics.utils'
export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
@@ -1,7 +1,7 @@
import dayjs from 'dayjs'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import {
calculateAggregatedMetrics,
calculateDateRange,
@@ -1,12 +1,12 @@
import dayjs from 'dayjs'
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import {
computeSuccessAndNonSuccessRates,
sumErrors,
sumTotal,
sumWarnings,
} from '../HomeNew/ProjectUsage.metrics'
} from '../ProjectHome/ProjectUsage.metrics'
/**
* Calculates the date range for fetching service health metrics
@@ -1,4 +1,5 @@
import dayjs from 'dayjs'
import type { LogsBarChartDatum } from './ProjectUsage.metrics'
/**
@@ -1,21 +1,16 @@
import {
closestCenter,
DndContext,
DragEndEvent,
PointerSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core'
import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from '@dnd-kit/sortable'
import { arrayMove, rectSortingStrategy, SortableContext, useSortable } from '@dnd-kit/sortable'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import dayjs from 'dayjs'
import { Plus, RefreshCw } from 'lucide-react'
import type { CSSProperties, DragEvent, ReactNode } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { keepPreviousData } from '@tanstack/react-query'
import { useParams } from 'common'
import { SnippetDropdown } from 'components/interfaces/HomeNew/SnippetDropdown'
import { SnippetDropdown } from 'components/interfaces/ProjectHome/SnippetDropdown'
import { ReportBlock } from 'components/interfaces/Reports/ReportBlock/ReportBlock'
import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import type { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig'
@@ -29,17 +24,20 @@ import {
UpsertContentPayload,
useContentUpsertMutation,
} from 'data/content/content-upsert-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import dayjs from 'dayjs'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useTrack } from 'lib/telemetry/track'
import { Plus, RefreshCw } from 'lucide-react'
import type { CSSProperties, DragEvent, ReactNode } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import type { Dashboards } from 'types'
import { Button } from 'ui'
import { Row } from 'ui-patterns'
import { keepPreviousData } from '@tanstack/react-query'
export function CustomReportSection() {
const startDate = dayjs().subtract(7, 'day').toISOString()
@@ -48,8 +46,7 @@ export function CustomReportSection() {
const { ref } = useParams()
const { profile } = useProfile()
const state = useDatabaseSelectorStateSnapshot()
const { data: organization } = useSelectedOrganizationQuery()
const { mutate: sendEvent } = useSendEventMutation()
const track = useTrack()
const { invalidateInfraMonitoringQuery } = useInvalidateAnalyticsQuery()
const { data: project } = useSelectedProjectQuery()
@@ -203,19 +200,7 @@ export function CustomReportSection() {
},
})
if (ref && organization?.slug) {
sendEvent({
action: 'home_custom_report_block_added',
properties: {
block_id: snippet.id,
position: 0,
},
groups: {
project: ref,
organization: organization.slug,
},
})
}
track('home_custom_report_block_added', { block_id: snippet.id, position: 0 })
return
}
const current = [...editableReport.layout]
@@ -225,19 +210,10 @@ export function CustomReportSection() {
setEditableReport(updated)
persistReport(updated)
if (ref && organization?.slug) {
sendEvent({
action: 'home_custom_report_block_added',
properties: {
block_id: snippet.id,
position: current.length - 1,
},
groups: {
project: ref,
organization: organization.slug,
},
})
}
track('home_custom_report_block_added', {
block_id: snippet.id,
position: current.length - 1,
})
},
[
editableReport,
@@ -245,8 +221,7 @@ export function CustomReportSection() {
ref,
profile,
upsertContent,
organization,
sendEvent,
track,
findNextPlacement,
createSnippetChartBlock,
persistReport,
@@ -265,17 +240,8 @@ export function CustomReportSection() {
setEditableReport(updated)
persistReport(updated)
if (ref && organization?.slug && removedChart) {
sendEvent({
action: 'home_custom_report_block_removed',
properties: {
block_id: String(removedChart.id),
},
groups: {
project: ref,
organization: organization.slug,
},
})
if (removedChart) {
track('home_custom_report_block_removed', { block_id: String(removedChart.id) })
}
}
@@ -325,12 +291,9 @@ export function CustomReportSection() {
// Handle success optimistically
toast.success(`Successfully created new query: ${label}`, { id: toastId })
addSnippetToReport({ id: payload.id, name: label })
sendEvent({
action: 'custom_report_assistant_sql_block_added',
groups: { project: ref, organization: organization?.slug ?? 'Unknown' },
})
track('custom_report_assistant_sql_block_added')
},
[ref, profile, project, upsertContent, addSnippetToReport, sendEvent, organization]
[ref, profile, project, upsertContent, addSnippetToReport, track]
)
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
@@ -1,32 +1,30 @@
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import dayjs from 'dayjs'
import { useEffect, useRef } from 'react'
import { IS_PLATFORM, useFlag, useParams } from 'common'
import { ProjectUsageSection as ProjectUsageSectionV1 } from 'components/interfaces/Home/ProjectUsageSection'
import { SortableSection } from 'components/interfaces/HomeNew/SortableSection'
import { TopSection } from 'components/interfaces/HomeNew/TopSection'
import { SortableSection } from 'components/interfaces/ProjectHome/SortableSection'
import { TopSection } from 'components/interfaces/ProjectHome/TopSection'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import dayjs from 'dayjs'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
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 HomeV2 = () => {
export const ProjectHome = () => {
const { enableBranching } = useParams()
const snap = useAppStateSnapshot()
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const { mutate: sendEvent } = useSendEventMutation()
const track = useTrack()
const showHomepageUsageV2 = useFlag('newHomepageUsageV2')
@@ -58,20 +56,11 @@ export const HomeV2 = () => {
const newIndex = items.indexOf(String(over.id))
if (oldIndex === -1 || newIndex === -1) return items
if (project?.ref && organization?.slug) {
sendEvent({
action: 'home_section_rows_moved',
properties: {
section_moved: String(active.id),
old_position: oldIndex,
new_position: newIndex,
},
groups: {
project: project.ref,
organization: organization.slug,
},
})
}
track('home_section_rows_moved', {
section_moved: String(active.id),
old_position: oldIndex,
new_position: newIndex,
})
return arrayMove(items, oldIndex, newIndex)
})
@@ -1,4 +1,5 @@
import { describe, it, expect } from 'vitest'
import { describe, expect, it } from 'vitest'
import {
computeSuccessAndNonSuccessRates,
sumErrors,
@@ -1,5 +1,5 @@
import { ActivityStats } from 'components/interfaces/HomeNew/ActivityStats'
import { ProjectConnectionHoverCard } from 'components/interfaces/HomeNew/ProjectConnectionHoverCard'
import { ActivityStats } from 'components/interfaces/ProjectHome/ActivityStats'
import { ProjectConnectionHoverCard } from 'components/interfaces/ProjectHome/ProjectConnectionHoverCard'
import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState'
import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import { InlineLink } from 'components/ui/InlineLink'
@@ -35,7 +35,6 @@ import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { withAuth } from '@/hooks/misc/withAuth'
import { usePHFlag } from '@/hooks/ui/useFlag'
import { PROJECT_STATUS } from '@/lib/constants'
import { buildStudioPageTitle } from '@/lib/page-title'
import { useAppStateSnapshot } from '@/state/app-state'
@@ -277,8 +276,6 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
const { ref } = useParams()
const state = useDatabaseSelectorStateSnapshot()
const { data: selectedProject } = useSelectedProjectQuery()
const isHomeNew = usePHFlag('homeNew') === 'new-home'
const isBackupsPage = router.pathname.includes('/project/[ref]/database/backups')
const isHomePage = router.pathname === '/project/[ref]'
@@ -298,13 +295,10 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
const isProjectPauseFailed = selectedProject?.status === PROJECT_STATUS.PAUSE_FAILED
const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'
// handle redirect to home for building state
const shouldRedirectToHomeForBuilding =
isProjectBuilding && requiresDbConnection && isHomeNew && !isHomePage
const shouldRedirectToHomeForBuilding = isProjectBuilding && requiresDbConnection && !isHomePage
// We won't be showing the building state with the new home page
const shouldShowBuildingState =
isProjectBuilding && requiresDbConnection && !(isHomeNew && isHomePage)
// Don't show building state on the home page — it handles building state inline
const shouldShowBuildingState = isProjectBuilding && requiresDbConnection && !isHomePage
useEffect(() => {
if (shouldRedirectToHomeForBuilding && ref) {
+10
View File
@@ -88,6 +88,11 @@ const nextConfig = {
destination: '/sign-in',
permanent: false,
},
{
source: '/project/:ref/building',
destination: '/project/:ref',
permanent: false,
},
]
: [
{
@@ -120,6 +125,11 @@ const nextConfig = {
destination: '/project/default',
permanent: false,
},
{
source: '/project/:ref/building',
destination: '/project/:ref',
permanent: false,
},
]),
{
source: '/project/:ref/auth',
+1 -2
View File
@@ -85,7 +85,6 @@ const Wizard: NextPageWithLayout = () => {
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
const showPostgresVersionSelector = useFlag('showPostgresVersionSelector')
const cloudProviderEnabled = useFlag('enableFlyCloudProvider')
const isHomeNew = usePHFlag('homeNew') === 'new-home'
const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
const isNotOnHigherPlan = !['team', 'enterprise', 'platform'].includes(currentOrg?.plan.id ?? '')
@@ -253,7 +252,7 @@ const Wizard: NextPageWithLayout = () => {
organization: res.organization_slug,
}
)
router.push(isHomeNew ? `/project/${res.ref}` : `/project/${res.ref}/building`)
router.push(`/project/${res.ref}`)
},
})
@@ -1,36 +0,0 @@
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import type { NextPageWithLayout } from 'types'
const ProjectBuildingPage: NextPageWithLayout = () => {
return <RedirectToDashboard />
}
ProjectBuildingPage.getLayout = (page) => (
<DefaultLayout>
<ProjectLayoutWithAuth title="Project Building">{page}</ProjectLayoutWithAuth>
</DefaultLayout>
)
export default ProjectBuildingPage
const RedirectToDashboard = () => {
const router = useRouter()
const { ref } = router.query
useEffect(() => {
// Use redirect to reload store data properly after project has been
// been created or unpaused, this is necessarily especially for unpausing
// so that the dashboard fetches the updated connection strings
// window.location.replace(`/project/${ref}`)
// TODO: for experiment only
// Cos we already pulling new connectionString after project building/restoring
// just use normal router.push is enough
router.push(`/project/${ref}`)
}, [])
return null
}
+2 -17
View File
@@ -1,25 +1,10 @@
import { IS_PLATFORM } from 'common'
import { Home } from 'components/interfaces/Home/Home'
import { HomeV2 } from 'components/interfaces/HomeNew/Home'
import { ProjectHome } from 'components/interfaces/ProjectHome/Home'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout'
import { useTrackExperimentExposure } from 'hooks/misc/useTrackExperimentExposure'
import { usePHFlag } from 'hooks/ui/useFlag'
import type { NextPageWithLayout } from 'types'
const HomePage: NextPageWithLayout = () => {
const homeNewVariant = usePHFlag<string>('homeNew')
const isHomeNew = homeNewVariant === 'new-home'
useTrackExperimentExposure(
'home_new',
IS_PLATFORM && typeof homeNewVariant !== 'boolean' ? homeNewVariant : undefined
)
if (isHomeNew) {
return <HomeV2 />
}
return <Home />
return <ProjectHome />
}
HomePage.getLayout = (page) => (
+1 -3
View File
@@ -286,9 +286,7 @@ export interface MwDiagData {
export function parseMwDiagCookie(cookieHeader: string): MwDiagData | null {
try {
const cookies = cookieHeader.split(';')
const match = cookies
.map((c) => c.trim())
.find((c) => c.startsWith(`${MW_DIAG_COOKIE_NAME}=`))
const match = cookies.map((c) => c.trim()).find((c) => c.startsWith(`${MW_DIAG_COOKIE_NAME}=`))
if (!match) return null
+5 -1
View File
@@ -13,7 +13,11 @@ import { IS_PLATFORM, IS_PROD, LOCAL_STORAGE_KEYS } from './constants'
import { useFeatureFlags } from './feature-flags'
import { post } from './fetchWrappers'
import type { FirstReferrerData, MwDiagData } from './first-referrer-cookie'
import { isExternalReferrer, parseFirstReferrerCookie, parseMwDiagCookie } from './first-referrer-cookie'
import {
isExternalReferrer,
parseFirstReferrerCookie,
parseMwDiagCookie,
} from './first-referrer-cookie'
import { ensurePlatformSuffix, isBrowser } from './helpers'
import { useParams, useTelemetryCookie } from './hooks'
import { posthogClient, type ClientTelemetryEvent } from './posthog-client'