| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- import { useParams } from 'common'
- import dayjs from 'dayjs'
- import Link from 'next/link'
- import { useEffect, useRef } from 'react'
- import {
- Badge,
- cn,
- Tabs_Shadcn_,
- TabsContent_Shadcn_,
- TabsList_Shadcn_,
- TabsTrigger_Shadcn_,
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from 'ui'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- 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'
- 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 px-4">
- <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>
- )
- }
|