- {!hideSectionToggles && availableEnvSlugs.length > 1 && (
+ {availableEnvSlugs.length > 1 && (
- {!hideSectionToggles && availableEnvSlugs.length > 1 && (
+ {availableEnvSlugs.length > 1 && (
void;
vercelManageAccessUrl?: string;
}) {
const { capture, startSessionRecording } = usePostHogTracking();
- const navigation = useNavigation();
const fetcher = useTypedFetcher();
const envMappingFetcher = useFetcher();
const completeOnboardingFetcher = useFetcher();
- const { Form: _CompleteOnboardingForm } = completeOnboardingFetcher;
const [searchParams] = useSearchParams();
const origin = searchParams.get("origin");
const fromMarketplaceContext = origin === "marketplace";
@@ -130,7 +128,6 @@ export function VercelOnboardingModal({
() => onboardingData?.availableProjects ?? [],
[onboardingData?.availableProjects]
);
- const _hasProjectSelected = onboardingData?.hasProjectSelected ?? false;
const customEnvironments = useMemo(
() => onboardingData?.customEnvironments ?? [],
[onboardingData?.customEnvironments]
@@ -224,10 +221,6 @@ export function VercelOnboardingModal({
environmentId: string;
displayName: string;
} | null>(null);
- const _availableEnvSlugsForOnboarding = getAvailableEnvSlugs(
- hasStagingEnvironment,
- hasPreviewEnvironment
- );
const availableEnvSlugsForOnboardingBuildSettings = getAvailableEnvSlugsForBuildSettings(
hasStagingEnvironment,
hasPreviewEnvironment
@@ -375,7 +368,6 @@ export function VercelOnboardingModal({
}
break;
- case "installing":
case "project-selection":
case "env-mapping":
case "env-var-sync":
@@ -459,8 +451,6 @@ export function VercelOnboardingModal({
const overlappingEnvVarsCount = enabledEnvVars.filter((v) => existingVars[v.key]).length;
- const _isSubmitting = navigation.state === "submitting" || navigation.state === "loading";
-
const actionUrl = vercelResourcePath(organizationSlug, projectSlug, environmentSlug);
const handleToggleEnvVar = useCallback((key: string, enabled: boolean) => {
@@ -634,19 +624,6 @@ export function VercelOnboardingModal({
gitHubAppInstallations.length,
]);
- const _handleFinishOnboarding = useCallback(
- (e: React.FormEvent) => {
- e.preventDefault();
- const form = e.currentTarget;
- const formData = new FormData(form);
- completeOnboardingFetcher.submit(formData, {
- method: "post",
- action: actionUrl,
- });
- },
- [completeOnboardingFetcher, actionUrl]
- );
-
useEffect(() => {
if (
completeOnboardingFetcher.data &&
@@ -698,13 +675,6 @@ export function VercelOnboardingModal({
}
}, [state, onClose, trackOnboarding, isGitHubConnectedForOnboarding]);
- useEffect(() => {
- if (state === "installing") {
- const installUrl = vercelAppInstallPath(organizationSlug, projectSlug);
- window.location.href = installUrl;
- }
- }, [state, organizationSlug, projectSlug]);
-
useEffect(() => {
if (
envMappingFetcher.data &&
@@ -749,7 +719,6 @@ export function VercelOnboardingModal({
state === "loading-projects" ||
state === "loading-env-mapping" ||
state === "loading-env-vars" ||
- state === "installing" ||
(state === "idle" && !onboardingData);
if (isLoadingState) {
@@ -758,9 +727,7 @@ export function VercelOnboardingModal({
open={isOpen}
onOpenChange={(open) => {
if (!open && !fromMarketplaceContext) {
- if ((state as string) !== "completed") {
- trackOnboarding("vercel onboarding abandoned");
- }
+ trackOnboarding("vercel onboarding abandoned");
onClose();
}
}}
@@ -772,9 +739,30 @@ export function VercelOnboardingModal({
Set up Vercel Integration
-
-
-
+ {onboardingDataUnavailable ? (
+
+
+ We couldn't load your Vercel projects. The integration may have been removed or lost
+ access to this organization on Vercel.
+
+
+ {onDataReload && (
+
+ )}
+ {vercelManageAccessUrl && (
+
+ Manage access on Vercel
+
+ )}
+
+
+ ) : (
+
+
+
+ )}
);
diff --git a/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts b/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
index 10a46c01b3a..4c9cdda07b5 100644
--- a/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
@@ -173,6 +173,9 @@ export class VercelSettingsPresenter extends BasePresenter {
})
).map((repo) => repo !== null);
+ // Match on slug, not type: preview branches are PREVIEW rows too, and only the
+ // branchable parent carries the "preview" slug. Keeps this in step with
+ // GitHubSettingsPresenter and ProjectSettingsService, which ask the same question.
const checkStagingEnvironment = () =>
fromPromise(
(this._replica as PrismaClient).runtimeEnvironment.findFirst({
@@ -181,7 +184,7 @@ export class VercelSettingsPresenter extends BasePresenter {
},
where: {
projectId,
- type: "STAGING",
+ slug: "stg",
},
}),
(error) => ({
@@ -198,7 +201,7 @@ export class VercelSettingsPresenter extends BasePresenter {
},
where: {
projectId,
- type: "PREVIEW",
+ slug: "preview",
},
}),
(error) => ({
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
index 864cc300fa4..b6c697187a6 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
@@ -223,6 +223,10 @@ export default function IntegrationsSettingsPage() {
const loadVercelOnboarding = vercelFetcher.load;
const onboardingData = vercelFetcher.data?.onboardingData ?? null;
const hasVercelFetcherData = vercelFetcher.data !== undefined;
+ // The fetcher always requests `?vercelOnboarding=true`, so a settled load with no
+ // onboardingData means the presenter returned null - not that we simply didn't ask.
+ const onboardingDataUnavailable =
+ hasVercelFetcherData && vercelFetcher.state === "idle" && onboardingData === null;
const vercelOnboardingPath = `${vercelResourcePath(
organization.slug,
project.slug,
@@ -375,24 +379,26 @@ export default function IntegrationsSettingsPage() {
/>
)}
-
-
-
- Applies to deployments triggered from GitHub, and CLI deployments run with the{" "}
-
- --native-build-server
- {" "}
- flag.
- >
- }
- />
-
-
>
)}
+
+ {/* Build settings also drive CLI deploys, so they stay available when the
+ GitHub app is disabled and the Git/Vercel sections above are hidden. */}
+
+
+ Applies to deployments triggered from GitHub, and CLI deployments run with the{" "}
+
+ --native-build-server
+ {" "}
+ flag.
+ >
+ }
+ />
+
+
{/* Vercel Onboarding Modal */}
@@ -407,6 +413,7 @@ export default function IntegrationsSettingsPage() {
hasStagingEnvironment={vercelFetcher.data?.hasStagingEnvironment ?? false}
hasPreviewEnvironment={vercelFetcher.data?.hasPreviewEnvironment ?? false}
hasOrgIntegration={vercelFetcher.data?.hasOrgIntegration ?? false}
+ onboardingDataUnavailable={onboardingDataUnavailable}
nextUrl={nextUrl ?? undefined}
vercelManageAccessUrl={vercelFetcher.data?.vercelManageAccessUrl}
onDataReload={(vercelEnvironmentId) => {
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 88fe46b7ba5..bc5e7187cac 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
@@ -2,7 +2,7 @@ import { getFormProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import { DialogClose } from "@radix-ui/react-dialog";
-import { Form, useActionData, useFetcher, useLocation, useNavigation } from "@remix-run/react";
+import { Form, useActionData, useFetcher, useNavigation } from "@remix-run/react";
import { type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { Result, fromPromise } from "neverthrow";
import { useEffect, useRef, useState } from "react";
@@ -62,7 +62,6 @@ import {
type SyncEnvVarsMapping,
type VercelProjectIntegrationData,
envSlugArrayField,
- getAvailableEnvSlugs,
getAvailableEnvSlugsForBuildSettings,
} from "~/v3/vercel/vercelProjectIntegrationSchema";
import { sanitizeVercelNextUrl } from "~/v3/vercel/vercelUrls.server";
@@ -596,7 +595,6 @@ function VercelLoadingIcon() {
function VercelSettingsRows({
organizationSlug,
projectSlug,
- environmentSlug: _environmentSlug,
hasOrgIntegration,
isGitHubConnected,
onOpenModal,
@@ -605,7 +603,6 @@ function VercelSettingsRows({
}: {
organizationSlug: string;
projectSlug: string;
- environmentSlug: string;
hasOrgIntegration: boolean;
isGitHubConnected: boolean;
onOpenModal?: () => void;
@@ -698,19 +695,6 @@ function VercelGitHubWarning() {
);
}
-function envSlugLabel(slug: EnvSlug): string {
- switch (slug) {
- case "prod":
- return "Production";
- case "stg":
- return "Staging";
- case "preview":
- return "Preview";
- case "dev":
- return "Development";
- }
-}
-
function ConnectedVercelProjectForm({
connectedProject,
hasStagingEnvironment,
@@ -774,7 +758,7 @@ function ConnectedVercelProjectForm({
stagingEnvChanged ||
autoPromoteChanged;
- const [configForm, _fields] = useForm({
+ const [configForm] = useForm({
id: "update-vercel-config",
lastResult: lastSubmission,
shouldRevalidate: "onSubmit",
@@ -833,7 +817,6 @@ function ConnectedVercelProjectForm({
const actionUrl = vercelResourcePath(organizationSlug, projectSlug, environmentSlug);
- const availableEnvSlugs = getAvailableEnvSlugs(hasStagingEnvironment, hasPreviewEnvironment);
const availableEnvSlugsForBuildSettings = getAvailableEnvSlugsForBuildSettings(
hasStagingEnvironment,
hasPreviewEnvironment
@@ -844,15 +827,6 @@ function ConnectedVercelProjectForm({
? { stg: "Set a Vercel environment for Staging first." }
: undefined;
- const _formatSelectedEnvs = (
- selected: EnvSlug[],
- availableSlugs: EnvSlug[] = availableEnvSlugs
- ): string => {
- if (selected.length === 0) return "None selected";
- if (selected.length === availableSlugs.length) return "All environments";
- return selected.map(envSlugLabel).join(", ");
- };
-
return (
<>
@@ -1205,7 +1178,6 @@ function VercelSettingsPanel({
}) {
const fetcher = useTypedFetcher();
const { load } = fetcher;
- const _location = useLocation();
const data = fetcher.data;
const [hasFetched, setHasFetched] = useState(false);
@@ -1270,7 +1242,6 @@ function VercelSettingsPanel({