Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function CustomDomainRow({ domain, environmentSlug }: CustomDomainRowProp
};

return (
<div className="border-b border-gray-4 last:border-b-0 group hover:bg-grayA-3 transition-colors">
<div className="border-b border-gray-4 last:border-b-0 hover:bg-transparent group">
<div className="flex items-center justify-between px-4 py-3 h-12">
<div className="flex items-center gap-3 flex-1 min-w-0">
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ import {
} from "@unkey/ui";
import { Controller, useForm } from "react-hook-form";
import { useProjectData } from "../../../../data-provider";
import { useEnvironmentSettings } from "../../../environment-provider";
import { FormSettingCard } from "../../shared/form-setting-card";
import { CustomDomainRow } from "./custom-domain-row";
import { type CustomDomainFormValues, customDomainSchema } from "./schema";

export const CustomDomains = () => {
const { environments, customDomains, projectId } = useProjectData();

const defaultEnvironmentId =
environments.find((e) => e.slug === "production")?.id ?? environments[0]?.id ?? "";
const {
settings: { environmentId: defaultEnvironmentId },
} = useEnvironmentSettings();

return (
<CustomDomainSettings
Expand Down Expand Up @@ -118,7 +119,7 @@ const CustomDomainSettings: React.FC<CustomDomainSettingsProps> = ({
<span className="text-[13px] text-gray-11 w-[140px]">Environment</span>
<span className="flex-1 text-[13px] text-gray-11">Domain</span>
</div>
<div className="flex items-start gap-3">
<div className="flex items-start gap-3 w-[480px]">
<Controller
control={control}
name="environmentId"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";

import { trpc } from "@/lib/trpc/client";
import { collection } from "@/lib/collections";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { eq, useLiveQuery } from "@tanstack/react-db";
import { Nodes2 } from "@unkey/icons";
import { toast } from "@unkey/ui";
import { useMemo } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { useProjectData } from "../../../../data-provider";
import { useEnvironmentSettings } from "../../../environment-provider";
import { FormSettingCard } from "../../shared/form-setting-card";
import { EnvVarRow } from "./env-var-row";
import { type EnvVarsFormValues, createEmptyRow, envVarsSchema } from "./schema";
Expand All @@ -17,27 +18,25 @@ import { computeEnvVarsDiff, groupByEnvironment, toTrpcType } from "./utils";

export const EnvVars = () => {
const { projectId, environments } = useProjectData();
const { settings } = useEnvironmentSettings();
const defaultEnvironmentId = settings.environmentId;

const defaultEnvironmentId =
environments.find((e) => e.slug === "production")?.id ?? environments[0]?.id;

const { data } = trpc.deploy.envVar.list.useQuery({ projectId }, { enabled: Boolean(projectId) });
const { data: envVarData } = useLiveQuery(
(q) => q.from({ v: collection.envVars }).where(({ v }) => eq(v.projectId, projectId)),
[projectId],
);

const allVariables = useMemo(() => {
if (!data) {
if (!envVarData) {
return [];
}
return environments.flatMap((env) => {
const envData = data[env.slug];
if (!envData) {
return [];
}
return envData.variables.map((v) => ({
...v,
environmentId: env.id,
}));
});
}, [data, environments]);
return envVarData.map((v) => ({
id: v.id,
key: v.key,
type: v.type,
environmentId: v.environmentId,
}));
}, [envVarData]);

const { decryptedValues, isDecrypting } = useDecryptedValues(allVariables);

Expand All @@ -62,10 +61,6 @@ export const EnvVars = () => {
return `${varIds}:${decryptedIds}`;
}, [allVariables, decryptedValues]);

if (!defaultEnvironmentId) {
return null;
}

return (
<EnvVarsForm
key={formKey}
Expand All @@ -91,8 +86,6 @@ const EnvVarsForm = ({
projectId: string;
isDecrypting: boolean;
}) => {
const utils = trpc.useUtils();

const {
register,
handleSubmit,
Expand All @@ -108,70 +101,39 @@ const EnvVarsForm = ({
const { ref, isDragging } = useDropZone(reset, defaultEnvironmentId);
const { fields, append, remove } = useFieldArray({ control, name: "envVars" });

const createMutation = trpc.deploy.envVar.create.useMutation();
const updateMutation = trpc.deploy.envVar.update.useMutation();
const deleteMutation = trpc.deploy.envVar.delete.useMutation();

const isSaving =
createMutation.isLoading ||
updateMutation.isLoading ||
deleteMutation.isLoading ||
isSubmitting;

const onSubmit = async (values: EnvVarsFormValues) => {
const { toDelete, toCreate, toUpdate, originalMap } = computeEnvVarsDiff(
const { toDelete, toCreate, toUpdate } = computeEnvVarsDiff(
defaultValues.envVars,
values.envVars,
);

const createsByEnv = groupByEnvironment(toCreate);

try {
await Promise.all([
...toDelete.map(async (id) => {
const key = originalMap.get(id)?.key ?? id;
try {
return await deleteMutation.mutateAsync({ envVarId: id });
} catch (err) {
throw new Error(`"${key}": ${err instanceof Error ? err.message : "Failed to delete"}`);
}
}),
...[...createsByEnv.entries()].map(([envId, vars]) =>
createMutation.mutateAsync({
await Promise.all([
...toDelete.map(async (id) => {
collection.envVars.delete(id);
}),
...[...createsByEnv.entries()].map(async ([envId, vars]) => {
for (const v of vars) {
collection.envVars.insert({
id: crypto.randomUUID(),
environmentId: envId,
variables: vars.map((v) => ({
key: v.key,
value: v.value,
type: toTrpcType(v.secret),
})),
}),
),
...toUpdate.map((v) =>
updateMutation
.mutateAsync({
envVarId: v.id as string,
key: v.key,
value: v.value,
type: toTrpcType(v.secret),
})
.catch((err) => {
throw new Error(
`"${v.key}": ${err instanceof Error ? err.message : "Failed to update"}`,
);
}),
),
]);

utils.deploy.envVar.list.invalidate({ projectId });
toast.success("Environment variables saved");
} catch (err) {
toast.error("Failed to save environment variables", {
description:
err instanceof Error
? err.message
: "An unexpected error occurred. Please try again or contact support@unkey.com",
});
}
projectId,
key: v.key,
value: v.value,
type: toTrpcType(v.secret) as "recoverable" | "writeonly",
description: null,
});
}
}),
...toUpdate.map(async (v) => {
collection.envVars.update(v.id as string, (draft) => {
draft.key = v.key;
draft.value = v.value;
draft.type = toTrpcType(v.secret) as "recoverable" | "writeonly";
});
}),
]);
};

const varCount = defaultValues.envVars.filter((v) => v.key !== "").length;
Expand All @@ -192,8 +154,8 @@ const EnvVarsForm = ({
description="Set environment variables available at runtime. Changes apply on next deploy."
displayValue={displayValue}
onSubmit={handleSubmit(onSubmit)}
canSave={isValid && !isSaving && !isDecrypting && isDirty}
isSaving={isSaving}
canSave={isValid && !isSubmitting && !isDecrypting && isDirty}
isSaving={isSubmitting}
ref={ref}
className={cn("relative", isDragging && "bg-primary/5")}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
import { trpc } from "@/lib/trpc/client";
import { collection } from "@/lib/collections";
import { zodResolver } from "@hookform/resolvers/zod";
import { FileSettings } from "@unkey/icons";
import { FormInput, toast } from "@unkey/ui";
import { FormInput } from "@unkey/ui";
import { useForm, useWatch } from "react-hook-form";
import { z } from "zod";
import { useProjectData } from "../../../data-provider";
import { useEnvironmentSettings } from "../../environment-provider";
import { FormSettingCard } from "../shared/form-setting-card";

const dockerfileSchema = z.object({
dockerfile: z.string().min(1, "Dockerfile path is required"),
});

export const DockerfileSettings = () => {
const { environments } = useProjectData();
const environmentId = environments[0]?.id;

const { data } = trpc.deploy.environmentSettings.get.useQuery(
{ environmentId: environmentId ?? "" },
{ enabled: Boolean(environmentId) },
);

const defaultValue = data?.buildSettings?.dockerfile ?? "Dockerfile";
return <DockerfileForm environmentId={environmentId} defaultValue={defaultValue} />;
};

const DockerfileForm = ({
environmentId,
defaultValue,
}: {
environmentId: string;
defaultValue: string;
}) => {
const utils = trpc.useUtils();
export const Dockerfile = () => {
const { settings } = useEnvironmentSettings();
const { environmentId, dockerfile: defaultValue } = settings;

const {
register,
Expand All @@ -46,35 +28,10 @@ const DockerfileForm = ({

const currentDockerfile = useWatch({ control, name: "dockerfile" });

const updateDockerfile = trpc.deploy.environmentSettings.build.updateDockerfile.useMutation({
onSuccess: (_data, variables) => {
toast.success("Dockerfile updated", {
description: `Path set to "${variables.dockerfile ?? defaultValue}".`,
duration: 5000,
});
utils.deploy.environmentSettings.get.invalidate({ environmentId });
},
onError: (err) => {
if (err.data?.code === "BAD_REQUEST") {
toast.error("Invalid Dockerfile path", {
description: err.message || "Please check your input and try again.",
});
} else {
toast.error("Failed to update Dockerfile", {
description:
err.message ||
"An unexpected error occurred. Please try again or contact support@unkey.com",
action: {
label: "Contact Support",
onClick: () => window.open("mailto:support@unkey.com", "_blank"),
},
});
}
},
});

const onSubmit = async (values: z.infer<typeof dockerfileSchema>) => {
await updateDockerfile.mutateAsync({ environmentId, dockerfile: values.dockerfile });
collection.environmentSettings.update(environmentId, (draft) => {
draft.dockerfile = values.dockerfile;
});
};

return (
Expand All @@ -85,7 +42,7 @@ const DockerfileForm = ({
displayValue={defaultValue}
onSubmit={handleSubmit(onSubmit)}
canSave={isValid && !isSubmitting && currentDockerfile !== defaultValue}
isSaving={updateDockerfile.isLoading || isSubmitting}
isSaving={isSubmitting}
>
<FormInput
required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type GitHubConnectionState =
| { status: "no-repo"; installUrl: string }
| { status: "connected"; repoFullName: string; repositoryId: number; installUrl: string };

export const GitHubSettings = () => {
export const GitHub = () => {
const { projectId } = useProjectData();

const state = JSON.stringify({ projectId });
Expand Down
Loading