|
1 | | -"use client"; |
| 1 | +import ApiKeysClient from "./Client"; |
2 | 2 |
|
3 | | -import { useEffect, useState } from "react"; |
4 | | -import { apiGet, apiPost, apiDelete } from "@/lib/apiClient"; |
5 | | - |
6 | | -type Item = { prefix: string; label: string; createdAt: number }; |
| 3 | +/** Server wrapper for `ApiKeysClient`. */ |
| 4 | +export const metadata = { |
| 5 | + title: "API keys", |
| 6 | + description: "Create, view and revoke API keys for programmatic access to StableRoute.", |
| 7 | +}; |
7 | 8 |
|
8 | 9 | export default function ApiKeysPage() { |
9 | | - const [items, setItems] = useState<Item[] | null>(null); |
10 | | - const [label, setLabel] = useState(""); |
11 | | - const [created, setCreated] = useState<string | null>(null); |
12 | | - const [error, setError] = useState<string | null>(null); |
13 | | - |
14 | | - const load = () => |
15 | | - apiGet<{ items: Item[] }>("/api/v1/api-keys") |
16 | | - .then((b) => setItems(b.items)) |
17 | | - .catch((e) => setError(e.message)); |
18 | | - useEffect(() => { |
19 | | - load(); |
20 | | - }, []); |
21 | | - |
22 | | - const onCreate = async (e: React.FormEvent) => { |
23 | | - e.preventDefault(); |
24 | | - setError(null); |
25 | | - try { |
26 | | - const r = await apiPost<{ key: string }>("/api/v1/api-keys", { label }); |
27 | | - setCreated(r.key); |
28 | | - setLabel(""); |
29 | | - await load(); |
30 | | - } catch (err) { |
31 | | - setError((err as Error).message); |
32 | | - } |
33 | | - }; |
34 | | - |
35 | | - return ( |
36 | | - <main |
37 | | - id="main-content" |
38 | | - tabIndex={-1} |
39 | | - className="mx-auto flex min-h-[60vh] max-w-3xl flex-col gap-6 p-8 focus:outline-none" |
40 | | - > |
41 | | - <h1 className="text-3xl font-semibold tracking-tight">API keys</h1> |
42 | | - <form onSubmit={onCreate} className="flex gap-2"> |
43 | | - <input |
44 | | - required |
45 | | - maxLength={64} |
46 | | - aria-label="Label" |
47 | | - value={label} |
48 | | - onChange={(e) => setLabel(e.target.value)} |
49 | | - placeholder="Label" |
50 | | - className="flex-1 rounded-md border border-neutral-300 px-3 py-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:border-neutral-700 dark:bg-neutral-900" |
51 | | - /> |
52 | | - <button |
53 | | - type="submit" |
54 | | - className="rounded-full bg-black px-4 py-2 text-sm font-medium text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500" |
55 | | - > |
56 | | - Create |
57 | | - </button> |
58 | | - </form> |
59 | | - {created && ( |
60 | | - <div role="status" className="rounded border border-emerald-300 bg-emerald-50 p-3 text-sm dark:border-emerald-900 dark:bg-emerald-950"> |
61 | | - <p className="font-medium">Copy now — shown only once:</p> |
62 | | - <code className="break-all">{created}</code> |
63 | | - </div> |
64 | | - )} |
65 | | - {error && <p role="alert" className="text-sm text-rose-600">{error}</p>} |
66 | | - {!items && !error && <p>Loading…</p>} |
67 | | - <section aria-live="polite" aria-atomic="true" className="contents"> |
68 | | - {items && items.length === 0 && ( |
69 | | - <p className="text-sm text-neutral-600 dark:text-neutral-400">No API keys yet.</p> |
70 | | - )} |
71 | | - {items && items.length > 0 && ( |
72 | | - <ul className="divide-y divide-neutral-200 dark:divide-neutral-800"> |
73 | | - {items.map((k) => ( |
74 | | - <li key={k.prefix} className="flex items-center justify-between py-3"> |
75 | | - <div> |
76 | | - <p className="text-sm font-medium">{k.label}</p> |
77 | | - <p className="font-mono text-xs text-neutral-500">{k.prefix}…</p> |
78 | | - </div> |
79 | | - <button |
80 | | - type="button" |
81 | | - onClick={() => apiDelete(`/api/v1/api-keys/${k.prefix}`).then(() => load())} |
82 | | - className="rounded border border-neutral-300 px-3 py-1 text-xs hover:border-rose-500 hover:text-rose-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:border-neutral-700" |
83 | | - > |
84 | | - Revoke |
85 | | - </button> |
86 | | - </li> |
87 | | - ))} |
88 | | - </ul> |
89 | | - )} |
90 | | - </section> |
91 | | - </main> |
92 | | - ); |
| 10 | + return <ApiKeysClient />; |
93 | 11 | } |
0 commit comments