Files
supabase/apps/studio/components/interfaces/Home/Home.tsx
Alaister Young 5f533247e1 Update docs url to env var (#38772)
* Update Supabase docs URLs to use env variable

Co-authored-by: a <a@alaisteryoung.com>

* Refactor: Use DOCS_URL constant for documentation links

This change centralizes documentation links using a new DOCS_URL constant, improving maintainability and consistency.

Co-authored-by: a <a@alaisteryoung.com>

* Refactor: Use DOCS_URL constant for all documentation links

This change replaces hardcoded documentation URLs with a centralized constant, improving maintainability and consistency.

Co-authored-by: a <a@alaisteryoung.com>

* replace more instances

* ci: Autofix updates from GitHub workflow

* remaining instances

* fix duplicate useRouter

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: alaister <10985857+alaister@users.noreply.github.com>
2025-09-26 10:16:33 +00:00

289 lines
13 KiB
TypeScript

import dayjs from 'dayjs'
import Link from 'next/link'
import { useEffect, useRef } from 'react'
import { useParams } from 'common'
import { ClientLibrary } from 'components/interfaces/Home'
import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget'
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 { 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,
useProjectByRefQuery,
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 } = useProjectByRefQuery(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, isLoading: isLoadingTables } = useTablesQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: 'public',
})
const { data: functionsData, isLoading: isLoadingFunctions } = useEdgeFunctionsQuery({
projectRef: project?.ref,
})
const { data: replicasData, isLoading: 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)
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
project={{
ref: project?.ref,
organization_slug: organization?.slug,
cloud_provider: project?.cloud_provider,
infra_compute_size: 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>
{IS_PLATFORM && (
<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 />
{isPaused && <ProjectPausedState />}
</div>
</div>
{!isPaused && (
<>
<div className="py-16 border-b border-muted">
<div className="mx-auto max-w-7xl space-y-16">
{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">
{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>
)
}