diff --git a/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx b/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx index 9b285db81ec..7ff99d7d448 100644 --- a/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx +++ b/apps/webapp/app/components/integrations/VercelOnboardingModal.tsx @@ -146,6 +146,11 @@ export function VercelOnboardingModal({ } return "project-selection"; } + // If onboarding was already completed but GitHub is not connected, + // go directly to the github-connection step (e.g., returning from GitHub App installation) + if (onboardingData?.isOnboardingComplete && !onboardingData?.isGitHubConnected) { + return "github-connection"; + } // For marketplace origin, skip env-mapping step and go directly to env-var-sync if (!fromMarketplaceContext) { const customEnvs = (onboardingData?.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment; @@ -1159,26 +1164,7 @@ export function VercelOnboardingModal({ > Complete - ) : ( - { - trackOnboarding("vercel onboarding github skipped"); - setState("completed"); - if (fromMarketplaceContext && nextUrl) { - const validUrl = safeRedirectUrl(nextUrl); - if (validUrl) { - window.location.href = validUrl; - } - } - }} - > - Skip for now - - ) - } - cancelButton={ - isGitHubConnectedForOnboarding && fromMarketplaceContext && nextUrl ? ( + ) : !fromMarketplaceContext ? ( { diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index 592ecd3f7c0..72901fa5ddb 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -80,6 +80,7 @@ export async function loader({ request }: LoaderFunctionArgs) { showGoogleAuth: isGoogleAuthSupported, lastAuthMethod, authError: null, + isVercelMarketplace: redirectTo.startsWith("/vercel/callback"), }, { headers: { @@ -106,6 +107,7 @@ export async function loader({ request }: LoaderFunctionArgs) { showGoogleAuth: isGoogleAuthSupported, lastAuthMethod, authError, + isVercelMarketplace: false, }); } } @@ -164,19 +166,21 @@ export default function LoginPage() { )} - - {data.lastAuthMethod === "email" && } - - - Continue with Email - - + {!data.isVercelMarketplace && ( + + {data.lastAuthMethod === "email" && } + + + Continue with Email + + + )} {data.authError && {data.authError}} diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx index c806381f1aa..15062a718bc 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx @@ -41,6 +41,7 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; import { ProjectSettingsService } from "~/services/projectSettings.server"; import { logger } from "~/services/logger.server"; import { triggerInitialDeployment } from "~/services/platform.v3.server"; +import { VercelIntegrationService } from "~/services/vercelIntegration.server"; import { requireUserId } from "~/services/session.server"; import { githubAppInstallPath, @@ -210,16 +211,22 @@ export async function action({ request, params }: ActionFunctionArgs) { ); if (resultOrFail.isOk()) { - // Trigger initial deployment for marketplace flows now that GitHub is connected - if (redirectUrl) { - try { - if (redirectUrl.includes("origin=marketplace")) { - await triggerInitialDeployment(projectId, { environment: "prod" }); - } - } catch (error) { - logger.error("Invalid redirect URL, skipping initial deployment trigger", { redirectUrl, error }); - // Invalid redirectUrl, skip initial deployment check + // Trigger initial deployment for marketplace flows now that GitHub is connected. + // We check the persisted onboardingOrigin on the Vercel integration rather than + // the redirectUrl, because the redirect URL loses the marketplace context when + // the user installs the GitHub App for the first time (full-page redirect cycle). + try { + const vercelService = new VercelIntegrationService(); + const vercelIntegration = await vercelService.getVercelProjectIntegration(projectId); + if ( + vercelIntegration?.parsedIntegrationData.onboardingCompleted && + vercelIntegration.parsedIntegrationData.onboardingOrigin === "marketplace" + ) { + logger.info("Marketplace flow detected, triggering initial deployment", { projectId }); + await triggerInitialDeployment(projectId, { environment: "prod" }); } + } catch (error) { + logger.error("Failed to check Vercel integration or trigger initial deployment", { projectId, error }); } return redirectWithMessage( diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx index 9f5be039e44..d1c4715a1c6 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx @@ -293,6 +293,7 @@ export async function action({ request, params }: ActionFunctionArgs) { syncEnvVarsMapping, next, skipRedirect, + origin, } = submission.value; const parsedStagingEnv = parseVercelStagingEnvironment(vercelStagingEnvironment); @@ -306,6 +307,7 @@ export async function action({ request, params }: ActionFunctionArgs) { atomicBuilds, discoverEnvVars, syncEnvVarsMapping: parsedSyncEnvVarsMapping, + origin: origin === "marketplace" ? "marketplace" : "dashboard", }); if (result) { diff --git a/apps/webapp/app/services/vercelIntegration.server.ts b/apps/webapp/app/services/vercelIntegration.server.ts index 5e9155a6b25..cf9e634afbb 100644 --- a/apps/webapp/app/services/vercelIntegration.server.ts +++ b/apps/webapp/app/services/vercelIntegration.server.ts @@ -526,6 +526,7 @@ export class VercelIntegrationService { atomicBuilds?: EnvSlug[] | null; discoverEnvVars?: EnvSlug[] | null; syncEnvVarsMapping?: SyncEnvVarsMapping; + origin?: "marketplace" | "dashboard"; } ): Promise { const existing = await this.getVercelProjectIntegration(projectId); @@ -544,8 +545,9 @@ export class VercelIntegrationService { vercelStagingEnvironment: params.vercelStagingEnvironment ?? null, }, //This is intentionally not updated here, in case of resetting the onboarding it should not override the existing mapping with an empty one - syncEnvVarsMapping: existing.parsedIntegrationData.syncEnvVarsMapping, + syncEnvVarsMapping: existing.parsedIntegrationData.syncEnvVarsMapping, onboardingCompleted: true, + onboardingOrigin: params.origin ?? existing.parsedIntegrationData.onboardingOrigin, }; const updated = await this.#prismaClient.organizationProjectIntegration.update({ diff --git a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts index fd23f10c995..9e39febe0ab 100644 --- a/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts +++ b/apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts @@ -72,6 +72,7 @@ export const VercelProjectIntegrationDataSchema = z.object({ vercelTeamSlug: z.string().optional(), vercelProjectId: z.string(), onboardingCompleted: z.boolean().optional(), + onboardingOrigin: z.enum(["marketplace", "dashboard"]).optional(), }); export type VercelProjectIntegrationData = z.infer;