import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { listProjectVariables, updateProjectVariables } from '@/lib/api-client'; import { Loader2, Plus, Save, Trash2, KeyRound } from 'lucide-react'; type Row = { key: string; value: string; isSecret: boolean }; // SharedVariablesSection edits project-level variables referenced from // service env values as ${{shared.KEY}}. export function SharedVariablesSection({ projectId }: { projectId: string }) { const queryClient = useQueryClient(); const [drafts, setDrafts] = useState(null); const varsQuery = useQuery({ queryKey: ['project-variables', projectId], queryFn: () => listProjectVariables(projectId), }); const saveMutation = useMutation({ mutationFn: (rows: Row[]) => updateProjectVariables( projectId, rows .filter((r) => r.key.trim()) .map((r) => ({ key: r.key.trim(), value: r.value, is_secret: r.isSecret })), ), onSuccess: () => { setDrafts(null); queryClient.invalidateQueries({ queryKey: ['project-variables', projectId] }); }, }); const rows: Row[] = drafts ?? (varsQuery.data ?? []).map((v) => ({ key: v.key, value: v.value, isSecret: v.isSecret })); const dirty = drafts !== null; const updateRow = (i: number, patch: Partial) => { const next = rows.map((r, idx) => (idx === i ? { ...r, ...patch } : r)); setDrafts(next); }; return (

Shared Variables

Reference from any service as{' '} {'${{shared.KEY}}'}. Applied on next deploy/redeploy — not live-updated.

{varsQuery.isLoading ? (
) : rows.length === 0 ? (

No shared variables.

) : (
{rows.map((row, i) => (
updateRow(i, { key: e.target.value })} placeholder="KEY" className="h-9 px-3 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] mono text-xs text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:border-[var(--accent-primary)] focus:ring-1 focus:ring-[var(--accent-primary)] transition-all" /> updateRow(i, { value: e.target.value })} type={row.isSecret ? 'password' : 'text'} placeholder="value" autoComplete="off" className="h-9 px-3 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] mono text-xs text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:border-[var(--accent-primary)] focus:ring-1 focus:ring-[var(--accent-primary)] transition-all" />
))}
)} {saveMutation.isError && (

{saveMutation.error instanceof Error ? saveMutation.error.message : 'Failed to save shared variables'}

)}
); }