Skip to content

Commit 7328bba

Browse files
committed
feat(vercel): Refactor Vercel integration components for improved functionality and error handling
1 parent e6b25ce commit 7328bba

File tree

7 files changed

+63
-42
lines changed

7 files changed

+63
-42
lines changed

apps/webapp/app/components/integrations/VercelBuildSettings.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
environmentFullTitle,
88
environmentTextClassName,
99
} from "~/components/environments/EnvironmentLabel";
10-
import type { EnvSlug } from "~/v3/vercel/vercelProjectIntegrationSchema";
10+
import { envSlugToType, type EnvSlug } from "~/v3/vercel/vercelProjectIntegrationSchema";
1111

1212
type BuildSettingsFieldsProps = {
1313
availableEnvSlugs: EnvSlug[];
@@ -20,10 +20,6 @@ type BuildSettingsFieldsProps = {
2020
envVarsConfigLink?: string;
2121
};
2222

23-
function slugToEnvType(slug: EnvSlug) {
24-
return slug === "prod" ? "PRODUCTION" : slug === "stg" ? "STAGING" : "PREVIEW";
25-
}
26-
2723
export function BuildSettingsFields({
2824
availableEnvSlugs,
2925
pullEnvVarsBeforeBuild,
@@ -66,7 +62,7 @@ export function BuildSettingsFields({
6662
</div>
6763
<div className="flex flex-col gap-2 rounded border bg-charcoal-800 p-3">
6864
{availableEnvSlugs.map((slug) => {
69-
const envType = slugToEnvType(slug);
65+
const envType = envSlugToType(slug);
7066
return (
7167
<div key={slug} className="flex items-center justify-between">
7268
<div className="flex items-center gap-1.5">
@@ -125,7 +121,7 @@ export function BuildSettingsFields({
125121
</div>
126122
<div className="flex flex-col gap-2 rounded border bg-charcoal-800 p-3">
127123
{availableEnvSlugs.map((slug) => {
128-
const envType = slugToEnvType(slug);
124+
const envType = envSlugToType(slug);
129125
const isPullDisabled = !pullEnvVarsBeforeBuild.includes(slug);
130126
return (
131127
<div

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ import { vercelResourcePath } from "~/routes/resources.orgs.$organizationSlug.pr
4949
import type { loader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel";
5050
import { useEffect, useState, useCallback, useRef } from "react";
5151

52+
function safeRedirectUrl(url: string): string | null {
53+
try {
54+
const parsed = new URL(url, window.location.origin);
55+
if (parsed.protocol === "https:" || parsed.origin === window.location.origin) {
56+
return parsed.toString();
57+
}
58+
} catch {
59+
// Invalid URL
60+
}
61+
return null;
62+
}
63+
5264
function formatVercelTargets(targets: string[]): string {
5365
const targetLabels: Record<string, string> = {
5466
production: "Production",
@@ -241,9 +253,12 @@ export function VercelOnboardingModal({
241253
isGitHubConnectedForOnboarding
242254
) {
243255
hasTriggeredMarketplaceRedirectRef.current = true;
244-
setTimeout(() => {
245-
window.location.href = nextUrl;
246-
}, 100);
256+
const validUrl = safeRedirectUrl(nextUrl);
257+
if (validUrl) {
258+
setTimeout(() => {
259+
window.location.href = validUrl;
260+
}, 100);
261+
}
247262
}
248263
}, [isOpen, fromMarketplaceContext, nextUrl, isOnboardingComplete, isGitHubConnectedForOnboarding]);
249264

apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type PrismaClient } from "@trigger.dev/database";
2-
import { fromPromise, ok, okAsync, ResultAsync } from "neverthrow";
2+
import { type Result, fromPromise, ok, okAsync, ResultAsync } from "neverthrow";
33
import { env } from "~/env.server";
44
import { logger } from "~/services/logger.server";
55
import { OrgIntegrationRepository } from "~/models/orgIntegration.server";
@@ -62,7 +62,7 @@ export class VercelSettingsPresenter extends BasePresenter {
6262
/**
6363
* Get Vercel integration settings for the settings page
6464
*/
65-
public async call({ projectId, organizationId }: VercelSettingsOptions) {
65+
public async call({ projectId, organizationId }: VercelSettingsOptions): Promise<Result<VercelSettingsResult, unknown>> {
6666
const vercelIntegrationEnabled = OrgIntegrationRepository.isVercelSupported;
6767

6868
if (!vercelIntegrationEnabled) {

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ import {
7474
} from "~/v3/environmentVariables/repository";
7575
import { UserAvatar } from "~/components/UserProfilePhoto";
7676
import { VercelIntegrationService } from "~/services/vercelIntegration.server";
77+
import { fromPromise } from "neverthrow";
78+
import { logger } from "~/services/logger.server";
7779
import { shouldSyncEnvVar, isPullEnvVarsEnabledForEnvironment, type TriggerEnvironmentType } from "~/v3/vercel/vercelProjectIntegrationSchema";
7880

7981
export const meta: MetaFunction = () => {
@@ -121,7 +123,7 @@ const schema = z.discriminatedUnion("action", [
121123
action: z.literal("update-vercel-sync"),
122124
key: z.string(),
123125
environmentType: z.enum(["PRODUCTION", "STAGING", "PREVIEW", "DEVELOPMENT"]),
124-
syncEnabled: z.string().transform((val) => val === "true"),
126+
syncEnabled: z.union([z.literal("true"), z.literal("false")]).transform((val) => val === "true"),
125127
}),
126128
]);
127129

@@ -187,22 +189,31 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
187189
return json(submission);
188190
}
189191

190-
// Clean up syncEnvVarsMapping if Vercel integration exists
192+
// Clean up syncEnvVarsMapping if Vercel integration exists (best-effort)
193+
const { environmentId, key } = submission.value;
191194
const vercelService = new VercelIntegrationService();
192-
const integration = await vercelService.getVercelProjectIntegration(project.id);
193-
if (integration) {
194-
const runtimeEnv = await prisma.runtimeEnvironment.findUnique({
195-
where: { id: submission.value.environmentId },
196-
select: { type: true },
197-
});
198-
if (runtimeEnv) {
199-
await vercelService.removeSyncEnvVarForEnvironment(
200-
project.id,
201-
submission.value.key,
202-
runtimeEnv.type as TriggerEnvironmentType
203-
);
204-
}
205-
}
195+
await fromPromise(
196+
(async () => {
197+
const integration = await vercelService.getVercelProjectIntegration(project.id);
198+
if (integration) {
199+
const runtimeEnv = await prisma.runtimeEnvironment.findUnique({
200+
where: { id: environmentId },
201+
select: { type: true },
202+
});
203+
if (runtimeEnv) {
204+
await vercelService.removeSyncEnvVarForEnvironment(
205+
project.id,
206+
key,
207+
runtimeEnv.type as TriggerEnvironmentType
208+
);
209+
}
210+
}
211+
})(),
212+
(error) => error
213+
).mapErr((error) => {
214+
logger.error("Failed to remove Vercel sync mapping", { error });
215+
return error;
216+
});
206217

207218
return redirectWithSuccessMessage(
208219
v3EnvironmentVariablesPath(

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ export default function Page() {
349349
if (!isModalOpen) {
350350
openVercelOnboarding();
351351
}
352-
} else if (vercelFetcher.state === "idle" && !vercelFetcher.data?.onboardingData) {
352+
} else if (vercelFetcher.state === "idle" && vercelFetcher.data === undefined) {
353353
// Load onboarding data
354354
vercelFetcher.load(
355355
`${vercelResourcePath(organization.slug, project.slug, environment.slug)}?vercelOnboarding=true`

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -538,9 +538,11 @@ export function ConnectGitHubRepoModal({
538538
</Button>
539539
}
540540
cancelButton={
541-
<DialogClose asChild>
542-
<Button variant="tertiary/medium">Cancel</Button>
543-
</DialogClose>
541+
preventDismiss ? undefined : (
542+
<DialogClose asChild>
543+
<Button variant="tertiary/medium">Cancel</Button>
544+
</DialogClose>
545+
)
544546
}
545547
/>
546548
</Fieldset>

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,24 +158,21 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
158158
}
159159

160160
const presenter = new VercelSettingsPresenter();
161-
const resultOrFail = await fromPromise(
162-
presenter.call({
163-
projectId: project.id,
164-
organizationId: project.organizationId,
165-
}),
166-
(error) => error
167-
);
161+
const resultOrFail = await presenter.call({
162+
projectId: project.id,
163+
organizationId: project.organizationId,
164+
});
168165

169-
if (resultOrFail.isErr() || !resultOrFail.value?.isOk()) {
166+
if (resultOrFail.isErr()) {
170167
logger.error("Failed to load Vercel settings", {
171168
url: request.url,
172169
params,
173-
error: resultOrFail.isErr() ? resultOrFail.error : undefined,
170+
error: resultOrFail.error,
174171
});
175172
throw new Response("Failed to load Vercel settings", { status: 500 });
176173
}
177174

178-
const result = resultOrFail.value.value;
175+
const result = resultOrFail.value;
179176
const url = new URL(request.url);
180177
const needsOnboarding = url.searchParams.get("vercelOnboarding") === "true";
181178
const vercelEnvironmentId = url.searchParams.get("vercelEnvironmentId") || undefined;

0 commit comments

Comments
 (0)