Skip to content

Commit ee80442

Browse files
committed
feat(vercel): Enhance RegenerateApiKeyModal to include Vercel integration options and sync functionality
1 parent 2953d4c commit ee80442

File tree

5 files changed

+63
-13
lines changed

5 files changed

+63
-13
lines changed

apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ import { FormButtons } from "../primitives/FormButtons";
1010
import { Input } from "../primitives/Input";
1111
import { InputGroup } from "../primitives/InputGroup";
1212
import { Paragraph } from "../primitives/Paragraph";
13+
import { CheckboxWithLabel } from "../primitives/Checkbox";
1314
import { Spinner } from "../primitives/Spinner";
1415

1516
type ModalProps = {
1617
id: string;
1718
title: string;
19+
hasVercelIntegration: boolean;
20+
isDevelopment: boolean;
1821
};
1922

2023
type ModalContentProps = ModalProps & {
2124
randomWord: string;
2225
closeModal: () => void;
2326
};
2427

25-
export function RegenerateApiKeyModal({ id, title }: ModalProps) {
28+
export function RegenerateApiKeyModal({
29+
id,
30+
title,
31+
hasVercelIntegration,
32+
isDevelopment,
33+
}: ModalProps) {
2634
const randomWord = generateTwoRandomWords();
2735
const [open, setOpen] = useState(false);
2836
return (
@@ -37,6 +45,8 @@ export function RegenerateApiKeyModal({ id, title }: ModalProps) {
3745
<RegenerateApiKeyModalContent
3846
id={id}
3947
title={title}
48+
hasVercelIntegration={hasVercelIntegration}
49+
isDevelopment={isDevelopment}
4050
randomWord={randomWord}
4151
closeModal={() => setOpen(false)}
4252
/>
@@ -45,7 +55,14 @@ export function RegenerateApiKeyModal({ id, title }: ModalProps) {
4555
);
4656
}
4757

48-
const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: ModalContentProps) => {
58+
const RegenerateApiKeyModalContent = ({
59+
id,
60+
randomWord,
61+
title,
62+
hasVercelIntegration,
63+
isDevelopment,
64+
closeModal,
65+
}: ModalContentProps) => {
4966
const [confirmationText, setConfirmationText] = useState("");
5067
const fetcher = useFetcher();
5168
const isSubmitting = fetcher.state === "submitting";
@@ -83,6 +100,15 @@ const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: Mod
83100
onChange={(e) => setConfirmationText(e.target.value)}
84101
/>
85102
</InputGroup>
103+
{hasVercelIntegration && !isDevelopment && (
104+
<CheckboxWithLabel
105+
name="syncToVercel"
106+
variant="simple/small"
107+
label="Also update TRIGGER_SECRET_KEY in Vercel"
108+
defaultChecked={true}
109+
value="on"
110+
/>
111+
)}
86112
<FormButtons
87113
confirmButton={
88114
<Button

apps/webapp/app/models/vercelIntegration.server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function extractVercelEnvs(
4747
}
4848

4949
function isVercelSecretType(type: string): boolean {
50-
return type === "secret" || type === "sensitive" || type === "encrypted";
50+
return type === "secret" || type === "sensitive";
5151
}
5252

5353
// ---------------------------------------------------------------------------
@@ -809,12 +809,12 @@ export class VercelIntegrationRepository {
809809
return this.getVercelClient(params.orgIntegration).andThen((client) =>
810810
ResultAsync.fromPromise(
811811
(async () => {
812-
// Get all environments for the project
812+
// Get all environments for the project (exclude DEVELOPMENT — we don't push keys to Vercel's development target)
813813
const environments = await prisma.runtimeEnvironment.findMany({
814814
where: {
815815
projectId: params.projectId,
816816
type: {
817-
in: ["PRODUCTION", "STAGING", "PREVIEW", "DEVELOPMENT"],
817+
in: ["PRODUCTION", "STAGING", "PREVIEW"],
818818
},
819819
},
820820
select: {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export class ApiKeysPresenter {
3838
apiKey: true,
3939
},
4040
},
41+
project: {
42+
select: {
43+
id: true,
44+
},
45+
},
4146
},
4247
where: {
4348
project: {
@@ -64,11 +69,22 @@ export class ApiKeysPresenter {
6469
throw new Error("Environment not found");
6570
}
6671

72+
const vercelIntegration =
73+
await this.#prismaClient.organizationProjectIntegration.findFirst({
74+
where: {
75+
projectId: environment.project.id,
76+
deletedAt: null,
77+
organizationIntegration: { service: "VERCEL", deletedAt: null },
78+
},
79+
select: { id: true },
80+
});
81+
6782
return {
6883
environment: {
6984
...environment,
7085
apiKey: environment?.parentEnvironment?.apiKey ?? environment?.apiKey,
7186
},
87+
hasVercelIntegration: vercelIntegration !== null,
7288
};
7389
}
7490
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
5151

5252
try {
5353
const presenter = new ApiKeysPresenter();
54-
const { environment } = await presenter.call({
54+
const { environment, hasVercelIntegration } = await presenter.call({
5555
userId,
5656
projectSlug: projectParam,
5757
environmentSlug: envParam,
5858
});
5959

6060
return typedjson({
6161
environment,
62+
hasVercelIntegration,
6263
});
6364
} catch (error) {
6465
console.error(error);
@@ -70,7 +71,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
7071
};
7172

7273
export default function Page() {
73-
const { environment } = useTypedLoaderData<typeof loader>();
74+
const { environment, hasVercelIntegration } = useTypedLoaderData<typeof loader>();
7475
const organization = useOrganization();
7576

7677
if (!environment) {
@@ -132,6 +133,8 @@ export default function Page() {
132133
<RegenerateApiKeyModal
133134
id={environment.parentEnvironment?.id ?? environment.id}
134135
title={environmentFullTitle(environment)}
136+
hasVercelIntegration={hasVercelIntegration}
137+
isDevelopment={environment.type === "DEVELOPMENT"}
135138
/>
136139
</div>
137140
<ClipboardField

apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
2121

2222
const { environmentId } = ParamsSchema.parse(params);
2323

24+
const formData = await request.formData();
25+
const syncToVercel = formData.get("syncToVercel") === "on";
26+
2427
try {
2528
const updatedEnvironment = await regenerateApiKey({ userId, environmentId });
2629

27-
// Sync the regenerated API key to Vercel if integration exists
28-
await syncApiKeyToVercel(
29-
updatedEnvironment.projectId,
30-
updatedEnvironment.type as "PRODUCTION" | "STAGING" | "PREVIEW" | "DEVELOPMENT",
31-
updatedEnvironment.apiKey
32-
);
30+
// Sync the regenerated API key to Vercel only when requested and not for DEVELOPMENT
31+
if (syncToVercel && updatedEnvironment.type !== "DEVELOPMENT") {
32+
await syncApiKeyToVercel(
33+
updatedEnvironment.projectId,
34+
updatedEnvironment.type as "PRODUCTION" | "STAGING" | "PREVIEW",
35+
updatedEnvironment.apiKey
36+
);
37+
}
3338

3439
return jsonWithSuccessMessage(
3540
{ ok: true },

0 commit comments

Comments
 (0)