@kyro-cms/admin 0.10.5 → 0.10.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -3,9 +3,9 @@ import { apiPost } from "../lib/api";
3
3
  import { useResourceManager } from "../lib/useResourceManager";
4
4
  import { useUIStore, toast } from "../lib/stores";
5
5
  import {
6
- Key, Plus, Trash2, Copy, CheckCircle2, Clock,
6
+ Key, Plus, Trash2, Clock, CheckCircle2,
7
7
  Shield, Zap, AlertTriangle, Info, Terminal,
8
- Code, RefreshCw,
8
+ Code,
9
9
  } from "./ui/icons";
10
10
  import { PageHeader } from "./ui/PageHeader";
11
11
  import { Badge } from "./ui/Badge";
@@ -62,9 +62,7 @@ export function ApiKeysManager() {
62
62
  const [newKeyName, setNewKeyName] = useState("");
63
63
  const [newKeyPermissions, setNewKeyPermissions] = useState<string[]>(["*"]);
64
64
  const [newKeyExpires, setNewKeyExpires] = useState("");
65
- const [copiedId, setCopiedId] = useState<string | null>(null);
66
65
  const [createError, setCreateError] = useState("");
67
- const [rotatingId, setRotatingId] = useState<string | null>(null);
68
66
  const { confirm: kyroConfirm } = useUIStore();
69
67
 
70
68
  const handleCreateKey = async () => {
@@ -81,32 +79,6 @@ export function ApiKeysManager() {
81
79
  } catch { setCreateError("Failed to create API key"); }
82
80
  };
83
81
 
84
- const handleRotateKey = (key: ApiKeyItem) => {
85
- kyroConfirm({
86
- title: "Rotate API Key",
87
- message: `Are you sure you want to regenerate the key "${key.name}"? The current key will stop working immediately and this action cannot be undone.`,
88
- variant: "danger",
89
- onConfirm: async () => {
90
- setRotatingId(key.id);
91
- try {
92
- const rotated = await apiPost<ApiKeyItem>(`/api/keys/${key.id}/rotate`);
93
- setNewKey(rotated);
94
- toast.success("API key rotated");
95
- } catch {
96
- toast.error("Failed to rotate key. Please try again.");
97
- } finally {
98
- setRotatingId(null);
99
- }
100
- },
101
- });
102
- };
103
-
104
- const copyToClipboard = (key: string, id: string) => {
105
- navigator.clipboard.writeText(key);
106
- setCopiedId(id);
107
- setTimeout(() => setCopiedId(null), 2000);
108
- };
109
-
110
82
  return (
111
83
  <div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
112
84
 
@@ -169,13 +141,6 @@ export function ApiKeysManager() {
169
141
  <span> -H <span className="text-green-500">"Authorization: ApiKey kyro_xxx"</span></span>
170
142
  </div>
171
143
  </pre>
172
- <button
173
- type="button"
174
- onClick={() => navigator.clipboard.writeText('curl -X GET https://api.yoursite.com/v1/posts -H "Authorization: ApiKey YOUR_KEY"')}
175
- className="absolute top-3 right-3 p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg opacity-0 group-hover/code:opacity-100 transition-opacity hover:text-[var(--kyro-primary)] shadow-sm"
176
- >
177
- <Copy className="w-3.5 h-3.5" />
178
- </button>
179
144
  </div>
180
145
  </div>
181
146
  </div>
@@ -220,13 +185,6 @@ export function ApiKeysManager() {
220
185
  <span>{"}"});</span>
221
186
  </div>
222
187
  </pre>
223
- <button
224
- type="button"
225
- onClick={() => navigator.clipboard.writeText('await fetch(url, { headers: { "Authorization": "ApiKey YOUR_KEY" } })')}
226
- className="absolute top-3 right-3 p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg opacity-0 group-hover/code:opacity-100 transition-opacity hover:text-[var(--kyro-primary)] shadow-sm"
227
- >
228
- <Copy className="w-3.5 h-3.5" />
229
- </button>
230
188
  </div>
231
189
  </div>
232
190
  </div>
@@ -281,18 +239,8 @@ export function ApiKeysManager() {
281
239
  This is the <span className="text-green-500 font-bold uppercase tracking-tight">only time</span> the full key will be shown. Please store it securely.
282
240
  </p>
283
241
 
284
- <div className="flex flex-col sm:flex-row gap-3">
285
- <div className="flex-1 flex items-center gap-3 p-4 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-2xl font-mono text-sm break-all group relative">
286
- <span className="opacity-80 select-all">{newKey.key}</span>
287
- </div>
288
- <button
289
- type="button"
290
- onClick={() => copyToClipboard(newKey.key!, newKey.id)}
291
- className="kyro-btn kyro-btn-primary flex items-center justify-center gap-2 px-8 py-4 rounded-2xl font-bold hover:scale-[1.02] active:scale-[0.98] transition-all shadow-lg shadow-[var(--kyro-primary)]/20"
292
- >
293
- {copiedId === newKey.id ? <CheckCircle2 className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
294
- <span>{copiedId === newKey.id ? "Copied" : "Copy Key"}</span>
295
- </button>
242
+ <div className="flex-1 flex items-center gap-3 p-4 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-2xl font-mono text-sm break-all group relative">
243
+ <span className="opacity-80 select-all">{newKey.key}</span>
296
244
  </div>
297
245
 
298
246
  <button
@@ -345,87 +293,46 @@ export function ApiKeysManager() {
345
293
  {keys.map((key) => (
346
294
  <div
347
295
  key={key.id}
348
- className="group relative overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-3xl p-6 hover:border-[var(--kyro-primary)]/50 transition-all duration-300"
296
+ className="group relative overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-2xl p-3 hover:border-[var(--kyro-primary)]/50 transition-all duration-300"
349
297
  >
350
- <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 relative z-10">
351
- <div className="flex-1">
352
- <div className="flex items-center gap-3 mb-4">
353
- <div className="p-2.5 bg-[var(--kyro-surface-accent)] rounded-xl group-hover:bg-[var(--kyro-primary)]/10 transition-colors">
354
- <Key className="w-5 h-5 text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-primary)] transition-colors" />
355
- </div>
356
- <div>
357
- <h3 className="text-lg font-bold group-hover:text-[var(--kyro-primary)] transition-colors">{key.name}</h3>
358
- <div className="flex items-center gap-2 mt-0.5">
359
- <span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
360
- <span className="text-[10px] font-bold uppercase tracking-wider text-green-500">Live</span>
361
- </div>
362
- </div>
298
+ <div className="flex items-center justify-between gap-3">
299
+ <div className="flex items-center gap-3 min-w-0 flex-1">
300
+ <div className="p-1.5 bg-[var(--kyro-surface-accent)] rounded-lg group-hover:bg-[var(--kyro-primary)]/10 transition-colors shrink-0">
301
+ <Key className="w-3.5 h-3.5 text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-primary)] transition-colors" />
363
302
  </div>
364
-
365
- <div className="grid sm:grid-cols-3 gap-6 pt-2">
366
- <div className="space-y-1">
367
- <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Key Snippet</span>
368
- <div className="font-mono text-sm opacity-60 tracking-tighter">
369
- {key.keyPrefix}<span className="opacity-20">••••••••••••••••</span>
370
- </div>
303
+ <div className="min-w-0 flex-1">
304
+ <div className="flex items-center gap-2">
305
+ <h3 className="text-sm font-bold truncate group-hover:text-[var(--kyro-primary)] transition-colors">{key.name}</h3>
306
+ <span className="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
371
307
  </div>
372
- <div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
373
- <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Usage Context</span>
374
- <div className="flex flex-wrap gap-1">
308
+ <div className="flex items-center gap-3 text-[11px] text-[var(--kyro-text-secondary)]">
309
+ <span className="font-mono opacity-60">{key.keyPrefix}••••••••••••••••</span>
310
+ <span className="opacity-30">·</span>
311
+ <span className="flex items-center gap-1">
375
312
  {key.permissions?.includes("*") ? (
376
- <Badge variant="warning" className="text-[8px] font-bold uppercase px-1.5">Full Access</Badge>
313
+ "Full Access"
377
314
  ) : (
378
- key.permissions?.slice(0, 2).map((p) => (
379
- <Badge key={p} variant="outline" className="text-[8px] font-bold px-1.5 opacity-60">{p}</Badge>
380
- ))
381
- )}
382
- {key.permissions && key.permissions.length > 2 && (
383
- <span className="text-[8px] font-bold opacity-30">+{key.permissions.length - 2} more</span>
315
+ key.permissions?.slice(0, 2).join(", ")
384
316
  )}
385
- </div>
386
- </div>
387
- <div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
388
- <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Last Activity</span>
389
- <div className="text-[10px] font-bold opacity-60 flex items-center gap-1.5">
317
+ {key.permissions && key.permissions.length > 2 && <span className="opacity-40">+{key.permissions.length - 2}</span>}
318
+ </span>
319
+ <span className="opacity-30">·</span>
320
+ <span className="flex items-center gap-1">
390
321
  <Clock className="w-3 h-3" />
391
- {key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never used"}
392
- </div>
322
+ {key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never"}
323
+ </span>
393
324
  </div>
394
325
  </div>
395
326
  </div>
396
327
 
397
- <div className="flex items-center gap-2 lg:bg-[var(--kyro-surface-accent)]/50 lg:p-2 lg:rounded-2xl lg:opacity-0 group-hover:opacity-100 transition-all duration-300 lg:translate-x-4 group-hover:translate-x-0">
398
- <button
399
- type="button"
400
- onClick={() => copyToClipboard(key.key || `${key.keyPrefix}...`, key.id)}
401
- className="flex-1 lg:flex-none p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all flex items-center justify-center gap-2"
402
- title="Copy key"
403
- >
404
- {copiedId === key.id ? <CheckCircle2 className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
405
- <span className="text-xs font-bold">{copiedId === key.id ? "Copied" : "Copy"}</span>
406
- </button>
407
-
408
- <div className="w-px h-6 bg-[var(--kyro-border)] mx-1 hidden lg:block" />
409
-
410
- <button
411
- type="button"
412
- onClick={() => handleRotateKey(key)}
413
- disabled={rotatingId === key.id}
414
- className="p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all group/rotate"
415
- title="Rotate Key"
416
- >
417
- <RefreshCw className={`w-4 h-4 text-[var(--kyro-text-secondary)] group-hover/rotate:text-[var(--kyro-primary)] ${rotatingId === key.id ? "animate-spin" : ""}`} />
418
- </button>
419
-
420
- <button
421
- type="button"
422
- onClick={() => remove(key.id, "API Key")}
423
- className="p-3 bg-red-500/5 border border-red-500/10 rounded-xl hover:bg-red-500/10 hover:border-red-500/30 transition-all group/delete"
424
- title="Revoke Access"
425
- >
426
- <Trash2 className="w-4 h-4 text-red-500/50 group-hover/delete:text-red-500" />
427
- </button>
428
- </div>
328
+ <button
329
+ type="button"
330
+ onClick={() => remove(key.id, "API Key")}
331
+ className="p-2 bg-red-500/5 border border-red-500/10 rounded-lg hover:bg-red-500/10 hover:border-red-500/30 transition-all opacity-0 group-hover:opacity-100 shrink-0"
332
+ title="Revoke Access"
333
+ >
334
+ <Trash2 className="w-3.5 h-3.5 text-red-500/50 group-hover/delete:text-red-500" />
335
+ </button>
429
336
  </div>
430
337
  </div>
431
338
  ))}