diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx index 4a47fe1467..bda776a0ee 100644 --- a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx @@ -1,71 +1,112 @@ -import { Switch } from "@/components/ui/switch"; -import { Button, Input } from "@unkey/ui"; +import { trpc } from "@/lib/trpc/client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { EnvVarInputs } from "./components/env-var-inputs"; +import { EnvVarSaveActions } from "./components/env-var-save-actions"; +import { EnvVarSecretSwitch } from "./components/env-var-secret-switch"; +import { type EnvVar, type EnvVarFormData, EnvVarFormSchema } from "./types"; type AddEnvVarRowProps = { - value: { key: string; value: string; isSecret: boolean }; - onChange: (value: { key: string; value: string; isSecret: boolean }) => void; - onSave: () => void; + projectId: string; + getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined; onCancel: () => void; }; -export function AddEnvVarRow({ value, onChange, onSave, onCancel }: AddEnvVarRowProps) { - const handleSave = () => { - if (!value.key.trim() || !value.value.trim()) { - return; +export function AddEnvVarRow({ projectId, getExistingEnvVar, onCancel }: AddEnvVarRowProps) { + const trpcUtils = trpc.useUtils(); + + // TODO: Add mutation when available + // const upsertMutation = trpc.deploy.project.envs.upsert.useMutation(); + + const { + register, + handleSubmit, + watch, + setValue, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + resolver: zodResolver( + EnvVarFormSchema.superRefine((data, ctx) => { + const existing = getExistingEnvVar(data.key); + if (existing) { + ctx.addIssue({ + code: "custom", + message: "Variable name already exists", + path: ["key"], + }); + } + }), + ), + defaultValues: { + key: "", + value: "", + type: "env", + }, + }); + + const watchedType = watch("type"); + + const handleSave = async (_formData: EnvVarFormData) => { + try { + // TODO: Call tRPC upsert when available + // await upsertMutation.mutateAsync({ + // projectId, + // ...formData + // }); + + // Mock successful save for now + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Invalidate to refresh data + await trpcUtils.deploy.project.envs.getEnvs.invalidate({ projectId }); + + onCancel(); // Close the add form + } catch (error) { + console.error("Failed to add env var:", error); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && isValid && !isSubmitting) { + handleSubmit(handleSave)(); + } else if (e.key === "Escape") { + onCancel(); } - onSave(); }; return (
-
- onChange({ ...value, key: e.target.value })} - placeholder="Variable name" - className="min-h-[32px] text-xs w-48" +
+ - = - onChange({ ...value, value: e.target.value })} - placeholder="Variable value" - className="min-h-[32px] text-xs flex-1" - type={value.isSecret ? "password" : "text"} - /> -
-
-
- Secret - onChange({ ...value, isSecret: checked })} +
+ + setValue("type", checked ? "secret" : "env", { + shouldDirty: true, + shouldValidate: true, + }) + } + disabled={isSubmitting} + /> +
- - -
+
); } diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx new file mode 100644 index 0000000000..0845e43fdd --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx @@ -0,0 +1,127 @@ +import { trpc } from "@/lib/trpc/client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { type EnvVar, type EnvVarFormData, EnvVarFormSchema } from "../types"; +import { EnvVarInputs } from "./env-var-inputs"; +import { EnvVarSaveActions } from "./env-var-save-actions"; +import { EnvVarSecretSwitch } from "./env-var-secret-switch"; + +type EnvVarFormProps = { + initialData: EnvVarFormData; + projectId: string; + getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined; + onSuccess: () => void; + onCancel: () => void; + excludeId?: string; + autoFocus?: boolean; + decrypted?: boolean; + className?: string; +}; + +export function EnvVarForm({ + initialData, + projectId, + getExistingEnvVar, + onSuccess, + onCancel, + excludeId, + decrypted, + autoFocus = false, + className = "w-full flex px-4 py-3 bg-gray-2 border-b border-gray-4 last:border-b-0", +}: EnvVarFormProps) { + const trpcUtils = trpc.useUtils(); + + // TODO: Add mutations when available + // const upsertMutation = trpc.deploy.project.envs.upsert.useMutation(); + + const { + register, + handleSubmit, + watch, + setValue, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + resolver: zodResolver( + EnvVarFormSchema.superRefine((data, ctx) => { + const existing = getExistingEnvVar(data.key, excludeId); + if (existing) { + ctx.addIssue({ + code: "custom", + message: "Variable name already exists", + path: ["key"], + }); + } + }), + ), + defaultValues: initialData, + }); + + const watchedType = watch("type"); + + const handleSave = async (_formData: EnvVarFormData) => { + try { + // TODO: Call tRPC upsert when available + // await upsertMutation.mutateAsync({ + // projectId, + // id: excludeId, // Will be undefined for new entries + // ...formData + // }); + + // Mock successful save for now + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Invalidate to refresh data + await trpcUtils.deploy.project.envs.getEnvs.invalidate({ projectId }); + + onSuccess(); + } catch (error) { + console.error("Failed to save env var:", error); + throw error; // Re-throw to let form handle the error state + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && isValid && !isSubmitting) { + handleSubmit(handleSave)(); + } else if (e.key === "Escape") { + onCancel(); + } + }; + + return ( +
+
+ +
+ + setValue("type", checked ? "secret" : "env", { + shouldDirty: true, + shouldValidate: true, + }) + } + disabled={isSubmitting} + /> + +
+ +
+ ); +} diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-inputs.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-inputs.tsx new file mode 100644 index 0000000000..ebc4c6607f --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-inputs.tsx @@ -0,0 +1,54 @@ +import { cn } from "@/lib/utils"; +import { Input } from "@unkey/ui"; +import { forwardRef } from "react"; +import type { FieldErrors, UseFormRegister } from "react-hook-form"; +import type { EnvVarFormData } from "../types"; + +type EnvVarInputsProps = { + register: UseFormRegister; + errors: FieldErrors; + isSecret: boolean; + onKeyDown?: (e: React.KeyboardEvent) => void; + decrypted?: boolean; + autoFocus?: boolean; +}; + +export const EnvVarInputs = forwardRef( + ({ register, errors, isSecret, onKeyDown, autoFocus = false, decrypted }, ref) => { + return ( +
+
+ +
+ = + +
+ ); + }, +); diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-save-actions.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-save-actions.tsx new file mode 100644 index 0000000000..fe253ef580 --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-save-actions.tsx @@ -0,0 +1,39 @@ +import { Button } from "@unkey/ui"; + +export const EnvVarSaveActions = ({ + save, + cancel, + isSubmitting, +}: { + isSubmitting: boolean; + save: { + disabled: boolean; + }; + cancel: { + disabled: boolean; + onClick: () => void; + }; +}) => { + return ( + <> + + + + ); +}; diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-secret-switch.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-secret-switch.tsx new file mode 100644 index 0000000000..6f329ae69c --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-secret-switch.tsx @@ -0,0 +1,24 @@ +import { Switch } from "@/components/ui/switch"; + +export const EnvVarSecretSwitch = ({ + isSecret, + onCheckedChange, + disabled, +}: { + isSecret: boolean; + onCheckedChange?(checked: boolean): void; + disabled: boolean; +}) => { + return ( +
+ Secret + +
+ ); +}; diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx index f33026ef41..e961433f79 100644 --- a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx @@ -1,104 +1,132 @@ -import type { EnvVar } from "@/lib/trpc/routers/deploy/project/envs/list"; +import { trpc } from "@/lib/trpc/client"; +import { cn } from "@/lib/utils"; import { Eye, EyeSlash, PenWriting3, Trash } from "@unkey/icons"; -import { Button, Input } from "@unkey/ui"; -import { useEffect, useState } from "react"; +import { Button } from "@unkey/ui"; +import { useState } from "react"; +import { EnvVarForm } from "./components/env-var-form"; +import type { EnvVar } from "./types"; type EnvVarRowProps = { envVar: EnvVar; - isEditing: boolean; - onEdit: () => void; - onSave: (updates: Partial) => void; - onDelete: () => void; - onCancel: () => void; + projectId: string; + getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined; }; -export function EnvVarRow({ - envVar, - isEditing, - onEdit, - onSave, - onDelete, - onCancel, -}: EnvVarRowProps) { - const [editKey, setEditKey] = useState(envVar.key); - const [editValue, setEditValue] = useState(envVar.value); - const [isValueVisible, setIsValueVisible] = useState(false); +export function EnvVarRow({ envVar, projectId, getExistingEnvVar }: EnvVarRowProps) { + const [isEditing, setIsEditing] = useState(false); + const [isDecrypted, setIsDecrypted] = useState(false); + // INFO: Won't be necessary once we add tRPC then we can use isSubmitting + const [isSecretLoading, setIsSecretLoading] = useState(false); + const [decryptedValue, setDecryptedValue] = useState(); - // Make value visible when entering edit mode - useEffect(() => { - if (isEditing) { - setIsValueVisible(true); + const trpcUtils = trpc.useUtils(); + + // TODO: Add mutations when available + // const deleteMutation = trpc.deploy.project.envs.delete.useMutation(); + // const decryptMutation = trpc.deploy.project.envs.decrypt.useMutation(); + + const handleDelete = async () => { + try { + // TODO: Call tRPC delete when available + // await deleteMutation.mutateAsync({ + // projectId, + // id: envVar.id + // }); + + // Mock successful delete for now + await new Promise((resolve) => setTimeout(resolve, 300)); + + // Invalidate to refresh data + await trpcUtils.deploy.project.envs.getEnvs.invalidate({ projectId }); + } catch (error) { + console.error("Failed to delete env var:", error); } - }, [isEditing]); + }; - const handleSave = () => { - if (!editKey.trim() || !editValue.trim()) { + const handleToggleSecret = async () => { + if (envVar.type !== "secret") { return; } - onSave({ key: editKey.trim(), value: editValue.trim() }); - }; - const handleCancel = () => { - setEditKey(envVar.key); - setEditValue(envVar.value); - setIsValueVisible(false); - onCancel(); + // This stupid nested branching won't be necessary once we have the actual tRPC. So disregard this when reviewing + if (isDecrypted) { + setIsDecrypted(false); + } else { + if (decryptedValue) { + setIsDecrypted(true); + } else { + setIsSecretLoading(true); + try { + // TODO: Call tRPC decrypt when available + // const result = await decryptMutation.mutateAsync({ + // projectId, + // envVarId: envVar.id + // }); + + // Mock decrypted value for now + await new Promise((resolve) => setTimeout(resolve, 800)); + const mockDecrypted = `decrypted-${envVar.key}`; + + setDecryptedValue(mockDecrypted); + setIsDecrypted(true); + } catch (error) { + console.error("Failed to decrypt secret:", error); + } finally { + setIsSecretLoading(false); + } + } + } }; if (isEditing) { return ( -
-
- setEditKey(e.target.value)} - placeholder="Variable name" - className="min-h-[32px] text-xs w-48 " - autoFocus - /> - = - setEditValue(e.target.value)} - placeholder="Variable value" - className="min-h-[32px] text-xs flex-1" - type="text" - /> -
-
- - -
-
+ setIsEditing(false)} + onCancel={() => setIsEditing(false)} + className="w-full flex px-4 py-3 bg-gray-2 h-12" + /> ); } return (
-
{envVar.key}
+
{envVar.key}
=
-
- {envVar.isSecret && !isValueVisible ? "••••••••••••••••" : envVar.value} +
+ {envVar.type === "secret" && !isDecrypted + ? "••••••••••••••••" + : envVar.type === "secret" && isSecretLoading + ? "Loading..." + : envVar.type === "secret" && isDecrypted && decryptedValue + ? decryptedValue + : envVar.value}
- {envVar.isSecret && ( + {envVar.type === "secret" && (
- -
diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx new file mode 100644 index 0000000000..c0643e7bc7 --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx @@ -0,0 +1,23 @@ +import { trpc } from "@/lib/trpc/client"; +import type { Environment } from "../types"; + +type UseEnvVarsManagerProps = { + projectId: string; + environment: Environment; +}; + +export function useEnvVarsManager({ projectId, environment }: UseEnvVarsManagerProps) { + const { data } = trpc.deploy.project.envs.getEnvs.useQuery({ projectId }); + const envVars = data?.[environment] ?? []; + + // Helper to check for duplicate environment variable keys within the current environment. + // Used for client-side validation before making server requests. + const getExistingEnvVar = (key: string, excludeId?: string) => { + return envVars.find((envVar) => envVar.key.trim() === key.trim() && envVar.id !== excludeId); + }; + + return { + envVars, + getExistingEnvVar, + }; +} diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var.tsx deleted file mode 100644 index bf3a94863f..0000000000 --- a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { trpc } from "@/lib/trpc/client"; -import type { EnvVar } from "@/lib/trpc/routers/deploy/project/envs/list"; -import { useCallback, useEffect, useState } from "react"; - -type UseEnvVarsProps = { - projectId: string; - environment: "production" | "preview" | "development"; -}; - -export function useEnvVars({ environment, projectId }: UseEnvVarsProps) { - const [editingId, setEditingId] = useState(null); - const [newVar, setNewVar] = useState({ key: "", value: "", isSecret: false }); - const [isAddingNew, setIsAddingNew] = useState(false); - const trpcUtil = trpc.useUtils(); - - const allEnvVars = trpcUtil.deploy.project.envs.getEnvs.getData({ - projectId, - }); - - const envVars = allEnvVars?.[environment] || []; - const [localEnvVars, setLocalEnvVars] = useState([]); - - // Sync server data with local state when it changes - useEffect(() => { - if (envVars.length > 0) { - setLocalEnvVars(envVars); - } - }, [envVars]); - - const addVariable = useCallback(() => { - if (!newVar.key.trim() || !newVar.value.trim()) { - return; - } - - const newEnvVar: EnvVar = { - id: Date.now().toString(), - key: newVar.key.trim(), - value: newVar.value.trim(), - isSecret: newVar.isSecret, - }; - - setLocalEnvVars((prev) => [...prev, newEnvVar]); - setNewVar({ key: "", value: "", isSecret: false }); - setIsAddingNew(false); - - // TODO: Call create mutation when available - }, [newVar]); - - const updateVariable = useCallback((id: string, updates: Partial) => { - setLocalEnvVars((prev) => prev.map((env) => (env.id === id ? { ...env, ...updates } : env))); - setEditingId(null); - - // TODO: Call update mutation when available - }, []); - - const deleteVariable = useCallback((id: string) => { - setLocalEnvVars((prev) => prev.filter((env) => env.id !== id)); - - // TODO: Call delete mutation when available - }, []); - - const startEditing = useCallback((id: string) => { - setEditingId(id); - setIsAddingNew(false); - }, []); - - const cancelEditing = useCallback(() => { - setEditingId(null); - setIsAddingNew(false); - setNewVar({ key: "", value: "", isSecret: false }); - }, []); - - const startAdding = useCallback(() => { - setIsAddingNew(true); - setEditingId(null); - }, []); - - return { - envVars: localEnvVars, - editingId, - newVar, - isAddingNew, - addVariable, - updateVariable, - deleteVariable, - startEditing, - cancelEditing, - startAdding, - setNewVar, - }; -} diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/index.tsx b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/index.tsx index 7b39b23782..d24b314d64 100644 --- a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/index.tsx +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/index.tsx @@ -4,18 +4,24 @@ import { Button } from "@unkey/ui"; import { type ReactNode, useState } from "react"; import { AddEnvVarRow } from "./add-env-var-row"; import { EnvVarRow } from "./env-var-row"; -import { useEnvVars } from "./hooks/use-env-var"; +import { useEnvVarsManager } from "./hooks/use-env-var-manager"; +import type { Environment } from "./types"; type EnvironmentVariablesSectionProps = { icon: ReactNode; title: string; projectId: string; - environment: "production" | "preview" | "development"; + environment: Environment; }; -const ANIMATION_STYLES = { - expand: "transition-all duration-400 ease-in", - slideIn: "transition-all duration-300 ease-out", +const ANIMATION_CONFIG = { + baseDelay: 200, + itemStagger: 50, + contentDelay: 150, +} as const; + +const LAYOUT_CONFIG = { + maxContentHeight: "max-h-64", } as const; export function EnvironmentVariablesSection({ @@ -24,40 +30,36 @@ export function EnvironmentVariablesSection({ environment, title, }: EnvironmentVariablesSectionProps) { - const { - envVars, - editingId, - newVar, - isAddingNew, - addVariable, - updateVariable, - deleteVariable, - startEditing, - cancelEditing, - startAdding, - setNewVar, - } = useEnvVars({ projectId, environment }); + const { envVars, getExistingEnvVar } = useEnvVarsManager({ + projectId, + environment, + }); const [isExpanded, setIsExpanded] = useState(false); + const [isAddingNew, setIsAddingNew] = useState(false); const toggleExpanded = () => { - setIsExpanded(!isExpanded); - if (!isExpanded) { - // Cancel any ongoing editing when collapsing - cancelEditing(); - } + setIsExpanded((prev) => { + const newExpanded = !prev; + if (!newExpanded) { + setIsAddingNew(false); + } + return newExpanded; + }); }; - const showPlusButton = isExpanded && !editingId && !isAddingNew; + const startAdding = () => setIsAddingNew(true); + const cancelAdding = () => setIsAddingNew(false); + + const showPlusButton = isExpanded && !isAddingNew; return (
{/* Header */} -
{icon}
- {title} {envVars.length > 0 ? `(${envVars.length})` : null}{" "} + {title} {envVars.length > 0 && `(${envVars.length})`}
@@ -83,86 +85,90 @@ export function EnvironmentVariablesSection({
- {/* Concave Separator - render with animation */} + + + {/* Expandable Content */}
-
-
-
- - {/* Expandable Content */}
-
+ {envVars.map((envVar, index) => ( +
+ +
+ ))} + + {isAddingNew && ( +
+ +
)} - style={{ - transitionDelay: isExpanded ? "150ms" : "0ms", - }} - > -
- {envVars.length === 0 && !isAddingNew ? ( -
- No environment variables configured -
- ) : ( -
- {envVars.map((envVar, index) => ( -
- startEditing(envVar.id)} - onSave={(updates) => updateVariable(envVar.id, updates)} - onDelete={() => deleteVariable(envVar.id)} - onCancel={cancelEditing} - /> -
- ))} - {isAddingNew && ( -
- -
- )} -
- )} -
+ + {envVars.length === 0 && !isAddingNew && }
); } + +const getItemAnimationProps = (index: number, isExpanded: boolean) => { + const prefersReduced = + typeof window !== "undefined" && + window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches; + const delay = prefersReduced + ? 0 + : ANIMATION_CONFIG.baseDelay + index * ANIMATION_CONFIG.itemStagger; + return { + className: cn( + "transition-all duration-150 ease-out", + isExpanded ? "translate-x-0 opacity-100" : "translate-x-2 opacity-0", + ), + style: { transitionDelay: isExpanded ? `${delay}ms` : "0ms" }, + }; +}; + +// Concave separator component +function ConcaveSeparator({ isExpanded }: { isExpanded: boolean }) { + return ( +
+
+
+
+
+ ); +} + +// Empty state component +function EmptyState() { + return ( +
+ No environment variables configured +
+ ); +} diff --git a/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/types.ts b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/types.ts new file mode 100644 index 0000000000..c8ea587f67 --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/types.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; + +export const EnvVarTypeSchema = z.enum(["env", "secret"]); +export type EnvVarType = z.infer; + +export const envVarSchema = z + .object({ + id: z.string(), + key: z.string(), + value: z.string(), // For 'env': actual value, for 'secret': encrypted blob + type: EnvVarTypeSchema, + decryptedValue: z.string().optional(), // Only populated when user views secret + }) + .superRefine((data, ctx) => { + if (data.type !== "secret" && data.decryptedValue) { + ctx.addIssue({ + code: "custom", + path: ["decryptedValue"], + message: "Only allowed for secrets", + }); + } + }); + +export type EnvVar = z.infer; + +export const EnvVarFormSchema = z.object({ + key: z + .string() + .trim() + .min(1, "Variable name is required") + .regex(/^[A-Za-z][A-Za-z0-9_]*$/, "Use letters, numbers, and underscores; start with a letter"), + value: z.string().min(1, "Variable value is required"), + type: EnvVarTypeSchema, +}); +export type EnvVarFormData = z.infer; + +export const EnvironmentSchema = z.enum(["production", "preview"]); +export type Environment = z.infer; diff --git a/apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts b/apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts index d5fda50ad1..c9798848a5 100644 --- a/apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts +++ b/apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts @@ -1,17 +1,10 @@ +import { envVarSchema } from "@/app/(app)/projects/[projectId]/details/env-variables-section/types"; import { ratelimit, requireUser, requireWorkspace, t, withRatelimit } from "@/lib/trpc/trpc"; import { z } from "zod"; -const envVarSchema = z.object({ - id: z.string(), - key: z.string(), - value: z.string(), - isSecret: z.boolean(), -}); - const environmentVariablesOutputSchema = z.object({ production: z.array(envVarSchema), preview: z.array(envVarSchema), - development: z.array(envVarSchema).optional(), }); export type EnvironmentVariables = z.infer; @@ -23,31 +16,31 @@ export const VARIABLES: EnvironmentVariables = { id: "1", key: "DATABASE_URL", value: "postgresql://user:pass@prod.db.com:5432/app", - isSecret: true, + type: "secret", }, { id: "2", key: "API_KEY", value: "sk_prod_1234567890abcdef", - isSecret: true, + type: "secret", }, { id: "3", key: "NODE_ENV", value: "production", - isSecret: false, + type: "env", }, { id: "4", key: "REDIS_URL", value: "redis://prod.redis.com:6379", - isSecret: true, + type: "secret", }, { id: "5", key: "LOG_LEVEL", value: "info", - isSecret: false, + type: "env", }, ], preview: [ @@ -55,19 +48,19 @@ export const VARIABLES: EnvironmentVariables = { id: "6", key: "DATABASE_URL", value: "postgresql://user:pass@staging.db.com:5432/app", - isSecret: true, + type: "secret", }, { id: "7", key: "API_KEY", value: "sk_test_abcdef1234567890", - isSecret: true, + type: "secret", }, { id: "8", key: "NODE_ENV", value: "development", - isSecret: false, + type: "env", }, ], };