import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, } from "@/components/ui/form"; import { Secrets } from "@/components/ui/secrets"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; const addEnvironmentSchema = z.object({ env: z.string(), buildArgs: z.string(), buildSecrets: z.string(), createEnvFile: z.boolean(), }); type EnvironmentSchema = z.infer; interface Props { applicationId: string; } export const ShowEnvironment = ({ applicationId }: Props) => { const { data: permissions } = api.user.getPermissions.useQuery(); const canWrite = permissions?.envVars.write ?? false; const { mutateAsync, isPending } = api.application.saveEnvironment.useMutation(); const { data, refetch } = api.application.one.useQuery( { applicationId, }, { enabled: !!applicationId, }, ); const form = useForm({ defaultValues: { env: "", buildArgs: "", buildSecrets: "", createEnvFile: true, }, resolver: zodResolver(addEnvironmentSchema), }); // Watch form values const currentEnv = form.watch("env"); const currentBuildArgs = form.watch("buildArgs"); const currentBuildSecrets = form.watch("buildSecrets"); const currentCreateEnvFile = form.watch("createEnvFile"); const hasChanges = currentEnv !== (data?.env || "") || currentBuildArgs !== (data?.buildArgs || "") || currentBuildSecrets !== (data?.buildSecrets || "") || currentCreateEnvFile !== (data?.createEnvFile ?? true); useEffect(() => { if (data) { form.reset({ env: data.env || "", buildArgs: data.buildArgs || "", buildSecrets: data.buildSecrets || "", createEnvFile: data.createEnvFile ?? true, }); } }, [data, form]); const onSubmit = async (formData: EnvironmentSchema) => { mutateAsync({ env: formData.env, buildArgs: formData.buildArgs, buildSecrets: formData.buildSecrets, createEnvFile: formData.createEnvFile, applicationId, }) .then(async () => { toast.success("Environments Added"); await refetch(); }) .catch(() => { toast.error("Error adding environment"); }); }; const handleCancel = () => { form.reset({ env: data?.env || "", buildArgs: data?.buildArgs || "", buildSecrets: data?.buildSecrets || "", createEnvFile: data?.createEnvFile ?? true, }); }; // Add keyboard shortcut for Ctrl+S/Cmd+S useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.code === "KeyS" && !isPending) { e.preventDefault(); form.handleSubmit(onSubmit)(); } }; document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); }; }, [form, onSubmit, isPending]); return (
You can add environment variables to your resource. {hasChanges && ( (You have unsaved changes) )} } placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")} /> {data?.buildType === "dockerfile" && ( Arguments are available only at build-time. See documentation  here . } placeholder="NPM_TOKEN=xyz" /> )} {data?.buildType === "dockerfile" && ( Secrets are specially designed for sensitive information and are only available at build-time. See documentation  here . } placeholder="NPM_TOKEN=xyz" /> )} {data?.buildType === "dockerfile" && ( (
Create Environment File When enabled, an .env file will be created in the same directory as your Dockerfile during the build process. Disable this if you don't want to generate an environment file.
)} /> )} {canWrite && (
{hasChanges && ( )}
)}
); };