@kyro-cms/admin 0.12.20 → 0.12.22

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.
Files changed (56) hide show
  1. package/dist/index.cjs +24 -24
  2. package/dist/index.d.cts +2 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +19 -19
  5. package/package.json +6 -6
  6. package/src/components/ApiKeysManager.tsx +177 -227
  7. package/src/components/AuditLogsPage.tsx +7 -7
  8. package/src/components/AuthorDashboard.tsx +235 -0
  9. package/src/components/AutoForm.tsx +14 -4
  10. package/src/components/CustomerDashboard.tsx +213 -0
  11. package/src/components/Dashboard.tsx +1 -1
  12. package/src/components/DashboardMetrics.tsx +74 -9
  13. package/src/components/DashboardQuickSections.tsx +237 -0
  14. package/src/components/ListView.tsx +3 -3
  15. package/src/components/MediaGallery.tsx +2 -2
  16. package/src/components/PluginsManager.tsx +1 -1
  17. package/src/components/SessionsManager.tsx +15 -15
  18. package/src/components/Sidebar.astro +564 -116
  19. package/src/components/UserManagement.tsx +43 -9
  20. package/src/components/UserMenu.tsx +123 -61
  21. package/src/components/autoform/AutoFormEditView.tsx +4 -4
  22. package/src/components/autoform/AutoFormHeader.tsx +2 -2
  23. package/src/components/ui/CommandPalette.tsx +1 -1
  24. package/src/components/ui/Dropdown.tsx +71 -16
  25. package/src/components/ui/PageHeader.tsx +1 -1
  26. package/src/components/ui/Pagination.tsx +1 -1
  27. package/src/components/ui/Toast.tsx +3 -2
  28. package/src/components/ui/Toaster.tsx +1 -1
  29. package/src/components/users/UserDetail.tsx +74 -29
  30. package/src/components/users/UserForm.tsx +1 -1
  31. package/src/components/users/UsersList.tsx +2 -2
  32. package/src/integration.ts +4 -0
  33. package/src/layouts/AdminLayout.astro +26 -34
  34. package/src/layouts/AuthLayout.astro +18 -9
  35. package/src/lib/auth-server.ts +99 -0
  36. package/src/pages/403.astro +54 -35
  37. package/src/pages/[collection]/[id].astro +8 -0
  38. package/src/pages/[collection]/index.astro +7 -0
  39. package/src/pages/audit/index.astro +4 -1
  40. package/src/pages/auth/check-email.astro +146 -0
  41. package/src/pages/auth/forgot-password.astro +95 -0
  42. package/src/pages/auth/login.astro +29 -12
  43. package/src/pages/auth/register.astro +38 -12
  44. package/src/pages/auth/reset-password.astro +125 -0
  45. package/src/pages/auth/verify-email.astro +87 -0
  46. package/src/pages/index.astro +220 -121
  47. package/src/pages/keys.astro +3 -1
  48. package/src/pages/media.astro +3 -1
  49. package/src/pages/preview/[collection]/[id].astro +23 -73
  50. package/src/pages/roles/index.astro +134 -101
  51. package/src/pages/settings/[slug].astro +43 -3
  52. package/src/pages/users/[id].astro +5 -1
  53. package/src/pages/users/index.astro +4 -0
  54. package/src/pages/users/new.astro +4 -0
  55. package/src/pages/webhooks.astro +3 -1
  56. package/src/routes.ts +16 -0
@@ -1,11 +1,8 @@
1
1
  import React, { useState } from "react";
2
- import { apiPost } from "../lib/api";
3
2
  import { useResourceManager } from "../lib/useResourceManager";
4
- import { useUIStore, toast } from "../lib/stores";
5
3
  import {
6
4
  Key, Plus, Trash2, Clock, CheckCircle2,
7
- Shield, Zap, AlertTriangle, Info, Terminal,
8
- Code,
5
+ Shield, Info, Terminal, Code, AlertTriangle,
9
6
  } from "./ui/icons";
10
7
  import { PageHeader } from "./ui/PageHeader";
11
8
  import { Badge } from "./ui/Badge";
@@ -23,11 +20,9 @@ interface ApiKeyItem {
23
20
  expiresAt?: string;
24
21
  }
25
22
 
26
-
27
-
28
23
  export function ApiKeysManager() {
29
- const { t } = useTranslation();
30
- const { items: keys, loading, create, update, remove, isCreateModalOpen, setIsCreateModalOpen } =
24
+ const { t } = useTranslation();
25
+ const { items: keys, loading, create, remove, isCreateModalOpen, setIsCreateModalOpen } =
31
26
  useResourceManager<ApiKeyItem>(
32
27
  React.useMemo(() => ({
33
28
  endpoint: "/api/keys",
@@ -44,7 +39,6 @@ export function ApiKeysManager() {
44
39
  const [newKeyPermissions, setNewKeyPermissions] = useState<string[]>(["*"]);
45
40
  const [newKeyExpires, setNewKeyExpires] = useState("");
46
41
  const [createError, setCreateError] = useState("");
47
- const { confirm: kyroConfirm } = useUIStore();
48
42
 
49
43
  const handleCreateKey = async () => {
50
44
  if (!newKeyName.trim()) { setCreateError("Name is required"); return; }
@@ -61,18 +55,18 @@ export function ApiKeysManager() {
61
55
  };
62
56
 
63
57
  return (
64
- <div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
65
-
58
+ <div className="w-full space-y-6 pb-24">
59
+ {/* Header */}
66
60
  <PageHeader
67
61
  title={t("tooltips.apiKeys", { defaultValue: "API Keys" })}
68
- description="Programmatic tokens for secure infrastructure integration."
62
+ description="Programmatic tokens for secure system integration and REST API access."
69
63
  icon={Key}
70
64
  actions={
71
65
  <div className="flex items-center gap-3">
72
66
  <button
73
67
  type="button"
74
68
  onClick={() => setShowHelpModal(true)}
75
- className="px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] rounded-xl font-bold text-xs border border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)] transition-all flex items-center gap-2"
69
+ className="px-4 py-2.5 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] rounded-xl font-bold text-xs border border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)] hover:text-[var(--kyro-text-primary)] transition-colors flex items-center gap-2"
76
70
  >
77
71
  <Info className="w-4 h-4" />
78
72
  <span>Integration Guide</span>
@@ -80,7 +74,7 @@ export function ApiKeysManager() {
80
74
  <button
81
75
  type="button"
82
76
  onClick={() => { setNewKeyName(""); setCreateError(""); setIsCreateModalOpen(true); }}
83
- className="px-6 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold text-sm shadow-xl shadow-[var(--kyro-primary)]/10 hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center gap-2"
77
+ className="px-5 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold text-xs hover:opacity-95 transition-opacity flex items-center gap-2"
84
78
  >
85
79
  <Plus className="w-4 h-4" />
86
80
  <span>Create Access Key</span>
@@ -89,275 +83,242 @@ export function ApiKeysManager() {
89
83
  }
90
84
  />
91
85
 
92
- <div className="grid md:grid-cols-2 gap-6">
93
- {/* Terminal Card 1 */}
94
- <div className="group relative overflow-hidden bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-[2rem] p-1 hover:border-[var(--kyro-primary)]/30 transition-all duration-500">
95
- <div className="p-6 bg-[var(--kyro-surface)] rounded-[1.8rem] h-full">
96
- <div className="flex items-center justify-between mb-6">
97
- <div className="flex items-center gap-3">
98
- <div className="p-2 bg-blue-500/10 rounded-lg">
99
- <Terminal className="w-4 h-4 text-blue-500" />
100
- </div>
101
- <h3 className="text-xs font-bold uppercase tracking-widest opacity-60">Shell / CURL</h3>
102
- </div>
103
- <div className="flex gap-1.5">
104
- <div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
105
- <div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
106
- <div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
86
+ {/* Code Snippets */}
87
+ <div className="grid md:grid-cols-2 gap-4">
88
+ {/* cURL Example */}
89
+ <div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
90
+ <div className="flex items-center justify-between mb-4">
91
+ <div className="flex items-center gap-2.5">
92
+ <div className="p-1.5 bg-blue-500/10 rounded-lg">
93
+ <Terminal className="w-4 h-4 text-blue-500" />
107
94
  </div>
95
+ <span className="text-xs font-bold text-[var(--kyro-text-primary)] tracking-wide">Shell / cURL</span>
108
96
  </div>
97
+ <span className="text-[10px] font-mono text-[var(--kyro-text-secondary)] opacity-60">HTTP REST</span>
98
+ </div>
109
99
 
110
- <div className="relative group/code">
111
- <pre className="p-5 bg-[var(--kyro-bg)] rounded-2xl border border-[var(--kyro-border)] font-mono text-[10px] leading-relaxed overflow-x-auto">
112
- <div className="flex gap-4">
113
- <span className="opacity-20 select-none w-4">1</span>
114
- <span><span className="text-[var(--kyro-primary)]">curl</span> -X GET \</span>
115
- </div>
116
- <div className="flex gap-4">
117
- <span className="opacity-20 select-none w-4">2</span>
118
- <span> https://api.yoursite.com/v1/posts \</span>
119
- </div>
120
- <div className="flex gap-4">
121
- <span className="opacity-20 select-none w-4">3</span>
122
- <span> -H <span className="text-green-500">"Authorization: ApiKey kyro_xxx"</span></span>
123
- </div>
124
- </pre>
100
+ <pre className="p-4 bg-[var(--kyro-bg)] rounded-lg border border-[var(--kyro-border)] font-mono text-xs leading-relaxed overflow-x-auto text-[var(--kyro-text-primary)]">
101
+ <div className="flex gap-3">
102
+ <span className="opacity-30 select-none">1</span>
103
+ <span><span className="text-blue-500 font-bold">curl</span> -X GET \</span>
125
104
  </div>
126
- </div>
105
+ <div className="flex gap-3">
106
+ <span className="opacity-30 select-none">2</span>
107
+ <span> https://api.yoursite.com/v1/posts \</span>
108
+ </div>
109
+ <div className="flex gap-3">
110
+ <span className="opacity-30 select-none">3</span>
111
+ <span> -H <span className="text-emerald-500 font-medium">"Authorization: ApiKey kyro_xxx"</span></span>
112
+ </div>
113
+ </pre>
127
114
  </div>
128
115
 
129
- {/* Terminal Card 2 */}
130
- <div className="group relative overflow-hidden bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-[2rem] p-1 hover:border-[var(--kyro-primary)]/30 transition-all duration-500">
131
- <div className="p-6 bg-[var(--kyro-surface)] rounded-[1.8rem] h-full">
132
- <div className="flex items-center justify-between mb-6">
133
- <div className="flex items-center gap-3">
134
- <div className="p-2 bg-indigo-500/10 rounded-lg">
135
- <Code className="w-4 h-4 text-indigo-500" />
136
- </div>
137
- <h3 className="text-xs font-bold uppercase tracking-widest opacity-60">JavaScript SDK</h3>
138
- </div>
139
- <div className="flex gap-1.5">
140
- <div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
141
- <div className="w-2.5 h-2.5 rounded-full bg-amber-500/20" />
142
- <div className="w-2.5 h-2.5 rounded-full bg-green-500/20" />
116
+ {/* JS SDK Example */}
117
+ <div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
118
+ <div className="flex items-center justify-between mb-4">
119
+ <div className="flex items-center gap-2.5">
120
+ <div className="p-1.5 bg-indigo-500/10 rounded-lg">
121
+ <Code className="w-4 h-4 text-indigo-500" />
143
122
  </div>
123
+ <span className="text-xs font-bold text-[var(--kyro-text-primary)] tracking-wide">JavaScript / Node</span>
144
124
  </div>
125
+ <span className="text-[10px] font-mono text-[var(--kyro-text-secondary)] opacity-60">Fetch API</span>
126
+ </div>
145
127
 
146
- <div className="relative group/code">
147
- <pre className="p-5 bg-[var(--kyro-bg)] rounded-2xl border border-[var(--kyro-border)] font-mono text-[10px] leading-relaxed overflow-x-auto">
148
- <div className="flex gap-4">
149
- <span className="opacity-20 select-none w-4">1</span>
150
- <span><span className="text-indigo-400">const</span> res = <span className="text-indigo-400">await</span> <span className="text-blue-400">fetch</span>(url, {"{"}</span>
151
- </div>
152
- <div className="flex gap-4">
153
- <span className="opacity-20 select-none w-4">2</span>
154
- <span> headers: {"{"} </span>
155
- </div>
156
- <div className="flex gap-4">
157
- <span className="opacity-20 select-none w-4">3</span>
158
- <span> <span className="text-green-500">'Authorization'</span>: <span className="text-green-500">'ApiKey xxx'</span></span>
159
- </div>
160
- <div className="flex gap-4">
161
- <span className="opacity-20 select-none w-4">4</span>
162
- <span> {"}"}</span>
163
- </div>
164
- <div className="flex gap-4">
165
- <span className="opacity-20 select-none w-4">5</span>
166
- <span>{"}"});</span>
167
- </div>
168
- </pre>
128
+ <pre className="p-4 bg-[var(--kyro-bg)] rounded-lg border border-[var(--kyro-border)] font-mono text-xs leading-relaxed overflow-x-auto text-[var(--kyro-text-primary)]">
129
+ <div className="flex gap-3">
130
+ <span className="opacity-30 select-none">1</span>
131
+ <span><span className="text-indigo-400 font-medium">const</span> res = <span className="text-indigo-400 font-medium">await</span> <span className="text-blue-400">fetch</span>(url, {"{"}</span>
169
132
  </div>
170
- </div>
133
+ <div className="flex gap-3">
134
+ <span className="opacity-30 select-none">2</span>
135
+ <span> headers: {"{"} <span className="text-emerald-500 font-medium">'Authorization'</span>: <span className="text-emerald-500 font-medium">'ApiKey xxx'</span> {"}"}</span>
136
+ </div>
137
+ <div className="flex gap-3">
138
+ <span className="opacity-30 select-none">3</span>
139
+ <span>{"}"});</span>
140
+ </div>
141
+ </pre>
171
142
  </div>
172
143
  </div>
173
144
 
174
- <div className="flex surface-tile flex-col gap-8">
175
- {/* Warning/Best Practices Banner */}
176
- <div className="relative overflow-hidden p-6 rounded-3xl border border-[var(--kyro-border)] bg-gradient-to-br from-[var(--kyro-surface-accent)] to-transparent group">
177
- <div className="absolute top-0 right-0 p-8 opacity-5 group-hover:opacity-10 transition-opacity">
178
- <Shield className="w-32 h-32 rotate-12" />
179
- </div>
180
-
181
- <div className="flex items-start gap-4 relative z-10">
182
- <div className="p-3 bg-amber-500/10 rounded-2xl">
183
- <Shield className="w-6 h-6 text-amber-500" />
145
+ {/* Main Container */}
146
+ <div className="space-y-6">
147
+ {/* Security Notice */}
148
+ <div className="surface-tile p-5 rounded-2xl border border-[var(--kyro-border)]">
149
+ <div className="flex items-start gap-4">
150
+ <div className="p-2.5 bg-amber-500/10 rounded-xl shrink-0">
151
+ <Shield className="w-5 h-5 text-amber-500" />
184
152
  </div>
185
- <div>
186
- <h4 className="text-lg font-bold text-[var(--kyro-text-primary)] mb-1">Security Best Practices</h4>
187
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-4 max-w-2xl">
188
- API keys grant full access to your resources. Treat them with the same level of security as your passwords.
189
- </p>
190
- <div className="grid sm:grid-cols-2 gap-x-8 gap-y-2">
191
- {[
192
- "Never commit keys to version control",
193
- "Rotate keys every 90 days",
194
- "Use specific permissions (least privilege)",
195
- "Store keys in secure environment variables"
196
- ].map((text, i) => (
197
- <div key={i} className="flex items-center gap-2 text-xs font-medium text-[var(--kyro-text-secondary)]">
198
- <div className="w-1.5 h-1.5 rounded-full bg-amber-500/50" />
199
- {text}
200
- </div>
201
- ))}
153
+ <div className="space-y-3">
154
+ <div>
155
+ <h4 className="text-sm font-bold text-[var(--kyro-text-primary)]">Security Best Practices</h4>
156
+ <p className="text-xs text-[var(--kyro-text-secondary)] mt-0.5">
157
+ API keys grant direct access to your CMS data. Treat them securely as secrets.
158
+ </p>
159
+ </div>
160
+ <div className="grid sm:grid-cols-2 gap-2 text-xs text-[var(--kyro-text-secondary)] font-medium">
161
+ <div className="flex items-center gap-2">
162
+ <div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
163
+ <span>Never commit keys to version control repositories</span>
164
+ </div>
165
+ <div className="flex items-center gap-2">
166
+ <div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
167
+ <span>Rotate key credentials every 90 days</span>
168
+ </div>
169
+ <div className="flex items-center gap-2">
170
+ <div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
171
+ <span>Use environment variables for production keys</span>
172
+ </div>
173
+ <div className="flex items-center gap-2">
174
+ <div className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
175
+ <span>Revoke unused or compromised keys immediately</span>
176
+ </div>
202
177
  </div>
203
178
  </div>
204
179
  </div>
205
180
  </div>
206
181
 
207
- {/* Success Banner for New Keys */}
182
+ {/* Newly Created Key Alert */}
208
183
  {newKey && (
209
- <div className="relative overflow-hidden p-8 rounded-3xl border border-green-500/30 bg-green-500/5 backdrop-blur-xl animate-in fade-in slide-in-from-top-4 duration-500">
210
- <div className="flex items-start gap-6">
211
- <div className="p-4 bg-green-500/10 rounded-2xl">
212
- <CheckCircle2 className="w-8 h-8 text-green-500" />
184
+ <div className="p-6 rounded-2xl border border-emerald-500/30 bg-emerald-500/10 text-[var(--kyro-text-primary)]">
185
+ <div className="flex items-start gap-4">
186
+ <div className="p-2.5 bg-emerald-500/20 rounded-xl shrink-0">
187
+ <CheckCircle2 className="w-6 h-6 text-emerald-500" />
213
188
  </div>
214
- <div className="flex-1">
215
- <div className="flex items-center justify-between mb-2">
216
- <h3 className="text-xl font-bold text-green-500">API Key Generated</h3>
189
+ <div className="flex-1 space-y-3">
190
+ <div className="flex items-center justify-between">
191
+ <h3 className="text-base font-bold text-emerald-500">API Key Generated Successfully</h3>
217
192
  <Badge variant="success" className="text-[10px] font-bold">New</Badge>
218
193
  </div>
219
- <p className="text-sm text-[var(--kyro-text-secondary)] mb-6">
220
- 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.
194
+ <p className="text-xs text-[var(--kyro-text-secondary)]">
195
+ Copy this key now. It will <strong className="text-emerald-500">never be displayed again</strong>.
221
196
  </p>
222
-
223
- <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">
224
- <span className="opacity-80 select-all">{newKey.key}</span>
197
+ <div className="p-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl font-mono text-xs break-all select-all font-semibold">
198
+ {newKey.key}
225
199
  </div>
226
-
227
200
  <button
228
201
  type="button"
229
202
  onClick={() => setNewKey(null)}
230
- className="mt-6 text-xs font-bold uppercase tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors"
203
+ className="text-xs font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors"
231
204
  >
232
- Dismiss Message
205
+ Dismiss
233
206
  </button>
234
207
  </div>
235
208
  </div>
236
209
  </div>
237
210
  )}
238
211
 
239
- <section className="space-y-6">
240
- <div className="flex items-center justify-between px-2">
212
+ {/* Active Keys Section */}
213
+ <div className="surface-tile p-6 rounded-2xl border border-[var(--kyro-border)] space-y-4">
214
+ <div className="flex items-center justify-between">
241
215
  <div className="flex items-center gap-2">
242
- <div className="w-1 h-4 bg-[var(--kyro-primary)] rounded-full" />
243
- <h2 className="text-sm font-medium tracking-[0.2em] opacity-40">Active Credentials</h2>
244
- </div>
245
- <div className="text-[10px] font-bold opacity-40">
246
- {keys.length} KEY{keys.length !== 1 && "S"}
216
+ <h2 className="text-sm font-bold text-[var(--kyro-text-primary)] tracking-wide">Active Credentials</h2>
217
+ <span className="px-2 py-0.5 text-[10px] font-bold rounded-md bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)]">
218
+ {keys.length}
219
+ </span>
247
220
  </div>
248
221
  </div>
249
222
 
250
223
  {loading ? (
251
- <div className="flex items-center justify-center p-20 surface-tile rounded-3xl opacity-50 italic">
252
- Synchronizing with vault...
224
+ <div className="p-8 text-center text-xs font-medium text-[var(--kyro-text-secondary)]">
225
+ Loading API keys...
253
226
  </div>
254
227
  ) : keys.length === 0 ? (
255
- <div className="p-16 text-center rounded-[3rem] border-2 border-dashed border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/30">
256
- <div className="w-20 h-20 mx-auto mb-6 bg-gradient-to-tr from-[var(--kyro-surface)] to-[var(--kyro-surface-accent)] rounded-3xl flex items-center justify-center shadow-xl border border-[var(--kyro-border)]">
257
- <Key className="w-10 h-10 text-[var(--kyro-primary)]" />
228
+ <div className="p-10 text-center rounded-xl border border-dashed border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/30 space-y-3">
229
+ <Key className="w-8 h-8 text-[var(--kyro-text-secondary)] mx-auto opacity-50" />
230
+ <div>
231
+ <h3 className="text-sm font-bold text-[var(--kyro-text-primary)]">No API Keys Generated</h3>
232
+ <p className="text-xs text-[var(--kyro-text-secondary)] mt-1 max-w-sm mx-auto">
233
+ Create an API key to allow external services to connect to your Kyro CMS instance safely.
234
+ </p>
258
235
  </div>
259
- <h3 className="text-2xl font-bold mb-3">Initialize Your Access</h3>
260
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-8 max-w-sm mx-auto">
261
- No API credentials found. Generate a key to begin interacting with the Kyro CMS programmatic interface.
262
- </p>
263
236
  <button
264
237
  type="button"
265
238
  onClick={() => setIsCreateModalOpen(true)}
266
- className="kyro-btn kyro-btn-primary inline-flex items-center gap-3 px-8 py-4 rounded-2xl font-bold hover:scale-[1.05] transition-all shadow-xl shadow-[var(--kyro-primary)]/10"
239
+ className="mt-2 inline-flex items-center gap-2 px-4 py-2 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] text-xs font-bold rounded-xl hover:opacity-95 transition-opacity"
267
240
  >
268
- <Plus className="w-5 h-5" />
269
- Generate API Key
241
+ <Plus className="w-4 h-4" />
242
+ <span>Create API Key</span>
270
243
  </button>
271
244
  </div>
272
245
  ) : (
273
- <div className="grid gap-4">
246
+ <div className="divide-y divide-[var(--kyro-border)]">
274
247
  {keys.map((key) => (
275
- <div
276
- key={key.id}
277
- 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"
278
- >
279
- <div className="flex items-center justify-between gap-3">
280
- <div className="flex items-center gap-3 min-w-0 flex-1">
281
- <div className="p-1.5 bg-[var(--kyro-surface-accent)] rounded-lg group-hover:bg-[var(--kyro-primary)]/10 transition-colors shrink-0">
282
- <Key className="w-3.5 h-3.5 text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-primary)] transition-colors" />
248
+ <div key={key.id} className="py-3.5 flex items-center justify-between gap-4 first:pt-0 last:pb-0">
249
+ <div className="flex items-center gap-3 min-w-0">
250
+ <div className="p-2 bg-[var(--kyro-surface-accent)] rounded-lg shrink-0">
251
+ <Key className="w-4 h-4 text-[var(--kyro-text-secondary)]" />
252
+ </div>
253
+ <div className="min-w-0">
254
+ <div className="flex items-center gap-2">
255
+ <span className="text-xs font-bold text-[var(--kyro-text-primary)] truncate">{key.name}</span>
256
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 shrink-0" />
283
257
  </div>
284
- <div className="min-w-0 flex-1">
285
- <div className="flex items-center gap-2">
286
- <h3 className="text-sm font-bold truncate group-hover:text-[var(--kyro-primary)] transition-colors">{key.name}</h3>
287
- <span className="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
288
- </div>
289
- <div className="flex items-center gap-3 text-[11px] text-[var(--kyro-text-secondary)]">
290
- <span className="font-mono opacity-60">{key.keyPrefix}••••••••••••••••</span>
291
- <span className="opacity-30">·</span>
292
- <span className="flex items-center gap-1">
293
- {key.permissions?.includes("*") ? (
294
- "Full Access"
295
- ) : (
296
- key.permissions?.slice(0, 2).join(", ")
297
- )}
298
- {key.permissions && key.permissions.length > 2 && <span className="opacity-40">+{key.permissions.length - 2}</span>}
299
- </span>
300
- <span className="opacity-30">·</span>
301
- <span className="flex items-center gap-1">
302
- <Clock className="w-3 h-3" />
303
- {key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never"}
304
- </span>
305
- </div>
258
+ <div className="flex items-center gap-3 text-[11px] text-[var(--kyro-text-secondary)] mt-0.5">
259
+ <span className="font-mono opacity-70">{key.keyPrefix}••••••••••••••••</span>
260
+ <span>·</span>
261
+ <span>
262
+ {key.permissions?.includes("*") ? "Full Access" : key.permissions?.slice(0, 2).join(", ")}
263
+ </span>
264
+ <span>·</span>
265
+ <span className="flex items-center gap-1">
266
+ <Clock className="w-3 h-3" />
267
+ {key.lastUsed ? new Date(key.lastUsed).toLocaleDateString() : "Never used"}
268
+ </span>
306
269
  </div>
307
270
  </div>
308
-
309
- <button
310
- type="button"
311
- onClick={() => remove(key.id, "API Key")}
312
- 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"
313
- title={t("tooltips.revokeAccess", { defaultValue: "Revoke Access" })}
314
- >
315
- <Trash2 className="w-3.5 h-3.5 text-red-500/50 group-hover/delete:text-red-500" />
316
- </button>
317
271
  </div>
272
+
273
+ <button
274
+ type="button"
275
+ onClick={() => remove(key.id, "API Key")}
276
+ className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-colors shrink-0"
277
+ title={t("tooltips.revokeAccess", { defaultValue: "Revoke Access" })}
278
+ >
279
+ <Trash2 className="w-4 h-4" />
280
+ </button>
318
281
  </div>
319
282
  ))}
320
283
  </div>
321
284
  )}
322
- </section>
285
+ </div>
323
286
  </div>
324
287
 
325
- {/* Create Modal */}
288
+ {/* Create Key Modal */}
326
289
  <Modal size="lg" open={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} title={t("tooltips.createNewApiKey", { defaultValue: "Create New API Key" })}>
327
290
  <ModalContent>
328
- <div className="space-y-5">
291
+ <div className="space-y-4">
329
292
  <div>
330
- <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)]">Name</label>
293
+ <label className="block text-xs font-bold mb-1 text-[var(--kyro-text-secondary)]">Key Name</label>
331
294
  <input
332
295
  type="text"
333
296
  value={newKeyName}
334
297
  onChange={(e) => { setNewKeyName(e.target.value); setCreateError(""); }}
335
- placeholder="e.g., Production App, Staging, Mobile App"
336
- className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
298
+ placeholder="e.g., Production Frontend, Mobile App, Webhook Client"
299
+ className="w-full px-3.5 py-2.5 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl text-xs font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-sidebar-active)]"
337
300
  onKeyDown={(e) => e.key === "Enter" && handleCreateKey()}
338
301
  />
339
- {createError && <p className="mt-1.5 text-xs text-red-500">{createError}</p>}
302
+ {createError && <p className="mt-1 text-xs text-red-500">{createError}</p>}
340
303
  </div>
341
304
 
342
-
343
-
344
305
  <div>
345
- <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)]">Expires (optional)</label>
306
+ <label className="block text-xs font-bold mb-1 text-[var(--kyro-text-secondary)]">Expires (Optional)</label>
346
307
  <input
347
308
  type="datetime-local"
348
309
  value={newKeyExpires}
349
310
  onChange={(e) => setNewKeyExpires(e.target.value)}
350
- className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
311
+ className="w-full px-3.5 py-2.5 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl text-xs font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-sidebar-active)]"
351
312
  />
352
- <p className="mt-1 text-[10px] text-[var(--kyro-text-muted)]">
353
- Leave empty for no expiration. Time is in UTC.
313
+ <p className="mt-1 text-[10px] text-[var(--kyro-text-secondary)]">
314
+ Leave blank for non-expiring credentials.
354
315
  </p>
355
316
  </div>
356
317
 
357
318
  <div className="p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2.5">
358
- <AlertTriangle className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5" />
319
+ <AlertTriangle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
359
320
  <p className="text-[11px] text-amber-600 font-medium">
360
- The key will be shown only once after creation — copy it immediately.
321
+ The generated key string will only be displayed once upon creation.
361
322
  </p>
362
323
  </div>
363
324
  </div>
@@ -366,50 +327,39 @@ export function ApiKeysManager() {
366
327
  <button
367
328
  type="button"
368
329
  onClick={() => setIsCreateModalOpen(false)}
369
- className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
330
+ className="px-4 py-2 rounded-xl text-xs font-bold border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
370
331
  >
371
332
  Cancel
372
333
  </button>
373
334
  <button
374
335
  type="button"
375
336
  onClick={handleCreateKey}
376
- className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
337
+ className="px-4 py-2 rounded-xl text-xs font-bold bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
377
338
  >
378
339
  Generate Key
379
340
  </button>
380
341
  </ModalActions>
381
342
  </Modal>
382
343
 
383
- {/* Help Modal */}
344
+ {/* Integration Guide Modal */}
384
345
  <Modal size="lg" open={showHelpModal} onClose={() => setShowHelpModal(false)} title={t("tooltips.howApiKeysWork", { defaultValue: "How API Keys Work" })}>
385
346
  <ModalContent>
386
- <div className="space-y-6">
387
- <div>
388
- <h4 className="font-bold mb-2">What is an API key?</h4>
389
- <p className="text-sm text-[var(--kyro-text-secondary)]">
390
- An API key is a unique token that authenticates your requests to the API. Think of it as a password that's specifically for programmatic access.
391
- </p>
392
- </div>
347
+ <div className="space-y-4">
393
348
  <div>
394
- <h4 className="font-bold mb-2">How to use it</h4>
395
- <p className="text-sm text-[var(--kyro-text-secondary)] mb-3">
396
- Add your API key to the Authorization header of your HTTP requests:
349
+ <h4 className="text-xs font-bold text-[var(--kyro-text-primary)] mb-1">Authorization Header Format</h4>
350
+ <p className="text-xs text-[var(--kyro-text-secondary)] mb-2">
351
+ Pass your API key in the standard Authorization header with the <code className="font-mono bg-[var(--kyro-surface-accent)] px-1 py-0.5 rounded text-[11px]">ApiKey</code> prefix:
397
352
  </p>
398
- <div className="bg-[var(--kyro-bg)] rounded-lg p-4 font-mono text-sm space-y-2">
399
- <div>
400
- <span className="text-[var(--kyro-text-secondary)]">Authorization:</span>{" "}
401
- <span className="text-[var(--kyro-primary)]">ApiKey </span>
402
- <span className="text-green-500">kyro_xxxxxxxxxxxx</span>
403
- </div>
353
+ <div className="bg-[var(--kyro-bg)] rounded-xl p-3 border border-[var(--kyro-border)] font-mono text-xs text-[var(--kyro-text-primary)]">
354
+ Authorization: ApiKey kyro_xxxxxxxxxxxxxxxx
404
355
  </div>
405
356
  </div>
406
357
  <div>
407
- <h4 className="font-bold mb-2">Best practices</h4>
408
- <ul className="text-sm text-[var(--kyro-text-secondary)] space-y-2 list-disc list-inside">
409
- <li>Never share your API key publicly</li>
410
- <li>Store it securely (environment variables, secrets manager)</li>
411
- <li>Create separate keys for different applications</li>
412
- <li>Revoke keys that are no longer in use</li>
358
+ <h4 className="text-xs font-bold text-[var(--kyro-text-primary)] mb-1">Key Security Principles</h4>
359
+ <ul className="text-xs text-[var(--kyro-text-secondary)] space-y-1.5 list-disc list-inside">
360
+ <li>Keep keys private and store them in server environment variables</li>
361
+ <li>Never expose keys in client-side front-end code bases</li>
362
+ <li>Use separate keys for distinct microservices or environments</li>
413
363
  </ul>
414
364
  </div>
415
365
  </div>
@@ -418,9 +368,9 @@ export function ApiKeysManager() {
418
368
  <button
419
369
  type="button"
420
370
  onClick={() => setShowHelpModal(false)}
421
- className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
371
+ className="px-4 py-2 rounded-xl text-xs font-bold bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
422
372
  >
423
- Got it
373
+ Got It
424
374
  </button>
425
375
  </ModalActions>
426
376
  </Modal>