mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore(studio): polish vercel deploy-button new project interstitial (#48113)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Studio UI polish for the Vercel deploy-button new-project connect flow (DEPR-616 follow-up). ## What is the current behavior? - Deploy-button project creation still used `VercelIntegrationWindowLayout` (top bar + Docs/Support footer) while install and choose-project already use shared `InterstitialLayout` - The remove-integration note sat as a floating admonition above the form - Advanced / internal-only config sections could show double dividers, and the Oriole radio bottom border could clip inside the collapsible ## What is the new behavior? - Deploy-button new-project uses `InterstitialLayout` with `VercelIntegrationLogo` and `VercelIntegrationFooter`, matching the other Vercel connect surfaces - Regular `/new` project creation is unchanged; Panel chrome is only flattened when `isVercelIntegrationFlow` is set - Removes unused `VercelIntegrationWindowLayout` / `IntegrationWindowLayout` - Fixes Advanced/Internal-only dividers and collapsible border clipping | Before | After | | --- | --- | | <img width="965" height="1107" alt="Supabase" src="https://github.com/user-attachments/assets/99c1225c-c9e5-4fac-bd64-17920403e447" /> | <img width="965" height="1107" alt="Create Vercel Project Supabase" src="https://github.com/user-attachments/assets/c42bff96-5f9f-4c8b-8f3c-093e380f90ed" /> | ## Additional context ### Testing With Studio running locally and while signed in: 1. Open `http://localhost:8082/integrations/vercel/<your-org-slug>/deploy-button/new-project` 2. Confirm the interstitial card: Vercel + Supabase logo pair, “Create a new project” title, form fields, and the muted remove-integration footer under the card 3. Confirm there is no old window chrome (no “Marketplace Connector” / “Deploy Button” top bar, no Docs/Support footer) 4. Optionally submit and confirm project creation still works 5. Spot-check `/new` to confirm the normal project creation form is unchanged If you have advanced config enabled, expand Advanced Configuration and confirm single dividers (not doubles) and that the Oriole option’s bottom border is not clipped. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **New Features** * Added configurable width to the shared interstitial layout for Vercel project creation. * Updated the Vercel “new project” flow to use the interstitial UI. * **Bug Fixes** * Prevented child borders/shadows from being clipped in expandable configuration sections. * **Refactor** * Removed legacy Vercel/window layout components and updated routing to rely on the interstitial flow. * Switched advanced and internal-only configuration sections to render inside panel content. * **Documentation** * Refreshed Vercel integration route guidance in the migration checklist and inline route comments. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
d0abc7a64a
commit
0324f3422a
12 files changed
+211
-338
No files matched your search
@@ -742,8 +742,6 @@
|
||||
"components/layouts/DatabaseLayout/DatabaseLayout.tsx": 1,
|
||||
"components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx": 1,
|
||||
"components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout.tsx": 1,
|
||||
"components/layouts/IntegrationsLayout/IntegrationWindowLayout.tsx": 1,
|
||||
"components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout.tsx": 1,
|
||||
"components/layouts/JWTKeys/JWTKeysLayout.tsx": 1,
|
||||
"components/layouts/LogsLayout/LogsLayout.tsx": 1,
|
||||
"components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx": 1,
|
||||
|
||||
@@ -78,7 +78,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
|
||||
- [x] `routes/_app/account.tsx` — AccountLayout (reads `accountLayoutTitle` from leaf `staticData`)
|
||||
- [x] `routes/_app/org.tsx` — OrganizationLayout (reads `orgLayoutTitle` from leaf `staticData`). **Delta vs plan:** placed at `_app/org.tsx` (wraps both `/org/` index and `/org/$slug/*`) instead of `_app/org/$slug.tsx`. PageLayout stays inline on `/org/$slug/index.tsx` since only that one route uses it.
|
||||
- [x] `routes/_app/new.tsx` — skipped; only `_app/new/index.tsx` lives under \_app (inlines WizardLayout). `new/$slug` is top-level (no AppLayout) so a sub-shell would not actually share state.
|
||||
- [x] `routes/integrations/vercel.tsx` — VercelIntegrationWindowLayout. **Delta vs plan:** placed at top-level rather than under `_app/` — Next getLayout for all three leaves wraps only in VercelIntegrationWindowLayout, no AppLayout/DefaultLayout.
|
||||
- [x] `routes/integrations/vercel.tsx` — passthrough `Outlet` only (no shared window layout). **Delta vs plan:** placed at top-level rather than under `_app/`. All three Vercel leaves (install, marketplace choose-project, deploy-button new-project) render their own `InterstitialLayout` inline; the old `VercelIntegrationWindowLayout` was removed.
|
||||
|
||||
### Project shell
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import { useFlag } from 'common'
|
||||
import { UseFormReturn } from 'react-hook-form'
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
CardContent,
|
||||
cn,
|
||||
FormControl,
|
||||
FormField,
|
||||
@@ -20,6 +18,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { CreateProjectForm } from './ProjectCreation.schema'
|
||||
import { DocsButton } from '@/components/ui/DocsButton'
|
||||
import Panel from '@/components/ui/Panel'
|
||||
import { DOCS_URL } from '@/lib/constants'
|
||||
|
||||
interface AdvancedConfigurationProps {
|
||||
@@ -30,93 +29,91 @@ export const AdvancedConfiguration = ({ form }: AdvancedConfigurationProps) => {
|
||||
const disableOrioleProjectCreation = useFlag('disableOrioleProjectCreation')
|
||||
|
||||
return (
|
||||
<Card className="border-0 border-b rounded-none">
|
||||
<CardContent>
|
||||
<CollapsibleCardSection
|
||||
title="Advanced Configuration"
|
||||
description="These settings cannot be changed after the project is created"
|
||||
>
|
||||
<FormField
|
||||
name="useOrioleDb"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
label="Postgres Type"
|
||||
className="[&>div>label]:break-normal!"
|
||||
>
|
||||
<FormControl>
|
||||
<RadioGroupStacked
|
||||
// Due to radio group not supporting boolean values
|
||||
// value is converted to boolean
|
||||
onValueChange={(value) => field.onChange(value === 'true')}
|
||||
defaultValue={field.value.toString()}
|
||||
>
|
||||
<FormItem asChild>
|
||||
<FormControl>
|
||||
<RadioGroupStackedItem
|
||||
value="false"
|
||||
// @ts-ignore
|
||||
label={
|
||||
<>
|
||||
Postgres
|
||||
<Badge>Default</Badge>
|
||||
</>
|
||||
}
|
||||
description="Recommended for production workloads"
|
||||
className="[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem asChild>
|
||||
<FormControl>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RadioGroupStackedItem
|
||||
value="true"
|
||||
// @ts-ignore
|
||||
label={
|
||||
<>
|
||||
Postgres with OrioleDB
|
||||
<Badge variant="warning">Alpha</Badge>
|
||||
</>
|
||||
}
|
||||
description="Not recommended for production workloads"
|
||||
className={cn(
|
||||
'[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2',
|
||||
form.getValues('useOrioleDb') ? 'rounded-b-none!' : ''
|
||||
)}
|
||||
disabled={disableOrioleProjectCreation}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
{disableOrioleProjectCreation && (
|
||||
<TooltipContent side="right" className="w-60 text-center">
|
||||
OrioleDB is temporarily disabled for new projects. Please try again
|
||||
later.
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</RadioGroupStacked>
|
||||
</FormControl>
|
||||
{form.getValues('useOrioleDb') && (
|
||||
<Admonition
|
||||
type="warning"
|
||||
className="rounded-t-none [&>div]:text-xs"
|
||||
title="OrioleDB is not production ready"
|
||||
description="Postgres with OrioleDB extension is currently in Public Alpha and not recommended for production usage yet."
|
||||
>
|
||||
<DocsButton className="mt-2" href={`${DOCS_URL}/guides/database/orioledb`} />
|
||||
</Admonition>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</CollapsibleCardSection>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Panel.Content>
|
||||
<CollapsibleCardSection
|
||||
title="Advanced Configuration"
|
||||
description="These settings cannot be changed after the project is created"
|
||||
>
|
||||
<FormField
|
||||
name="useOrioleDb"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
label="Postgres Type"
|
||||
className="[&>div>label]:break-normal!"
|
||||
>
|
||||
<FormControl>
|
||||
<RadioGroupStacked
|
||||
// Due to radio group not supporting boolean values
|
||||
// value is converted to boolean
|
||||
onValueChange={(value) => field.onChange(value === 'true')}
|
||||
defaultValue={field.value.toString()}
|
||||
>
|
||||
<FormItem asChild>
|
||||
<FormControl>
|
||||
<RadioGroupStackedItem
|
||||
value="false"
|
||||
// @ts-ignore
|
||||
label={
|
||||
<>
|
||||
Postgres
|
||||
<Badge>Default</Badge>
|
||||
</>
|
||||
}
|
||||
description="Recommended for production workloads"
|
||||
className="[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem asChild>
|
||||
<FormControl>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RadioGroupStackedItem
|
||||
value="true"
|
||||
// @ts-ignore
|
||||
label={
|
||||
<>
|
||||
Postgres with OrioleDB
|
||||
<Badge variant="warning">Alpha</Badge>
|
||||
</>
|
||||
}
|
||||
description="Not recommended for production workloads"
|
||||
className={cn(
|
||||
'[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2',
|
||||
form.getValues('useOrioleDb') ? 'rounded-b-none!' : ''
|
||||
)}
|
||||
disabled={disableOrioleProjectCreation}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
{disableOrioleProjectCreation && (
|
||||
<TooltipContent side="right" className="w-60 text-center">
|
||||
OrioleDB is temporarily disabled for new projects. Please try again
|
||||
later.
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</RadioGroupStacked>
|
||||
</FormControl>
|
||||
{form.getValues('useOrioleDb') && (
|
||||
<Admonition
|
||||
type="warning"
|
||||
className="rounded-t-none [&>div]:text-xs"
|
||||
title="OrioleDB is not production ready"
|
||||
description="Postgres with OrioleDB extension is currently in Public Alpha and not recommended for production usage yet."
|
||||
>
|
||||
<DocsButton className="mt-2" href={`${DOCS_URL}/guides/database/orioledb`} />
|
||||
</Admonition>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</CollapsibleCardSection>
|
||||
</Panel.Content>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useParams } from 'common'
|
||||
import { UseFormReturn } from 'react-hook-form'
|
||||
import { type CloudProvider } from 'shared-data'
|
||||
import { Card, CardContent, FormControl, FormField, Input } from 'ui'
|
||||
import { FormControl, FormField, Input } from 'ui'
|
||||
import { CollapsibleCardSection } from 'ui-patterns/CollapsibleCardSection'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CloudProviderSelector } from './CloudProviderSelector'
|
||||
import { HighAvailabilityInput } from './HighAvailabilityInput'
|
||||
import { PostgresVersionSelector } from './PostgresVersionSelector'
|
||||
import { CreateProjectForm } from './ProjectCreation.schema'
|
||||
import Panel from '@/components/ui/Panel'
|
||||
|
||||
interface InternalOnlyConfigurationProps {
|
||||
form: UseFormReturn<CreateProjectForm>
|
||||
@@ -19,76 +20,74 @@ export const InternalOnlyConfiguration = ({ form }: InternalOnlyConfigurationPro
|
||||
const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
|
||||
|
||||
return (
|
||||
<Card className="border-0 border-b rounded-none">
|
||||
<CardContent>
|
||||
<CollapsibleCardSection
|
||||
title="Internal-only Configuration"
|
||||
description="These settings are only visible to internal staff"
|
||||
>
|
||||
<div className="flex flex-col gap-y-6">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="postgresVersionSelection"
|
||||
render={({ field }) => (
|
||||
<PostgresVersionSelector
|
||||
field={field}
|
||||
form={form}
|
||||
cloudProvider={form.getValues('cloudProvider') as CloudProvider}
|
||||
organizationSlug={slug}
|
||||
dbRegion={form.getValues('dbRegion')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Panel.Content>
|
||||
<CollapsibleCardSection
|
||||
title="Internal-only Configuration"
|
||||
description="These settings are only visible to internal staff"
|
||||
>
|
||||
<div className="flex flex-col gap-y-6">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="postgresVersionSelection"
|
||||
render={({ field }) => (
|
||||
<PostgresVersionSelector
|
||||
field={field}
|
||||
form={form}
|
||||
cloudProvider={form.getValues('cloudProvider') as CloudProvider}
|
||||
organizationSlug={slug}
|
||||
dbRegion={form.getValues('dbRegion')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<HighAvailabilityInput form={form} />
|
||||
</div>
|
||||
|
||||
{showNonProdFields && (
|
||||
<div>
|
||||
<p className="text-xs text-foreground-lighter mb-6">
|
||||
The settings below are only applicable for local/staging projects
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<CloudProviderSelector form={form} />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="postgresVersion"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Custom Postgres version"
|
||||
layout="horizontal"
|
||||
description="Specify a custom version of Postgres (defaults to the latest)."
|
||||
>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g 17.6.1.104" {...field} autoComplete="off" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="instanceType"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Custom instance type"
|
||||
layout="horizontal"
|
||||
description="Specify a custom instance type."
|
||||
>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g t3.nano" {...field} autoComplete="off" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<HighAvailabilityInput form={form} />
|
||||
</div>
|
||||
</CollapsibleCardSection>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{showNonProdFields && (
|
||||
<div>
|
||||
<p className="text-xs text-foreground-lighter mb-6">
|
||||
The settings below are only applicable for local/staging projects
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<CloudProviderSelector form={form} />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="postgresVersion"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Custom Postgres version"
|
||||
layout="horizontal"
|
||||
description="Specify a custom version of Postgres (defaults to the latest)."
|
||||
>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g 17.6.1.104" {...field} autoComplete="off" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="instanceType"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Custom instance type"
|
||||
layout="horizontal"
|
||||
description="Specify a custom instance type."
|
||||
>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g t3.nano" {...field} autoComplete="off" />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleCardSection>
|
||||
</Panel.Content>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useForm, useFormState } from 'react-hook-form'
|
||||
import { type CloudProvider } from 'shared-data'
|
||||
import { toast } from 'sonner'
|
||||
import { Button, Form, useWatch } from 'ui'
|
||||
import { Button, cn, Form, useWatch } from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { z } from 'zod'
|
||||
@@ -92,6 +92,8 @@ interface ProjectCreationFormProps {
|
||||
* - "Cancel" button
|
||||
* - Shows the following:
|
||||
* - "Data seeding" section
|
||||
* - When embedded in the Vercel interstitial, flattens Panel chrome so the shared
|
||||
* form fields sit inside InterstitialLayout without a nested card
|
||||
* 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
|
||||
*/
|
||||
@@ -581,14 +583,20 @@ export const ProjectCreationForm = ({
|
||||
>
|
||||
<Panel
|
||||
loading={!isOrganizationsSuccess}
|
||||
noMargin={isVercelIntegrationFlow}
|
||||
className={cn(
|
||||
!isVercelIntegrationFlow && 'border-0 shadow-none rounded-none bg-transparent'
|
||||
)}
|
||||
title={
|
||||
<div key="panel-title">
|
||||
<h3>Create a new project</h3>
|
||||
<p className="text-sm text-foreground-lighter text-balance">
|
||||
Your project will have its own dedicated instance and full Postgres database. An API
|
||||
will be set up so you can easily interact with your new database.
|
||||
</p>
|
||||
</div>
|
||||
!isVercelIntegrationFlow && (
|
||||
<div key="panel-title">
|
||||
<h3>Create a new project</h3>
|
||||
<p className="text-sm text-foreground-lighter text-balance">
|
||||
Your project will have its own dedicated instance and full Postgres database. An
|
||||
API will be set up so you can easily interact with your new database.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
footer={
|
||||
<ProjectCreationFooter
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { Book, LifeBuoy, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { forwardRef, PropsWithChildren, ReactNode } from 'react'
|
||||
import { cn, LoadingLine } from 'ui'
|
||||
|
||||
import { ScaffoldContainer } from '../Scaffold'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
import { BASE_PATH } from '@/lib/constants'
|
||||
|
||||
export type IntegrationWindowLayoutProps = {
|
||||
title: string
|
||||
integrationIcon: ReactNode
|
||||
loading?: boolean
|
||||
docsHref?: string
|
||||
}
|
||||
|
||||
const IntegrationWindowLayout = ({
|
||||
title,
|
||||
integrationIcon,
|
||||
children,
|
||||
loading = false,
|
||||
docsHref,
|
||||
}: PropsWithChildren<IntegrationWindowLayoutProps>) => {
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Header title={title} integrationIcon={integrationIcon} />
|
||||
<LoadingLine loading={loading} />
|
||||
<main className="overflow-auto flex flex-col h-full bg">{children}</main>
|
||||
<ScaffoldContainer className="bg-studio flex flex-row gap-6 py-6 border-t">
|
||||
{docsHref && (
|
||||
<Link
|
||||
href={docsHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-xs text-foreground-light hover:text"
|
||||
>
|
||||
<Book size={16} />
|
||||
Docs
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href={'https://supabase.com/support'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-xs text-light hover:text"
|
||||
>
|
||||
<LifeBuoy size={16} />
|
||||
Support
|
||||
</Link>
|
||||
</ScaffoldContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const INTEGRATION_LAYOUT_MAX_WIDTH = '' // 'max-w-[720px]'
|
||||
|
||||
export default withAuth(IntegrationWindowLayout)
|
||||
|
||||
export const IntegrationWindowLayoutWithoutAuth = IntegrationWindowLayout
|
||||
|
||||
export type HeaderProps = {
|
||||
title: string
|
||||
integrationIcon: ReactNode
|
||||
}
|
||||
|
||||
const Header = ({ title, integrationIcon }: HeaderProps) => {
|
||||
return (
|
||||
<div className="bg">
|
||||
<ScaffoldContainer className={cn('py-3', INTEGRATION_LAYOUT_MAX_WIDTH)}>
|
||||
<div className="flex items-center gap-6 w-full">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="bg-white shadow-sm border rounded-sm p-1 w-8 h-8 flex justify-center items-center">
|
||||
<img src={`${BASE_PATH}/img/supabase-logo.svg`} alt="Supabase" className="w-4" />
|
||||
</div>
|
||||
<X className="text-border-stronger" strokeWidth={2} size={16} />
|
||||
{integrationIcon}
|
||||
</div>
|
||||
<span className="text-sm" title={title}>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</ScaffoldContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const maxWidthClasses = 'mx-auto w-full max-w-[1600px]'
|
||||
const paddingClasses = 'px-6 lg:px-14 xl:px-28 2xl:px-32'
|
||||
|
||||
export const IntegrationScaffoldContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
return <div ref={ref} {...props} className={cn(maxWidthClasses, paddingClasses, className)} />
|
||||
})
|
||||
|
||||
IntegrationScaffoldContainer.displayName = 'IntegrationScaffoldContainer'
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useParams } from 'common'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import InlineSVG from 'react-inlinesvg'
|
||||
|
||||
import IntegrationWindowLayout from './IntegrationWindowLayout'
|
||||
import { BASE_PATH } from '@/lib/constants'
|
||||
import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
|
||||
|
||||
const VERCEL_ICON = (
|
||||
<div className="bg-black shadow-sm rounded-sm p-1 w-8 h-8 flex justify-center items-center text-white">
|
||||
<InlineSVG src={`${BASE_PATH}/img/icons/vercel-icon.svg`} title="Vercel Icon" className="w-4" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const VercelIntegrationWindowLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
const { externalId } = useParams()
|
||||
|
||||
const snapshot = useIntegrationInstallationSnapshot()
|
||||
|
||||
const title = externalId
|
||||
? 'Supabase + Vercel Deploy Button'
|
||||
: 'Supabase + Vercel Integration Marketplace Connector'
|
||||
|
||||
return (
|
||||
<IntegrationWindowLayout
|
||||
title={title}
|
||||
integrationIcon={VERCEL_ICON}
|
||||
loading={snapshot.loading}
|
||||
docsHref="https://supabase.com/partners/integrations/vercel"
|
||||
>
|
||||
{children}
|
||||
</IntegrationWindowLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default VercelIntegrationWindowLayout
|
||||
@@ -16,6 +16,8 @@ interface InterstitialLayoutProps {
|
||||
footer?: ReactNode
|
||||
containerClassName?: string
|
||||
cardClassName?: string
|
||||
/** Shared max-width for the card and footer column. Defaults to `max-w-[400px]`. */
|
||||
widthClassName?: string
|
||||
titleClassName?: string
|
||||
descriptionClassName?: string
|
||||
}
|
||||
@@ -34,6 +36,7 @@ export const InterstitialLayout = ({
|
||||
footer,
|
||||
containerClassName,
|
||||
cardClassName,
|
||||
widthClassName = 'max-w-[400px]',
|
||||
titleClassName,
|
||||
descriptionClassName,
|
||||
children,
|
||||
@@ -67,7 +70,7 @@ export const InterstitialLayout = ({
|
||||
<MotionCard
|
||||
layout="size"
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={cn('overflow-hidden max-w-[400px] w-full mx-auto', cardClassName)}
|
||||
className={cn('overflow-hidden w-full mx-auto', widthClassName, cardClassName)}
|
||||
>
|
||||
{(logo || title || description) && (
|
||||
<CardHeader className="font-normal items-center gap-0 space-y-0 px-6 py-6 text-center [--card-padding-x:1.5rem] border-0">
|
||||
@@ -92,7 +95,7 @@ export const InterstitialLayout = ({
|
||||
)}
|
||||
>
|
||||
{footer ? (
|
||||
<div className="flex w-full max-w-[400px] flex-col items-center gap-4">
|
||||
<div className={cn('flex w-full flex-col items-center gap-4', widthClassName)}>
|
||||
{card}
|
||||
<div className="px-2 text-center text-balance">{footer}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import { useParams } from 'common'
|
||||
import Head from 'next/head'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
|
||||
import { isVercelUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
|
||||
import {
|
||||
VercelIntegrationFooter,
|
||||
VercelIntegrationLogo,
|
||||
} from '@/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial'
|
||||
import { ProjectCreationForm } from '@/components/interfaces/ProjectCreation/ProjectCreationForm'
|
||||
import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
|
||||
import { ScaffoldColumn, ScaffoldContainer } from '@/components/layouts/Scaffold'
|
||||
import { InterstitialLayout } from '@/components/layouts/InterstitialLayout'
|
||||
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
|
||||
import { useIntegrationsQuery } from '@/data/integrations/integrations-query'
|
||||
import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integrations/integrations-vercel-connections-create-mutation'
|
||||
import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
|
||||
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
import { buildStudioPageTitle } from '@/lib/page-title'
|
||||
import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
const PAGE_TITLE = buildStudioPageTitle({
|
||||
section: 'Create Vercel Project',
|
||||
brand: 'Supabase',
|
||||
})
|
||||
|
||||
const VercelIntegration: NextPageWithLayout = () => {
|
||||
const { slug, next, currentProjectId: foreignProjectId } = useParams()
|
||||
const snapshot = useIntegrationInstallationSnapshot()
|
||||
@@ -91,23 +101,22 @@ const VercelIntegration: NextPageWithLayout = () => {
|
||||
}, [data, isSuccess])
|
||||
|
||||
return (
|
||||
<ScaffoldContainer className="flex flex-col gap-6 grow py-8">
|
||||
<ScaffoldColumn className="mx-auto w-full max-w-2xl">
|
||||
<Admonition
|
||||
type="default"
|
||||
layout="horizontal"
|
||||
title="You can uninstall this Integration at any time."
|
||||
description="You can remove this integration at any time via Vercel or the Supabase dashboard"
|
||||
/>
|
||||
<>
|
||||
<Head>
|
||||
<title>{PAGE_TITLE}</title>
|
||||
</Head>
|
||||
|
||||
<InterstitialLayout
|
||||
logo={<VercelIntegrationLogo />}
|
||||
title="Create a new project"
|
||||
description="Your project will have its own dedicated instance and full Postgres database. An API will be set up so you can easily interact with your new database."
|
||||
footer={<VercelIntegrationFooter />}
|
||||
widthClassName="max-w-2xl"
|
||||
>
|
||||
<ProjectCreationForm isVercelIntegrationFlow onCreateSuccess={setNewProjectRef} />
|
||||
</ScaffoldColumn>
|
||||
</ScaffoldContainer>
|
||||
</InterstitialLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
VercelIntegration.getLayout = (page) => (
|
||||
<VercelIntegrationWindowLayout>{page}</VercelIntegrationWindowLayout>
|
||||
)
|
||||
|
||||
export default VercelIntegration
|
||||
export default withAuth(VercelIntegration)
|
||||
@@ -4,11 +4,9 @@ export const Route = createFileRoute('/integrations/vercel')({
|
||||
component: VercelIntegrationPassthrough,
|
||||
})
|
||||
|
||||
// No shared layout here. Since #47623, the install and
|
||||
// marketplace/choose-project pages render their own InterstitialLayout and
|
||||
// have no Next getLayout, so the Next runtime shows them without any window
|
||||
// chrome. Only deploy-button/new-project still uses
|
||||
// VercelIntegrationWindowLayout, and its route wraps it at the leaf.
|
||||
// No shared layout here. Since #47623, the Vercel install, marketplace
|
||||
// choose-project, and deploy-button/new-project pages each render their own
|
||||
// InterstitialLayout and have no Next getLayout / window chrome.
|
||||
function VercelIntegrationPassthrough() {
|
||||
return <Outlet />
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
|
||||
import VercelIntegration from '@/pages/integrations/vercel/[slug]/deploy-button/new-project'
|
||||
|
||||
export const Route = createFileRoute('/integrations/vercel/$slug/deploy-button/new-project')({
|
||||
component: VercelDeployButtonNewProjectRoute,
|
||||
})
|
||||
|
||||
// Mirrors the page's Next getLayout, which wraps this leaf (and only this
|
||||
// leaf) in VercelIntegrationWindowLayout.
|
||||
function VercelDeployButtonNewProjectRoute() {
|
||||
return (
|
||||
<VercelIntegrationWindowLayout>
|
||||
<VercelIntegration dehydratedState={undefined} />
|
||||
</VercelIntegrationWindowLayout>
|
||||
)
|
||||
return <VercelIntegration dehydratedState={undefined} />
|
||||
}
|
||||
@@ -25,7 +25,8 @@ export const CollapsibleCardSection = ({
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
'[overflow-y:clip] pt-2 data-closed:animate-collapsible-up data-open:animate-collapsible-down'
|
||||
// pb-px keeps child borders/shadows from being clipped by overflow-y: clip
|
||||
'[overflow-y:clip] pt-2 pb-px data-closed:animate-collapsible-up data-open:animate-collapsible-down'
|
||||
)}
|
||||
>
|
||||
{description && <p className="text-xs text-foreground-lighter mb-6">{description}</p>}
|
||||
|
||||
Reference in new issue
Block a user