Files
supabase/apps/studio/components/interfaces/ProjectCreation/ProjectCreationFooter.tsx
Joshen Lim 6e0078182b Consolidate project creation UI for vercel integration flow (#47995)
## Context

There's 2 areas of the dashboard that has the project creation flow -
and this PR consolidates both to use the same UI components to minimise
duplication + keep things consistent

### Before
<img width="1920" height="957" alt="image"
src="https://github.com/user-attachments/assets/2a7ab79d-71c7-43f2-925b-1e1666cc3a69"
/>

### After
<img width="1389" height="957" alt="image"
src="https://github.com/user-attachments/assets/f8465568-af99-46eb-80ee-7ac345383231"
/>

## Changes involved
- What this means for the project creation flow for Vercel Integration:
  - Smart region can be selected
  - Compute size can be selected
  - Enable Data API can be checked
  - Automatic RLS enable can be checked
- How it differs from the main project creation flow on `new/slug`
  - Organization selection is disabled (cannot be changed)
  - The following UI is hidden:
    - "Internal configuration" section
    - "GitHub repository" field
    - "Free project info" at the bottom
    - "Cancel" button

Eventually we could looking into reducing the differences more, e.g
having data seeding for both ways, and showing GitHub repository field
for Vercel integration

Resolves DEPR-616
Resolves FE-3905

## To test
Tbh, I'm not really sure how you'd be able to test the vercel
integration locally or on staging, this seemingly can only be done when
changes land on prod.

- What I'd do however is to just test the project creation flow
minimally by landing on
`/integrations/vercel/_/deploy-button/new-project`
  - Project creation can work, but just not the connection creation part

- And also test project creation on `/new/slug` as well

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
  * Added “Create sample tables with seed data” during project creation.
* Enhanced Vercel integration setup with a guided creation flow and
post-creation connection step.
* Added an option to disable organization selection in specialized
flows.
* Added support for triggering a callback after successful project
creation.
  * Added support for hiding the Cancel button in specialized flows.

* **UI Improvements**
* Refined the connected GitHub repository selector button/dropdown
visuals.
* Improved security options behavior for different project creation
contexts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 15:19:38 +08:00

195 lines
7.9 KiB
TypeScript

import { useFlag } from 'common'
import { useRouter } from 'next/router'
import { UseFormReturn } from 'react-hook-form'
import {
Badge,
Button,
PopoverSeparator,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import { CreateProjectForm } from './ProjectCreation.schema'
import { instanceLabel, monthlyInstancePrice } from './ProjectCreation.utils'
import { InlineLink } from '@/components/ui/InlineLink'
import { OrgProject } from '@/data/projects/org-projects-infinite-query'
import { useLastVisitedOrganization } from '@/hooks/misc/useLastVisitedOrganization'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { DOCS_URL } from '@/lib/constants'
interface ProjectCreationFooterProps {
form: UseFormReturn<CreateProjectForm>
canCreateProject: boolean
instanceSize?: string
organizationProjects: OrgProject[]
isCreatingNewProject: boolean
isSuccessNewProject: boolean
hideCancelButton: boolean
}
export const ProjectCreationFooter = ({
form,
canCreateProject,
instanceSize,
organizationProjects,
isCreatingNewProject,
isSuccessNewProject,
hideCancelButton,
}: ProjectCreationFooterProps) => {
const router = useRouter()
const { data: currentOrg } = useSelectedOrganizationQuery()
const isFreePlan = currentOrg?.plan?.id === 'free'
const { lastVisitedOrganization } = useLastVisitedOrganization()
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
const availableComputeCredits = organizationProjects.length === 0 ? 10 : 0
const additionalMonthlySpend = isFreePlan
? 0
: monthlyInstancePrice(instanceSize) - availableComputeCredits
// [kevin] This will eventually all be provided by a new API endpoint to preview and validate project creation, this is just for kaizen now
const monthlyComputeCosts =
// current project costs
organizationProjects.reduce((prev, acc) => {
const primaryDatabase = acc.databases.find((db) => db.identifier === acc.ref)
const cost = !!primaryDatabase ? monthlyInstancePrice(primaryDatabase.infra_compute_size) : 0
return prev + cost
}, 0) +
// selected compute size
monthlyInstancePrice(instanceSize) -
// compute credits
10
return (
<div key="panel-footer" className="grid grid-cols-12 w-full gap-4 items-center">
<div className="col-span-4">
{!isFreePlan &&
!projectCreationDisabled &&
canCreateProject &&
additionalMonthlySpend > 0 && (
<div className="flex justify-between text-sm">
<span>Additional costs</span>
<div className="text-brand flex gap-1 items-center font-mono font-medium">
<span>${additionalMonthlySpend}/m</span>
<InfoTooltip side="top" className="max-w-[450px] p-0">
<div className="p-4 text-sm text-foreground-light space-y-1">
<p>
Each project includes a dedicated Postgres instance running on its own server.
You are charged for the{' '}
<InlineLink href={`${DOCS_URL}/guides/platform/billing-on-supabase`}>
Compute resource
</InlineLink>{' '}
of that server, independent of your database usage.
</p>
{monthlyComputeCosts > 0 && (
<p>Compute costs are applied on top of your subscription plan costs.</p>
)}
</div>
<Table className="mt-2">
<TableHeader className="[&_th]:h-7">
<TableRow className="py-2">
<TableHead className="w-[170px]">Project</TableHead>
<TableHead>Compute Size</TableHead>
<TableHead className="text-right">Monthly Costs</TableHead>
</TableRow>
</TableHeader>
<TableBody className="[&_td]:py-2">
{organizationProjects.map((project) => {
const primaryDb = project.databases.find(
(db) => db.identifier === project.ref
)
return (
<TableRow key={project.ref} className="text-foreground-light">
<TableCell className="w-[170px] truncate">{project.name}</TableCell>
<TableCell className="text-center">
{instanceLabel(primaryDb?.infra_compute_size)}
</TableCell>
<TableCell className="text-right">
${monthlyInstancePrice(primaryDb?.infra_compute_size)}
</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell className="w-[170px] flex gap-2">
<span className="truncate">
{form.getValues('projectName') || 'New project'}
</span>
<Badge variant="success">New</Badge>
</TableCell>
<TableCell className="text-center">{instanceLabel(instanceSize)}</TableCell>
<TableCell className="text-right">
${monthlyInstancePrice(instanceSize)}
</TableCell>
</TableRow>
</TableBody>
</Table>
<PopoverSeparator />
<Table>
<TableHeader className="[&_th]:h-7">
<TableRow>
<TableHead colSpan={2}>Compute Credits</TableHead>
<TableHead colSpan={1} className="text-right">
-$10
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="[&_td]:py-2">
<TableRow className="text-foreground">
<TableCell colSpan={2}>
Total Monthly Compute Costs
{/**
* API currently doesnt output replica information on the projects list endpoint. Until then, we cannot correctly calculate the costs including RRs.
* Will be adjusted in the future [kevin]
*/}
{organizationProjects.length > 0 && (
<p className="text-xs text-foreground-lighter">
Excluding Read replicas
</p>
)}
</TableCell>
<TableCell colSpan={1} className="text-right">
${monthlyComputeCosts}
</TableCell>
</TableRow>
</TableBody>
</Table>
</InfoTooltip>
</div>
</div>
)}
</div>
<div className="flex items-end col-span-8 space-x-2 ml-auto">
{!hideCancelButton && (
<Button
variant="default"
disabled={isCreatingNewProject || isSuccessNewProject}
onClick={() => {
if (!!lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}}
>
Cancel
</Button>
)}
<Button
type="submit"
loading={isCreatingNewProject || isSuccessNewProject}
disabled={!canCreateProject}
>
Create new project
</Button>
</div>
</div>
)
}