mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 04:45:08 +03:00
## Summary The `connectSection` A/B experiment concluded as a true null (no effect on activation or any downstream metric after 13 days at 50/50, ~153K mature orgs). Saxon decided to ship the Connect section as the permanent experience. This PR removes the Getting Started control variant, the old Connect modal, all experiment flag gating, and related telemetry types. ## Changes - Delete `GettingStarted/` directory (5 files: section component, types, utils, progress hook) - Delete old `Connect.tsx` dialog modal (replaced by ConnectSheet) - Remove `connectSection` PostHog flag reads from `Home.tsx` and `LayoutHeader.tsx` - Remove `getSectionVisibility()` experiment logic and `ConnectSectionVariant` type - Remove `getting-started` from `DEFAULT_SECTION_ORDER` - Always render `<ConnectSheet />` in header (no more conditional with old `<Connect />` modal) - Remove `variant` prop from `ConnectSection` component - Remove 4 getting-started telemetry event interfaces from `telemetry-constants.ts` - Update `mergeSectionOrder` tests to reflect new section order ## Testing Tested on Vercel preview: - [x] Project homepage shows Connect section for new projects (< 10 days old) - [x] Connect section hidden for mature projects (> 10 days old) - [x] Header Connect button opens ConnectSheet (not old modal) - [x] Connect tiles open ConnectSheet with correct tab - [x] Section drag-and-drop still works without getting-started in the order - [x] Existing users with `getting-started` in localStorage order don't break (mergeSectionOrder strips it) ## Linear - fixes GROWTH-730 --------- Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
|
import {
|
|
useIsBranching2Enabled,
|
|
useIsFloatingMobileToolbarEnabled,
|
|
} from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
|
import { ConnectButton } from 'components/interfaces/ConnectButton/ConnectButton'
|
|
import { ConnectSheet } from 'components/interfaces/ConnectSheet/ConnectSheet'
|
|
import { LocalDropdown } from 'components/interfaces/LocalDropdown'
|
|
import { UserDropdown } from 'components/interfaces/UserDropdown'
|
|
import { AdvisorButton } from 'components/layouts/AppLayout/AdvisorButton'
|
|
import { AssistantButton } from 'components/layouts/AppLayout/AssistantButton'
|
|
import { BranchDropdown } from 'components/layouts/AppLayout/BranchDropdown'
|
|
import { InlineEditorButton } from 'components/layouts/AppLayout/InlineEditorButton'
|
|
import { OrganizationDropdown } from 'components/layouts/AppLayout/OrganizationDropdown'
|
|
import { ProjectDropdown } from 'components/layouts/AppLayout/ProjectDropdown'
|
|
import { HelpButton } from 'components/ui/HelpPanel/HelpButton'
|
|
import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils'
|
|
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
|
|
import dayjs from 'dayjs'
|
|
import { DevToolbarTrigger } from 'dev-tools'
|
|
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 { IS_PLATFORM } from 'lib/constants'
|
|
import { ChevronLeft } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { ReactNode, useMemo } from 'react'
|
|
import { Badge, cn } from 'ui'
|
|
import { CommandMenuTriggerInput } from 'ui-patterns'
|
|
|
|
import { BreadcrumbsView } from './BreadcrumbsView'
|
|
import { FeedbackDropdown } from './FeedbackDropdown/FeedbackDropdown'
|
|
import { HomeIcon } from './HomeIcon'
|
|
import { LocalVersionPopover } from './LocalVersionPopover'
|
|
import { MergeRequestButton } from './MergeRequestButton'
|
|
|
|
const LayoutHeaderDivider = ({ className, ...props }: React.HTMLProps<HTMLSpanElement>) => (
|
|
<span className={cn('text-border-stronger pr-2', className)} {...props}>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
width="16"
|
|
height="16"
|
|
stroke="currentColor"
|
|
strokeWidth="1"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
fill="none"
|
|
shapeRendering="geometricPrecision"
|
|
aria-hidden={true}
|
|
>
|
|
<path d="M16 3.549L7.12 20.600" />
|
|
</svg>
|
|
</span>
|
|
)
|
|
|
|
interface LayoutHeaderProps {
|
|
customHeaderComponents?: ReactNode
|
|
breadcrumbs?: unknown[]
|
|
headerTitle?: string
|
|
backToDashboardURL?: string
|
|
}
|
|
|
|
export const LayoutHeader = ({
|
|
customHeaderComponents,
|
|
breadcrumbs = [],
|
|
headerTitle,
|
|
backToDashboardURL,
|
|
}: LayoutHeaderProps) => {
|
|
const router = useRouter()
|
|
const { ref: projectRef, slug } = useParams()
|
|
const { data: selectedProject } = useSelectedProjectQuery()
|
|
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
|
const gitlessBranching = useIsBranching2Enabled()
|
|
|
|
const showFloatingMobileToolbar = useIsFloatingMobileToolbarEnabled()
|
|
const [commandMenuEnabled] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.HOTKEY_COMMAND_MENU, true)
|
|
|
|
const isAccountPage = router.pathname.startsWith('/account')
|
|
|
|
// We only want to query the org usage and check for possible over-ages for plans without usage billing enabled (free or pro with spend cap)
|
|
const { data: orgUsage } = useOrgUsageQuery(
|
|
{ orgSlug: selectedOrganization?.slug },
|
|
{ enabled: selectedOrganization?.usage_billing_enabled === false }
|
|
)
|
|
|
|
const exceedingLimits = useMemo(() => {
|
|
if (orgUsage) {
|
|
return getResourcesExceededLimitsOrg(orgUsage?.usages || []).length > 0
|
|
} else {
|
|
return false
|
|
}
|
|
}, [orgUsage])
|
|
|
|
const isNewProject =
|
|
selectedProject?.inserted_at !== undefined &&
|
|
dayjs(selectedProject.inserted_at).isAfter(dayjs().subtract(5, 'day'))
|
|
|
|
const connectButtonType = isNewProject ? 'primary' : 'default'
|
|
|
|
// show org selection if we are on a project page or on a explicit org route
|
|
const showOrgSelection = slug || (selectedOrganization && projectRef)
|
|
|
|
return (
|
|
<>
|
|
<header
|
|
className={cn(
|
|
'flex h-11 md:h-12 items-center flex-shrink-0 border-b',
|
|
showFloatingMobileToolbar && 'hidden md:flex'
|
|
)}
|
|
>
|
|
{backToDashboardURL && isAccountPage && (
|
|
<div className="flex items-center justify-center border-r flex-0 md:hidden h-full aspect-square">
|
|
<Link
|
|
href={backToDashboardURL}
|
|
className="flex items-center justify-center border-none !bg-transparent rounded-md min-w-[30px] w-[30px] h-[30px] text-foreground-lighter hover:text-foreground transition-colors"
|
|
>
|
|
<ChevronLeft strokeWidth={1.5} size={16} />
|
|
</Link>
|
|
</div>
|
|
)}
|
|
<div
|
|
className={cn(
|
|
'flex items-center justify-between h-full pr-3 flex-1 overflow-x-auto gap-x-8 pl-4'
|
|
)}
|
|
>
|
|
<div className="flex md:hidden items-center text-sm not-sr-only">
|
|
<AnimatePresence>
|
|
{headerTitle && (
|
|
<motion.div
|
|
className="flex items-center -ml-1"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -20 }}
|
|
transition={{
|
|
duration: 0.15,
|
|
ease: 'easeOut',
|
|
}}
|
|
>
|
|
<LayoutHeaderDivider />
|
|
<span className="text-foreground">{headerTitle}</span>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
<div className="hidden md:flex items-center text-sm">
|
|
<HomeIcon />
|
|
<div className="flex items-center md:pl-2">
|
|
{showOrgSelection && IS_PLATFORM ? (
|
|
<>
|
|
<LayoutHeaderDivider className="hidden md:block" />
|
|
<OrganizationDropdown />
|
|
</>
|
|
) : null}
|
|
<AnimatePresence>
|
|
{projectRef && (
|
|
<motion.div
|
|
className="flex items-center"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -20 }}
|
|
transition={{
|
|
duration: 0.15,
|
|
ease: 'easeOut',
|
|
}}
|
|
>
|
|
{IS_PLATFORM && <LayoutHeaderDivider />}
|
|
<ProjectDropdown />
|
|
|
|
{exceedingLimits && (
|
|
<div className="ml-2">
|
|
<Link href={`/org/${selectedOrganization?.slug}/usage`}>
|
|
<Badge variant="destructive">Exceeding usage limits</Badge>
|
|
</Link>
|
|
</div>
|
|
)}
|
|
|
|
{selectedProject && IS_PLATFORM && (
|
|
<>
|
|
<LayoutHeaderDivider />
|
|
<BranchDropdown />
|
|
</>
|
|
)}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
<AnimatePresence>
|
|
{headerTitle && (
|
|
<motion.div
|
|
className="flex items-center"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -20 }}
|
|
transition={{
|
|
duration: 0.15,
|
|
ease: 'easeOut',
|
|
}}
|
|
>
|
|
<LayoutHeaderDivider />
|
|
<span className="text-foreground">{headerTitle}</span>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
<AnimatePresence>
|
|
{projectRef && (
|
|
<motion.div
|
|
className="ml-3 items-center gap-x-2 flex"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -20 }}
|
|
transition={{
|
|
duration: 0.15,
|
|
ease: 'easeOut',
|
|
}}
|
|
>
|
|
{IS_PLATFORM && gitlessBranching && <MergeRequestButton />}
|
|
<ConnectButton buttonType={connectButtonType} />
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
<BreadcrumbsView defaultValue={breadcrumbs} />
|
|
</div>
|
|
<div className="flex items-center gap-x-2">
|
|
{customHeaderComponents && customHeaderComponents}
|
|
{IS_PLATFORM ? (
|
|
<>
|
|
<DevToolbarTrigger />
|
|
<FeedbackDropdown />
|
|
|
|
<div className="flex items-center gap-1 md:gap-2">
|
|
<CommandMenuTriggerInput
|
|
showShortcut={commandMenuEnabled}
|
|
placeholder="Search..."
|
|
className={cn(
|
|
'hidden md:flex md:min-w-32 xl:min-w-32 rounded-full bg-transparent',
|
|
'[&_.command-shortcut>div]:border-none',
|
|
'[&_.command-shortcut>div]:pr-2',
|
|
'[&_.command-shortcut>div]:bg-transparent',
|
|
'[&_.command-shortcut>div]:text-foreground-lighter'
|
|
)}
|
|
/>
|
|
<HelpButton />
|
|
<AdvisorButton projectRef={projectRef} />
|
|
<AnimatePresence initial={false}>
|
|
{!!projectRef && (
|
|
<>
|
|
<InlineEditorButton />
|
|
<AssistantButton />
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
<UserDropdown triggerClassName="hidden md:flex" />
|
|
</>
|
|
) : (
|
|
<>
|
|
<LocalVersionPopover />
|
|
<div className="flex items-center gap-1 md:gap-2">
|
|
<CommandMenuTriggerInput
|
|
placeholder="Search..."
|
|
className="hidden md:flex md:min-w-32 xl:min-w-32 rounded-full bg-transparent
|
|
[&_.command-shortcut>div]:border-none
|
|
[&_.command-shortcut>div]:pr-2
|
|
[&_.command-shortcut>div]:bg-transparent
|
|
[&_.command-shortcut>div]:text-foreground-lighter
|
|
"
|
|
/>
|
|
<HelpButton />
|
|
<AdvisorButton projectRef={projectRef} />
|
|
<AnimatePresence initial={false}>
|
|
{!!projectRef && (
|
|
<>
|
|
<InlineEditorButton />
|
|
<AssistantButton />
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
<LocalDropdown triggerClassName="hidden md:flex" />
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<ConnectSheet />
|
|
</>
|
|
)
|
|
}
|