@harbinger-ai/harbinger 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
- import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon } from "./icons.js";
4
+ import { motion } from "framer-motion";
5
+ import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon, SpinnerIcon } from "./icons.js";
5
6
  import { createNewApiKey, getApiKeys, deleteApiKey } from "../actions.js";
6
7
  function timeAgo(ts) {
7
8
  if (!ts) return "Never";
@@ -46,7 +47,7 @@ function CopyButton({ text }) {
46
47
  "button",
47
48
  {
48
49
  onClick: handleCopy,
49
- className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground",
50
+ className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors",
50
51
  children: [
51
52
  copied ? /* @__PURE__ */ jsx(CheckIcon, { size: 14 }) : /* @__PURE__ */ jsx(CopyIcon, { size: 14 }),
52
53
  copied ? "Copied" : "Copy"
@@ -54,11 +55,10 @@ function CopyButton({ text }) {
54
55
  }
55
56
  );
56
57
  }
57
- function Section({ title, description, children }) {
58
- return /* @__PURE__ */ jsxs("div", { className: "pb-8 mb-8 border-b border-border last:border-b-0 last:pb-0 last:mb-0", children: [
59
- /* @__PURE__ */ jsx("h2", { className: "text-base font-medium mb-1", children: title }),
60
- description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: description }),
61
- children
58
+ function SectionHeader({ label, description }) {
59
+ return /* @__PURE__ */ jsxs("div", { className: "pb-2 mb-4", children: [
60
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: label }),
61
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 font-mono", children: description })
62
62
  ] });
63
63
  }
64
64
  function ApiKeySection() {
@@ -123,93 +123,118 @@ function ApiKeySection() {
123
123
  handleCreate();
124
124
  };
125
125
  if (loading) {
126
- return /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-md bg-border/50" });
126
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx("div", { className: "h-16 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" }) });
127
127
  }
128
128
  return /* @__PURE__ */ jsxs("div", { children: [
129
- error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mb-4", children: error }),
130
- newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/30 bg-green-500/5 p-4 mb-4", children: [
131
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
132
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-600 dark:text-green-400", children: "API key created \u2014 copy it now. You won't be able to see it again." }),
129
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-[--destructive]/20 bg-[--destructive]/5 px-3 py-2 mb-4", children: /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-[--destructive]", children: error }) }),
130
+ newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/20 bg-green-500/5 p-4 mb-4", children: [
131
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-2", children: [
132
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
133
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
134
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
135
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
136
+ ] }),
137
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] text-green-500 ml-1", children: "new api key" }),
133
138
  /* @__PURE__ */ jsx(
134
139
  "button",
135
140
  {
136
141
  onClick: () => setNewKey(null),
137
- className: "text-xs text-muted-foreground hover:text-foreground shrink-0",
142
+ className: "text-[10px] font-mono text-muted-foreground hover:text-foreground ml-auto",
138
143
  children: "Dismiss"
139
144
  }
140
145
  )
141
146
  ] }),
147
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-green-500 mb-2", children: "Copy this key now. You won't be able to see it again." }),
142
148
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
143
- /* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-muted px-3 py-2 text-xs font-mono break-all select-all", children: newKey }),
149
+ /* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-black/30 border border-white/[0.04] px-3 py-2 text-[11px] font-mono break-all select-all text-foreground/80", children: newKey }),
144
150
  /* @__PURE__ */ jsx(CopyButton, { text: newKey })
145
151
  ] })
146
152
  ] }),
147
- currentKey ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
148
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
149
- /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(KeyIcon, { size: 16 }) }),
150
- /* @__PURE__ */ jsxs("div", { children: [
151
- /* @__PURE__ */ jsxs("code", { className: "text-sm font-mono", children: [
152
- currentKey.keyPrefix,
153
- "..."
153
+ currentKey ? /* @__PURE__ */ jsx(
154
+ motion.div,
155
+ {
156
+ initial: { opacity: 0, y: 8 },
157
+ animate: { opacity: 1, y: 0 },
158
+ className: "rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors",
159
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-4", children: [
160
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
161
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
162
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
163
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
154
164
  ] }),
155
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
156
- "Created ",
157
- formatDate(currentKey.createdAt),
158
- currentKey.lastUsedAt && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
159
- "\xB7 Last used ",
160
- timeAgo(currentKey.lastUsedAt)
165
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(KeyIcon, { size: 16, className: "text-[--cyan]" }) }),
166
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
167
+ /* @__PURE__ */ jsxs("code", { className: "text-sm font-mono text-foreground", children: [
168
+ currentKey.keyPrefix,
169
+ "..."
170
+ ] }),
171
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-0.5 font-mono", children: [
172
+ "Created ",
173
+ formatDate(currentKey.createdAt),
174
+ currentKey.lastUsedAt && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
175
+ "\\u00b7 Last used ",
176
+ timeAgo(currentKey.lastUsedAt)
177
+ ] })
161
178
  ] })
179
+ ] }),
180
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
181
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--success]" }),
182
+ /* @__PURE__ */ jsxs(
183
+ "button",
184
+ {
185
+ onClick: handleRegenerate,
186
+ disabled: creating,
187
+ className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors disabled:opacity-50 ${confirmRegenerate ? "border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10" : "border-white/[0.06] text-muted-foreground hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan]"}`,
188
+ children: [
189
+ creating ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
190
+ creating ? "Generating..." : confirmRegenerate ? "Confirm" : "Regenerate"
191
+ ]
192
+ }
193
+ ),
194
+ /* @__PURE__ */ jsxs(
195
+ "button",
196
+ {
197
+ onClick: handleDelete,
198
+ className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors ${confirmDelete ? "border-[--destructive]/30 text-[--destructive] hover:bg-[--destructive]/10" : "border-white/[0.06] text-muted-foreground hover:text-[--destructive] hover:border-[--destructive]/20"}`,
199
+ children: [
200
+ /* @__PURE__ */ jsx(TrashIcon, { size: 12 }),
201
+ confirmDelete ? "Confirm" : "Delete"
202
+ ]
203
+ }
204
+ )
162
205
  ] })
163
206
  ] })
164
- ] }),
165
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
166
- /* @__PURE__ */ jsxs(
167
- "button",
168
- {
169
- onClick: handleRegenerate,
170
- disabled: creating,
171
- className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmRegenerate ? "border-yellow-500 text-yellow-600 hover:bg-yellow-500/10" : "border-border text-muted-foreground hover:bg-accent hover:text-foreground"} disabled:opacity-50`,
172
- children: [
173
- /* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
174
- creating ? "Generating..." : confirmRegenerate ? "Confirm regenerate" : "Regenerate"
175
- ]
176
- }
177
- ),
178
- /* @__PURE__ */ jsxs(
179
- "button",
180
- {
181
- onClick: handleDelete,
182
- className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmDelete ? "border-destructive text-destructive hover:bg-destructive/10" : "border-border text-muted-foreground hover:text-destructive hover:border-destructive/50"}`,
183
- children: [
184
- /* @__PURE__ */ jsx(TrashIcon, { size: 12 }),
185
- confirmDelete ? "Confirm delete" : "Delete"
186
- ]
187
- }
188
- )
189
- ] })
190
- ] }) }) : /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed bg-card p-6 flex flex-col items-center text-center", children: [
191
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: "No API key configured" }),
192
- /* @__PURE__ */ jsx(
207
+ }
208
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]", children: [
209
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-[--cyan]/10 p-4 mb-4", children: /* @__PURE__ */ jsx(KeyIcon, { size: 24, className: "text-[--cyan]" }) }),
210
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No API key configured" }),
211
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono mb-4", children: "Generate a key to authenticate external requests to /api endpoints." }),
212
+ /* @__PURE__ */ jsxs(
193
213
  "button",
194
214
  {
195
215
  onClick: handleCreate,
196
216
  disabled: creating,
197
- className: "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 disabled:pointer-events-none",
198
- children: creating ? "Creating..." : "Create API key"
217
+ className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
218
+ children: [
219
+ creating ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(KeyIcon, { size: 12 }),
220
+ creating ? "Creating..." : "Create API Key"
221
+ ]
199
222
  }
200
223
  )
201
224
  ] })
202
225
  ] });
203
226
  }
204
227
  function SettingsSecretsPage() {
205
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
206
- Section,
207
- {
208
- title: "API Key",
209
- description: "Authenticates external requests to /api endpoints. Pass via the x-api-key header.",
210
- children: /* @__PURE__ */ jsx(ApiKeySection, {})
211
- }
212
- ) });
228
+ return /* @__PURE__ */ jsxs("div", { children: [
229
+ /* @__PURE__ */ jsx(
230
+ SectionHeader,
231
+ {
232
+ label: "API Key",
233
+ description: "Authenticates external requests to /api endpoints. Pass via the x-api-key header."
234
+ }
235
+ ),
236
+ /* @__PURE__ */ jsx(ApiKeySection, {})
237
+ ] });
213
238
  }
214
239
  export {
215
240
  SettingsSecretsPage
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
- import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon } from './icons.js';
4
+ import { motion } from 'framer-motion';
5
+ import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon, SpinnerIcon } from './icons.js';
5
6
  import { createNewApiKey, getApiKeys, deleteApiKey } from '../actions.js';
6
7
 
7
8
  function timeAgo(ts) {
@@ -19,7 +20,7 @@ function timeAgo(ts) {
19
20
  }
20
21
 
21
22
  function formatDate(ts) {
22
- if (!ts) return '';
23
+ if (!ts) return '\u2014';
23
24
  return new Date(ts).toLocaleDateString(undefined, {
24
25
  year: 'numeric',
25
26
  month: 'short',
@@ -50,7 +51,7 @@ function CopyButton({ text }) {
50
51
  return (
51
52
  <button
52
53
  onClick={handleCopy}
53
- className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground"
54
+ className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors"
54
55
  >
55
56
  {copied ? <CheckIcon size={14} /> : <CopyIcon size={14} />}
56
57
  {copied ? 'Copied' : 'Copy'}
@@ -58,25 +59,20 @@ function CopyButton({ text }) {
58
59
  );
59
60
  }
60
61
 
61
- // ─────────────────────────────────────────────────────────────────────────────
62
- // Section wrapper — reusable for each secrets section
63
- // ─────────────────────────────────────────────────────────────────────────────
62
+ // ─── Section Header ──────────────────────────────────────────────────────────
64
63
 
65
- function Section({ title, description, children }) {
64
+ function SectionHeader({ label, description }) {
66
65
  return (
67
- <div className="pb-8 mb-8 border-b border-border last:border-b-0 last:pb-0 last:mb-0">
68
- <h2 className="text-base font-medium mb-1">{title}</h2>
66
+ <div className="pb-2 mb-4">
67
+ <span className="font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider">{label}</span>
69
68
  {description && (
70
- <p className="text-sm text-muted-foreground mb-4">{description}</p>
69
+ <p className="text-xs text-muted-foreground mt-1 font-mono">{description}</p>
71
70
  )}
72
- {children}
73
71
  </div>
74
72
  );
75
73
  }
76
74
 
77
- // ─────────────────────────────────────────────────────────────────────────────
78
- // API Key section
79
- // ─────────────────────────────────────────────────────────────────────────────
75
+ // ─── API Key section ─────────────────────────────────────────────────────────
80
76
 
81
77
  function ApiKeySection() {
82
78
  const [currentKey, setCurrentKey] = useState(null);
@@ -92,7 +88,6 @@ function ApiKeySection() {
92
88
  const result = await getApiKeys();
93
89
  setCurrentKey(result);
94
90
  } catch {
95
- // ignore
96
91
  } finally {
97
92
  setLoading(false);
98
93
  }
@@ -133,9 +128,7 @@ function ApiKeySection() {
133
128
  setCurrentKey(null);
134
129
  setNewKey(null);
135
130
  setConfirmDelete(false);
136
- } catch {
137
- // ignore
138
- }
131
+ } catch {}
139
132
  };
140
133
 
141
134
  const handleRegenerate = () => {
@@ -148,31 +141,43 @@ function ApiKeySection() {
148
141
  };
149
142
 
150
143
  if (loading) {
151
- return <div className="h-14 animate-pulse rounded-md bg-border/50" />;
144
+ return (
145
+ <div className="flex flex-col gap-3">
146
+ <div className="h-16 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" />
147
+ </div>
148
+ );
152
149
  }
153
150
 
154
151
  return (
155
152
  <div>
156
153
  {error && (
157
- <p className="text-sm text-destructive mb-4">{error}</p>
154
+ <div className="rounded-md border border-[--destructive]/20 bg-[--destructive]/5 px-3 py-2 mb-4">
155
+ <p className="text-xs font-mono text-[--destructive]">{error}</p>
156
+ </div>
158
157
  )}
159
158
 
160
159
  {/* New key banner */}
161
160
  {newKey && (
162
- <div className="rounded-lg border border-green-500/30 bg-green-500/5 p-4 mb-4">
163
- <div className="flex items-start justify-between gap-3 mb-2">
164
- <p className="text-sm font-medium text-green-600 dark:text-green-400">
165
- API key created copy it now. You won't be able to see it again.
166
- </p>
161
+ <div className="rounded-lg border border-green-500/20 bg-green-500/5 p-4 mb-4">
162
+ <div className="flex items-center gap-1.5 mb-2">
163
+ <div className="flex items-center gap-1">
164
+ <div className="w-2 h-2 rounded-full bg-[#ff5f57]" />
165
+ <div className="w-2 h-2 rounded-full bg-[#febc2e]" />
166
+ <div className="w-2 h-2 rounded-full bg-[#28c840]" />
167
+ </div>
168
+ <span className="font-mono text-[9px] text-green-500 ml-1">new api key</span>
167
169
  <button
168
170
  onClick={() => setNewKey(null)}
169
- className="text-xs text-muted-foreground hover:text-foreground shrink-0"
171
+ className="text-[10px] font-mono text-muted-foreground hover:text-foreground ml-auto"
170
172
  >
171
173
  Dismiss
172
174
  </button>
173
175
  </div>
176
+ <p className="text-xs font-mono text-green-500 mb-2">
177
+ Copy this key now. You won't be able to see it again.
178
+ </p>
174
179
  <div className="flex items-center gap-2">
175
- <code className="flex-1 rounded-md bg-muted px-3 py-2 text-xs font-mono break-all select-all">
180
+ <code className="flex-1 rounded-md bg-black/30 border border-white/[0.04] px-3 py-2 text-[11px] font-mono break-all select-all text-foreground/80">
176
181
  {newKey}
177
182
  </code>
178
183
  <CopyButton text={newKey} />
@@ -181,58 +186,73 @@ function ApiKeySection() {
181
186
  )}
182
187
 
183
188
  {currentKey ? (
184
- <div className="rounded-lg border bg-card p-4">
185
- <div className="flex items-center justify-between">
186
- <div className="flex items-center gap-3">
187
- <div className="shrink-0 rounded-md bg-muted p-2">
188
- <KeyIcon size={16} />
189
- </div>
190
- <div>
191
- <code className="text-sm font-mono">{currentKey.keyPrefix}...</code>
192
- <p className="text-xs text-muted-foreground mt-0.5">
193
- Created {formatDate(currentKey.createdAt)}
194
- {currentKey.lastUsedAt && (
195
- <span className="ml-2">· Last used {timeAgo(currentKey.lastUsedAt)}</span>
196
- )}
197
- </p>
198
- </div>
189
+ <motion.div
190
+ initial={{ opacity: 0, y: 8 }}
191
+ animate={{ opacity: 1, y: 0 }}
192
+ className="rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors"
193
+ >
194
+ <div className="flex items-center gap-3 p-4">
195
+ <div className="flex items-center gap-1 shrink-0">
196
+ <div className="w-2 h-2 rounded-full bg-[#ff5f57]" />
197
+ <div className="w-2 h-2 rounded-full bg-[#febc2e]" />
198
+ <div className="w-2 h-2 rounded-full bg-[#28c840]" />
199
+ </div>
200
+ <div className="shrink-0 rounded-md bg-[--cyan]/10 p-2">
201
+ <KeyIcon size={16} className="text-[--cyan]" />
199
202
  </div>
200
- <div className="flex items-center gap-2">
203
+ <div className="flex-1 min-w-0">
204
+ <code className="text-sm font-mono text-foreground">{currentKey.keyPrefix}...</code>
205
+ <p className="text-[10px] text-muted-foreground mt-0.5 font-mono">
206
+ Created {formatDate(currentKey.createdAt)}
207
+ {currentKey.lastUsedAt && (
208
+ <span className="ml-2">\u00b7 Last used {timeAgo(currentKey.lastUsedAt)}</span>
209
+ )}
210
+ </p>
211
+ </div>
212
+ <div className="flex items-center gap-2 shrink-0">
213
+ <div className="w-2 h-2 rounded-full bg-[--success]" />
201
214
  <button
202
215
  onClick={handleRegenerate}
203
216
  disabled={creating}
204
- className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
217
+ className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors disabled:opacity-50 ${
205
218
  confirmRegenerate
206
- ? 'border-yellow-500 text-yellow-600 hover:bg-yellow-500/10'
207
- : 'border-border text-muted-foreground hover:bg-accent hover:text-foreground'
208
- } disabled:opacity-50`}
219
+ ? 'border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10'
220
+ : 'border-white/[0.06] text-muted-foreground hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan]'
221
+ }`}
209
222
  >
210
- <RefreshIcon size={12} />
211
- {creating ? 'Generating...' : confirmRegenerate ? 'Confirm regenerate' : 'Regenerate'}
223
+ {creating ? <SpinnerIcon size={12} /> : <RefreshIcon size={12} />}
224
+ {creating ? 'Generating...' : confirmRegenerate ? 'Confirm' : 'Regenerate'}
212
225
  </button>
213
226
  <button
214
227
  onClick={handleDelete}
215
- className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
228
+ className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors ${
216
229
  confirmDelete
217
- ? 'border-destructive text-destructive hover:bg-destructive/10'
218
- : 'border-border text-muted-foreground hover:text-destructive hover:border-destructive/50'
230
+ ? 'border-[--destructive]/30 text-[--destructive] hover:bg-[--destructive]/10'
231
+ : 'border-white/[0.06] text-muted-foreground hover:text-[--destructive] hover:border-[--destructive]/20'
219
232
  }`}
220
233
  >
221
234
  <TrashIcon size={12} />
222
- {confirmDelete ? 'Confirm delete' : 'Delete'}
235
+ {confirmDelete ? 'Confirm' : 'Delete'}
223
236
  </button>
224
237
  </div>
225
238
  </div>
226
- </div>
239
+ </motion.div>
227
240
  ) : (
228
- <div className="rounded-lg border border-dashed bg-card p-6 flex flex-col items-center text-center">
229
- <p className="text-sm text-muted-foreground mb-3">No API key configured</p>
241
+ <div className="flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]">
242
+ <div className="rounded-full bg-[--cyan]/10 p-4 mb-4">
243
+ <KeyIcon size={24} className="text-[--cyan]" />
244
+ </div>
245
+ <p className="text-sm font-mono font-medium mb-1">No API key configured</p>
246
+ <p className="text-[11px] text-muted-foreground font-mono mb-4">
247
+ Generate a key to authenticate external requests to /api endpoints.
248
+ </p>
230
249
  <button
231
250
  onClick={handleCreate}
232
251
  disabled={creating}
233
- className="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 disabled:pointer-events-none"
252
+ className="inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50"
234
253
  >
235
- {creating ? 'Creating...' : 'Create API key'}
254
+ {creating ? <SpinnerIcon size={12} /> : <KeyIcon size={12} />}
255
+ {creating ? 'Creating...' : 'Create API Key'}
236
256
  </button>
237
257
  </div>
238
258
  )}
@@ -240,25 +260,16 @@ function ApiKeySection() {
240
260
  );
241
261
  }
242
262
 
243
- // ─────────────────────────────────────────────────────────────────────────────
244
- // Main page
245
- // ─────────────────────────────────────────────────────────────────────────────
263
+ // ─── Main Page ───────────────────────────────────────────────────────────────
246
264
 
247
265
  export function SettingsSecretsPage() {
248
266
  return (
249
267
  <div>
250
- <Section
251
- title="API Key"
268
+ <SectionHeader
269
+ label="API Key"
252
270
  description="Authenticates external requests to /api endpoints. Pass via the x-api-key header."
253
- >
254
- <ApiKeySection />
255
- </Section>
256
-
257
- {/* Future sections go here, e.g.:
258
- <Section title="GitHub Token" description="...">
259
- <GitHubTokenSection />
260
- </Section>
261
- */}
271
+ />
272
+ <ApiKeySection />
262
273
  </div>
263
274
  );
264
275
  }