diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx index 8ea17f52cf8..2a491957170 100644 --- a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx +++ b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx @@ -117,7 +117,8 @@ export const ProjectLinker = ({ ) : showNoEntitiesState && (noSupabaseProjects || noForeignProjects) ? (
No {missingEntity} projects found. Create a {missingEntity} project to link to a{' '} - {oppositeMissingEntity} project, or skip and connect later. + {oppositeMissingEntity} project + {onSkip !== undefined ? ', or skip and connect later.' : '.'}
) : ( <> @@ -192,8 +193,12 @@ export const ProjectLinker = ({

You will need to create a {missingEntity} Project to link to a {oppositeMissingEntity}{' '} Project. -
- You can skip this and create a Project Connection later. + {onSkip !== undefined && ( + <> +
+ You can skip this and create a Project Connection later. + + )}

) : ( diff --git a/apps/studio/lib/integrations/vercel-install.utils.ts b/apps/studio/lib/integrations/vercel-install.utils.ts index 837ffda50dc..8eaac03c080 100644 --- a/apps/studio/lib/integrations/vercel-install.utils.ts +++ b/apps/studio/lib/integrations/vercel-install.utils.ts @@ -13,9 +13,33 @@ export function getErrorMessage(error: unknown): string | undefined { export type VercelInstallSource = 'deploy-button' | 'marketplace' | 'external' -export function getVercelInstallSource( +/** + * Vercel sometimes sends source=marketplace with Deploy Button params + * (currentProjectId + external-id). Treat that as deploy-button for routing. + */ +export function hasVercelDeployButtonSignals({ + currentProjectId, + externalId, +}: { + currentProjectId?: string + externalId?: string +}): boolean { + return Boolean(currentProjectId && externalId) +} + +export function resolveVercelInstallSource({ + source, + currentProjectId, + externalId, +}: { source: string | undefined -): VercelInstallSource | undefined { + currentProjectId?: string + externalId?: string +}): VercelInstallSource | undefined { + if (hasVercelDeployButtonSignals({ currentProjectId, externalId })) { + return 'deploy-button' + } + switch (source) { case 'deploy-button': case 'marketplace': diff --git a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx index be6674e039f..7febfc533af 100644 --- a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx +++ b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx @@ -28,6 +28,7 @@ import { useOrganizationsQuery } from '@/data/organizations/organizations-query' import { withAuth } from '@/hooks/misc/withAuth' import { BASE_PATH } from '@/lib/constants' import { getErrorMessage } from '@/lib/get-error-message' +import { hasVercelDeployButtonSignals } from '@/lib/integrations/vercel-install.utils' import { buildStudioPageTitle } from '@/lib/page-title' import { useProfileNameAndPicture } from '@/lib/profile' import { EMPTY_ARR } from '@/lib/void' @@ -40,9 +41,10 @@ const PAGE_TITLE = buildStudioPageTitle({ }) const VercelChooseProjectPage: NextPageWithLayout = () => { - const { slug, configurationId, next, currentProjectId } = useParams() + const { slug, configurationId, next, currentProjectId, externalId } = useParams() const { username, primaryEmail, avatarUrl } = useProfileNameAndPicture() const displayName = primaryEmail ?? username ?? '' + const isDeployButtonFlow = hasVercelDeployButtonSignals({ currentProjectId, externalId }) const { data: integrationData, @@ -199,11 +201,15 @@ const VercelChooseProjectPage: NextPageWithLayout = () => { getForeignProjectIcon={getForeignProjectIcon} choosePrompt="Choose Vercel project" defaultForeignProjectId={currentProjectId} - onSkip={() => { - if (next && isVercelUrl(next)) { - window.location.href = next - } - }} + onSkip={ + isDeployButtonFlow + ? undefined + : () => { + if (next && isVercelUrl(next)) { + window.location.href = next + } + } + } loadingForeignProjects={isLoadingVercelProjectsData} mode="Vercel" /> diff --git a/apps/studio/pages/integrations/vercel/install.tsx b/apps/studio/pages/integrations/vercel/install.tsx index 9defd01e649..abc1c711021 100644 --- a/apps/studio/pages/integrations/vercel/install.tsx +++ b/apps/studio/pages/integrations/vercel/install.tsx @@ -32,7 +32,7 @@ import { withAuth } from '@/hooks/misc/withAuth' import { buildVercelInstallRouteQuery, getErrorMessage, - getVercelInstallSource, + resolveVercelInstallSource, } from '@/lib/integrations/vercel-install.utils' import { buildStudioPageTitle } from '@/lib/page-title' import { useProfileNameAndPicture } from '@/lib/profile' @@ -115,7 +115,11 @@ const VercelIntegration: NextPageWithLayout = () => { */ function handleRouteChange() { const orgSlug = selectedOrg?.slug - const vercelInstallSource = getVercelInstallSource(source) + const vercelInstallSource = resolveVercelInstallSource({ + source, + currentProjectId, + externalId, + }) const query = buildVercelInstallRouteQuery({ source: vercelInstallSource, organizationSlug: orgSlug, diff --git a/apps/studio/tests/pages/integrations/vercel/install.utils.test.ts b/apps/studio/tests/pages/integrations/vercel/install.utils.test.ts index b8109105f60..7d96716d14d 100644 --- a/apps/studio/tests/pages/integrations/vercel/install.utils.test.ts +++ b/apps/studio/tests/pages/integrations/vercel/install.utils.test.ts @@ -3,7 +3,8 @@ import { describe, expect, test } from 'vitest' import { buildVercelInstallRouteQuery, getErrorMessage, - getVercelInstallSource, + hasVercelDeployButtonSignals, + resolveVercelInstallSource, } from '@/lib/integrations/vercel-install.utils' describe('getErrorMessage', () => { @@ -89,15 +90,46 @@ describe('buildVercelInstallRouteQuery', () => { }) }) -describe('getVercelInstallSource', () => { - test('returns supported Vercel install sources', () => { - expect(getVercelInstallSource('deploy-button')).toBe('deploy-button') - expect(getVercelInstallSource('marketplace')).toBe('marketplace') - expect(getVercelInstallSource('external')).toBe('external') +describe('hasVercelDeployButtonSignals', () => { + test('requires both currentProjectId and externalId', () => { + expect( + hasVercelDeployButtonSignals({ + currentProjectId: 'prj_123', + externalId: 'https://github.com/org/repo', + }) + ).toBe(true) + expect(hasVercelDeployButtonSignals({ currentProjectId: 'prj_123' })).toBe(false) + expect(hasVercelDeployButtonSignals({ externalId: 'https://github.com/org/repo' })).toBe(false) + expect(hasVercelDeployButtonSignals({})).toBe(false) }) +}) - test('returns undefined for unsupported sources', () => { - expect(getVercelInstallSource('deploybutton')).toBeUndefined() - expect(getVercelInstallSource(undefined)).toBeUndefined() +describe('resolveVercelInstallSource', () => { + test('overrides marketplace and external when deploy-button signals are present', () => { + expect( + resolveVercelInstallSource({ + source: 'marketplace', + currentProjectId: 'prj_123', + externalId: 'https://github.com/org/repo', + }) + ).toBe('deploy-button') + expect( + resolveVercelInstallSource({ + source: 'external', + currentProjectId: 'prj_123', + externalId: 'https://github.com/org/repo', + }) + ).toBe('deploy-button') + }) + + test('keeps the declared source when deploy-button signals are incomplete', () => { + expect( + resolveVercelInstallSource({ + source: 'marketplace', + currentProjectId: 'prj_123', + }) + ).toBe('marketplace') + expect(resolveVercelInstallSource({ source: 'deploy-button' })).toBe('deploy-button') + expect(resolveVercelInstallSource({ source: undefined })).toBeUndefined() }) })