page.tsx82 lines · main
1import { revalidatePath } from 'next/cache';
2
3import { apiFetch, apiJson } from '../../../../../../lib/api';
4import { AddEnvForm } from './add-env-form';
5import { DeleteEnvButton } from './delete-env-button';
6
7interface EnvVar {
8 id: string;
9 key: string;
10 lastFour: string;
11 createdAt: string;
12 updatedAt: string;
13}
14
15export const dynamic = 'force-dynamic';
16
17export default async function EnvPage({ params }: { params: Promise<{ id: string }> }) {
18 const { id } = await params;
19 const { env } = await apiJson<{ env: EnvVar[] }>(`/v1/projects/${id}/env`);
20
21 async function upsert(formData: FormData) {
22 'use server';
23 const { id } = await params;
24 const key = String(formData.get('key') ?? '').trim();
25 const value = String(formData.get('value') ?? '');
26 const res = await apiFetch(`/v1/projects/${id}/env`, {
27 method: 'PUT',
28 headers: { 'content-type': 'application/json' },
29 body: JSON.stringify({ key, value }),
30 });
31 if (!res.ok) {
32 const body = await res.text().catch(() => '');
33 throw new Error(body || `upsert failed: ${res.status}`);
34 }
35 revalidatePath(`/dashboard/projects/${id}/env`);
36 }
37
38 async function remove(envVarId: string) {
39 'use server';
40 const { id } = await params;
41 const res = await apiFetch(`/v1/projects/${id}/env/${envVarId}`, { method: 'DELETE' });
42 if (!res.ok) throw new Error(`delete failed: ${res.status}`);
43 revalidatePath(`/dashboard/projects/${id}/env`);
44 }
45
46 return (
47 <div className="flex flex-col gap-6">
48 <header>
49 <h2 className="font-mono text-sm text-[var(--color-text)]">env vars</h2>
50 <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">
51 injected as <code>ctx.env</code> on every function invoke. stored AES-256-GCM encrypted at
52 rest. only the last 4 chars show here.
53 </p>
54 </header>
55
56 <AddEnvForm action={upsert} />
57
58 {env.length === 0 ? (
59 <p className="rounded-md border border-dashed border-[var(--color-border)] p-6 text-center font-mono text-sm text-[var(--color-text-muted)]">
60 no env vars set.
61 </p>
62 ) : (
63 <ul className="flex flex-col gap-2">
64 {env.map((v) => (
65 <li
66 key={v.id}
67 className="flex items-center justify-between rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] px-4 py-3"
68 >
69 <div>
70 <p className="font-mono text-sm">{v.key}</p>
71 <p className="mt-0.5 font-mono text-xs text-[var(--color-text-subtle)]">
72 •••{v.lastFour} · updated {new Date(v.updatedAt).toISOString().slice(0, 10)}
73 </p>
74 </div>
75 <DeleteEnvButton envVarId={v.id} envKey={v.key} onDelete={remove} />
76 </li>
77 ))}
78 </ul>
79 )}
80 </div>
81 );
82}