@chaaskit/client 0.1.0

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 (135) hide show
  1. package/dist/favicon.svg +11 -0
  2. package/dist/index.html +17 -0
  3. package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
  4. package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
  5. package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
  6. package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
  7. package/dist/lib/extensions.js +10 -0
  8. package/dist/lib/extensions.js.map +1 -0
  9. package/dist/lib/favicon.svg +11 -0
  10. package/dist/lib/index.js +74126 -0
  11. package/dist/lib/index.js.map +1 -0
  12. package/dist/lib/logo.svg +12 -0
  13. package/dist/lib/routes/AcceptInviteRoute.js +19 -0
  14. package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
  15. package/dist/lib/routes/AdminDashboardRoute.js +19 -0
  16. package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
  17. package/dist/lib/routes/AdminTeamRoute.js +19 -0
  18. package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
  19. package/dist/lib/routes/AdminTeamsRoute.js +19 -0
  20. package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
  21. package/dist/lib/routes/AdminUsersRoute.js +19 -0
  22. package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
  23. package/dist/lib/routes/ApiKeysRoute.js +19 -0
  24. package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
  25. package/dist/lib/routes/AutomationsRoute.js +19 -0
  26. package/dist/lib/routes/AutomationsRoute.js.map +1 -0
  27. package/dist/lib/routes/ChatRoute.js +19 -0
  28. package/dist/lib/routes/ChatRoute.js.map +1 -0
  29. package/dist/lib/routes/DocumentsRoute.js +19 -0
  30. package/dist/lib/routes/DocumentsRoute.js.map +1 -0
  31. package/dist/lib/routes/OAuthConsentRoute.js +19 -0
  32. package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
  33. package/dist/lib/routes/PricingRoute.js +19 -0
  34. package/dist/lib/routes/PricingRoute.js.map +1 -0
  35. package/dist/lib/routes/PrivacyRoute.js +19 -0
  36. package/dist/lib/routes/PrivacyRoute.js.map +1 -0
  37. package/dist/lib/routes/TeamSettingsRoute.js +19 -0
  38. package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
  39. package/dist/lib/routes/TermsRoute.js +19 -0
  40. package/dist/lib/routes/TermsRoute.js.map +1 -0
  41. package/dist/lib/routes/VerifyEmailRoute.js +19 -0
  42. package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
  43. package/dist/lib/routes.js +79 -0
  44. package/dist/lib/routes.js.map +1 -0
  45. package/dist/lib/ssr-utils.js +29 -0
  46. package/dist/lib/ssr-utils.js.map +1 -0
  47. package/dist/lib/ssr.js +60 -0
  48. package/dist/lib/ssr.js.map +1 -0
  49. package/dist/lib/styles.css +2410 -0
  50. package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
  51. package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
  52. package/dist/logo.svg +12 -0
  53. package/package.json +84 -0
  54. package/src/components/AgentSelector.tsx +90 -0
  55. package/src/components/BranchModal.tsx +129 -0
  56. package/src/components/ClientOnly.tsx +27 -0
  57. package/src/components/ExportMenu.tsx +122 -0
  58. package/src/components/LoadingSkeletons.tsx +110 -0
  59. package/src/components/MCPCredentialsSection.tsx +309 -0
  60. package/src/components/MentionChip.tsx +149 -0
  61. package/src/components/MentionDropdown.tsx +175 -0
  62. package/src/components/MentionInput.tsx +293 -0
  63. package/src/components/MessageItem.tsx +300 -0
  64. package/src/components/MessageList.tsx +159 -0
  65. package/src/components/OAuthAppsSection.tsx +124 -0
  66. package/src/components/ProjectFolder.tsx +141 -0
  67. package/src/components/ProjectModal.tsx +296 -0
  68. package/src/components/SSRMessageList.tsx +153 -0
  69. package/src/components/SearchModal.tsx +173 -0
  70. package/src/components/SettingsModal.tsx +412 -0
  71. package/src/components/ShareModal.tsx +280 -0
  72. package/src/components/Sidebar.tsx +491 -0
  73. package/src/components/TeamSwitcher.tsx +273 -0
  74. package/src/components/ToolCallDisplay.tsx +473 -0
  75. package/src/components/ToolConfirmationModal.tsx +130 -0
  76. package/src/components/UsageChart.tsx +177 -0
  77. package/src/components/content/CodeBlock.tsx +69 -0
  78. package/src/components/content/MarkdownRenderer.tsx +64 -0
  79. package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
  80. package/src/contexts/AuthContext.tsx +119 -0
  81. package/src/contexts/ConfigContext.tsx +214 -0
  82. package/src/contexts/ProjectContext.tsx +167 -0
  83. package/src/contexts/ServerConfigProvider.tsx +41 -0
  84. package/src/contexts/ServerThemeProvider.tsx +47 -0
  85. package/src/contexts/TeamContext.tsx +255 -0
  86. package/src/contexts/ThemeContext.tsx +113 -0
  87. package/src/extensions/index.ts +15 -0
  88. package/src/extensions/registry.ts +187 -0
  89. package/src/extensions/useExtensions.ts +52 -0
  90. package/src/hooks/useAppPath.ts +34 -0
  91. package/src/hooks/useBasePath.ts +13 -0
  92. package/src/hooks/useKeyboardShortcuts.ts +50 -0
  93. package/src/hooks/useMentionSearch.ts +106 -0
  94. package/src/index.tsx +116 -0
  95. package/src/layouts/MainLayout.tsx +98 -0
  96. package/src/pages/AcceptInvitePage.tsx +175 -0
  97. package/src/pages/AdminDashboardPage.tsx +362 -0
  98. package/src/pages/AdminTeamPage.tsx +304 -0
  99. package/src/pages/AdminTeamsPage.tsx +242 -0
  100. package/src/pages/AdminUsersPage.tsx +385 -0
  101. package/src/pages/ApiKeysPage.tsx +449 -0
  102. package/src/pages/ChatPage.tsx +310 -0
  103. package/src/pages/DocumentsPage.tsx +577 -0
  104. package/src/pages/LoginPage.tsx +232 -0
  105. package/src/pages/OAuthConsentPage.tsx +234 -0
  106. package/src/pages/PricingPage.tsx +314 -0
  107. package/src/pages/PrivacyPage.tsx +65 -0
  108. package/src/pages/RegisterPage.tsx +153 -0
  109. package/src/pages/ScheduledPromptsPage.tsx +702 -0
  110. package/src/pages/SharedThreadPage.tsx +116 -0
  111. package/src/pages/TeamSettingsPage.tsx +1085 -0
  112. package/src/pages/TermsPage.tsx +82 -0
  113. package/src/pages/VerifyEmailPage.tsx +202 -0
  114. package/src/routes/AcceptInviteRoute.tsx +24 -0
  115. package/src/routes/AdminDashboardRoute.tsx +24 -0
  116. package/src/routes/AdminTeamRoute.tsx +24 -0
  117. package/src/routes/AdminTeamsRoute.tsx +24 -0
  118. package/src/routes/AdminUsersRoute.tsx +24 -0
  119. package/src/routes/ApiKeysRoute.tsx +24 -0
  120. package/src/routes/AutomationsRoute.tsx +24 -0
  121. package/src/routes/ChatRoute.tsx +28 -0
  122. package/src/routes/DocumentsRoute.tsx +24 -0
  123. package/src/routes/OAuthConsentRoute.tsx +24 -0
  124. package/src/routes/PricingRoute.tsx +24 -0
  125. package/src/routes/PrivacyRoute.tsx +24 -0
  126. package/src/routes/TeamSettingsRoute.tsx +24 -0
  127. package/src/routes/TermsRoute.tsx +24 -0
  128. package/src/routes/VerifyEmailRoute.tsx +24 -0
  129. package/src/routes/index.ts +57 -0
  130. package/src/ssr-utils.tsx +84 -0
  131. package/src/ssr.ts +123 -0
  132. package/src/stores/chatStore.ts +670 -0
  133. package/src/styles/index.css +254 -0
  134. package/src/utils/api.ts +78 -0
  135. package/src/vite-env.d.ts +13 -0
@@ -0,0 +1,449 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Link, Navigate } from 'react-router';
3
+ import { Key, Plus, Trash2, Copy, Check, X, Loader2, AlertTriangle } from 'lucide-react';
4
+ import { useConfig, useConfigLoaded } from '../contexts/ConfigContext';
5
+ import { useAuth } from '../contexts/AuthContext';
6
+ import { useTeam } from '../contexts/TeamContext';
7
+ import { useAppPath } from '../hooks/useAppPath';
8
+ import { api } from '../utils/api';
9
+
10
+ interface ApiKey {
11
+ id: string;
12
+ name: string;
13
+ keyPrefix: string;
14
+ teamId: string | null;
15
+ team: { id: string; name: string } | null;
16
+ lastUsedAt: string | null;
17
+ expiresAt: string | null;
18
+ createdAt: string;
19
+ }
20
+
21
+ interface CreateKeyResponse {
22
+ key: {
23
+ id: string;
24
+ name: string;
25
+ keyPrefix: string;
26
+ teamId: string | null;
27
+ teamName: string | null;
28
+ createdAt: string;
29
+ expiresAt: string | null;
30
+ };
31
+ secret: string;
32
+ }
33
+
34
+ export default function ApiKeysPage() {
35
+ const config = useConfig();
36
+ const configLoaded = useConfigLoaded();
37
+ const { user } = useAuth();
38
+ const { teams } = useTeam();
39
+ const appPath = useAppPath();
40
+
41
+ const [keys, setKeys] = useState<ApiKey[]>([]);
42
+ const [isLoading, setIsLoading] = useState(true);
43
+ const [error, setError] = useState('');
44
+
45
+ const [showCreateModal, setShowCreateModal] = useState(false);
46
+ const [newKeyName, setNewKeyName] = useState('');
47
+ const [newKeyScope, setNewKeyScope] = useState('');
48
+ const [newKeyExpiration, setNewKeyExpiration] = useState('never');
49
+ const [isCreating, setIsCreating] = useState(false);
50
+
51
+ const [createdKey, setCreatedKey] = useState<CreateKeyResponse | null>(null);
52
+ const [copied, setCopied] = useState(false);
53
+
54
+ const [deletingKeyId, setDeletingKeyId] = useState<string | null>(null);
55
+
56
+ const [canAccess, setCanAccess] = useState<boolean | null>(null);
57
+
58
+ const teamsEnabled = config.teams?.enabled ?? false;
59
+ const showScopeSelector = teamsEnabled && teams.length > 0;
60
+
61
+ // Check access on mount (wait for config to load first)
62
+ useEffect(() => {
63
+ if (!configLoaded) return;
64
+
65
+ if (!config.api?.enabled) {
66
+ setCanAccess(false);
67
+ setIsLoading(false);
68
+ return;
69
+ }
70
+
71
+ api.get<{ canAccess: boolean }>('/api/api-keys/access')
72
+ .then((res) => {
73
+ setCanAccess(res.canAccess);
74
+ if (res.canAccess) {
75
+ loadKeys();
76
+ } else {
77
+ setIsLoading(false);
78
+ }
79
+ })
80
+ .catch(() => {
81
+ setCanAccess(false);
82
+ setIsLoading(false);
83
+ });
84
+ }, [configLoaded, config.api?.enabled]);
85
+
86
+ async function loadKeys() {
87
+ try {
88
+ const res = await api.get<{ keys: ApiKey[] }>('/api/api-keys');
89
+ setKeys(res.keys);
90
+ } catch (err) {
91
+ setError(err instanceof Error ? err.message : 'Failed to load API keys');
92
+ } finally {
93
+ setIsLoading(false);
94
+ }
95
+ }
96
+
97
+ async function handleCreateKey(e: React.FormEvent) {
98
+ e.preventDefault();
99
+ setIsCreating(true);
100
+ setError('');
101
+
102
+ try {
103
+ let expiresAt: string | undefined;
104
+ if (newKeyExpiration !== 'never') {
105
+ const date = new Date();
106
+ switch (newKeyExpiration) {
107
+ case '30d':
108
+ date.setDate(date.getDate() + 30);
109
+ break;
110
+ case '90d':
111
+ date.setDate(date.getDate() + 90);
112
+ break;
113
+ case '1y':
114
+ date.setFullYear(date.getFullYear() + 1);
115
+ break;
116
+ }
117
+ expiresAt = date.toISOString();
118
+ }
119
+
120
+ const res = await api.post<CreateKeyResponse>('/api/api-keys', {
121
+ name: newKeyName || 'Untitled Key',
122
+ teamId: newKeyScope || undefined,
123
+ expiresAt,
124
+ });
125
+
126
+ setCreatedKey(res);
127
+ setShowCreateModal(false);
128
+ setNewKeyName('');
129
+ setNewKeyScope('');
130
+ setNewKeyExpiration('never');
131
+ await loadKeys();
132
+ } catch (err) {
133
+ setError(err instanceof Error ? err.message : 'Failed to create API key');
134
+ } finally {
135
+ setIsCreating(false);
136
+ }
137
+ }
138
+
139
+ async function handleDeleteKey(keyId: string) {
140
+ if (!confirm('Are you sure you want to revoke this API key? This action cannot be undone.')) {
141
+ return;
142
+ }
143
+
144
+ setDeletingKeyId(keyId);
145
+ setError('');
146
+
147
+ try {
148
+ await api.delete(`/api/api-keys/${keyId}`);
149
+ setKeys((prev) => prev.filter((k) => k.id !== keyId));
150
+ } catch (err) {
151
+ setError(err instanceof Error ? err.message : 'Failed to delete API key');
152
+ } finally {
153
+ setDeletingKeyId(null);
154
+ }
155
+ }
156
+
157
+ function handleCopyKey() {
158
+ if (createdKey) {
159
+ navigator.clipboard.writeText(createdKey.secret);
160
+ setCopied(true);
161
+ setTimeout(() => setCopied(false), 2000);
162
+ }
163
+ }
164
+
165
+ function formatDate(dateString: string | null): string {
166
+ if (!dateString) return 'Never';
167
+ const date = new Date(dateString);
168
+ return date.toLocaleDateString(undefined, {
169
+ year: 'numeric',
170
+ month: 'short',
171
+ day: 'numeric',
172
+ });
173
+ }
174
+
175
+ function formatRelativeDate(dateString: string | null): string {
176
+ if (!dateString) return 'Never';
177
+ const date = new Date(dateString);
178
+ const now = new Date();
179
+ const diffMs = now.getTime() - date.getTime();
180
+ const diffMins = Math.floor(diffMs / 60000);
181
+ const diffHours = Math.floor(diffMs / 3600000);
182
+ const diffDays = Math.floor(diffMs / 86400000);
183
+
184
+ if (diffMins < 1) return 'Just now';
185
+ if (diffMins < 60) return `${diffMins}m ago`;
186
+ if (diffHours < 24) return `${diffHours}h ago`;
187
+ if (diffDays === 1) return 'Yesterday';
188
+ if (diffDays < 7) return `${diffDays}d ago`;
189
+ return formatDate(dateString);
190
+ }
191
+
192
+ // Redirect if feature is disabled
193
+ if (canAccess === false) {
194
+ return <Navigate to={appPath('/')} replace />;
195
+ }
196
+
197
+ return (
198
+ <div className="min-h-screen bg-background p-4 sm:p-8">
199
+ <div className="mx-auto max-w-3xl">
200
+ {/* Header */}
201
+ <div className="flex items-center justify-between mb-6 sm:mb-8">
202
+ <div className="flex items-center gap-3">
203
+ <Key size={24} className="text-primary" />
204
+ <h1 className="text-xl sm:text-2xl font-bold text-text-primary">
205
+ API Keys
206
+ </h1>
207
+ </div>
208
+ <Link
209
+ to={appPath('/')}
210
+ className="flex items-center justify-center rounded-lg p-2 text-text-muted hover:text-text-primary hover:bg-background-secondary"
211
+ aria-label="Close"
212
+ >
213
+ <X size={20} />
214
+ </Link>
215
+ </div>
216
+
217
+ {/* Description */}
218
+ <div className="mb-6 text-sm text-text-secondary">
219
+ API keys allow you to access the API programmatically. Keep your keys secret and never share them publicly.
220
+ </div>
221
+
222
+ {/* Error */}
223
+ {error && (
224
+ <div className="mb-6 rounded-lg bg-error/10 p-4 text-sm text-error">
225
+ {error}
226
+ </div>
227
+ )}
228
+
229
+ {/* Create Button */}
230
+ <div className="mb-6">
231
+ <button
232
+ onClick={() => setShowCreateModal(true)}
233
+ className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover"
234
+ >
235
+ <Plus size={16} />
236
+ Create API Key
237
+ </button>
238
+ </div>
239
+
240
+ {/* Keys List */}
241
+ {isLoading ? (
242
+ <div className="flex items-center justify-center py-12">
243
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
244
+ </div>
245
+ ) : keys.length === 0 ? (
246
+ <div className="rounded-lg border border-border bg-background-secondary p-8 text-center">
247
+ <Key size={48} className="mx-auto mb-4 text-text-muted" />
248
+ <h3 className="text-lg font-medium text-text-primary mb-2">
249
+ No API Keys
250
+ </h3>
251
+ <p className="text-sm text-text-secondary mb-4">
252
+ Create an API key to start using the API programmatically.
253
+ </p>
254
+ </div>
255
+ ) : (
256
+ <div className="space-y-3">
257
+ {keys.map((key) => (
258
+ <div
259
+ key={key.id}
260
+ className="rounded-lg border border-border bg-background-secondary p-4"
261
+ >
262
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
263
+ <div className="flex-1 min-w-0">
264
+ <div className="flex items-center gap-2 mb-1">
265
+ <span className="font-medium text-text-primary truncate">
266
+ {key.name}
267
+ </span>
268
+ <span className="text-xs px-2 py-0.5 rounded-full bg-background text-text-muted font-mono">
269
+ {key.keyPrefix}...
270
+ </span>
271
+ </div>
272
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-muted">
273
+ <span>
274
+ Scope: {key.team ? key.team.name : 'Personal'}
275
+ </span>
276
+ <span>
277
+ Created: {formatDate(key.createdAt)}
278
+ </span>
279
+ <span>
280
+ Last used: {formatRelativeDate(key.lastUsedAt)}
281
+ </span>
282
+ {key.expiresAt && (
283
+ <span>
284
+ Expires: {formatDate(key.expiresAt)}
285
+ </span>
286
+ )}
287
+ </div>
288
+ </div>
289
+ <button
290
+ onClick={() => handleDeleteKey(key.id)}
291
+ disabled={deletingKeyId === key.id}
292
+ className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-error hover:bg-error/10 disabled:opacity-50"
293
+ >
294
+ {deletingKeyId === key.id ? (
295
+ <Loader2 size={14} className="animate-spin" />
296
+ ) : (
297
+ <Trash2 size={14} />
298
+ )}
299
+ Revoke
300
+ </button>
301
+ </div>
302
+ </div>
303
+ ))}
304
+ </div>
305
+ )}
306
+
307
+ {/* Create Modal */}
308
+ {showCreateModal && (
309
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
310
+ <div
311
+ className="absolute inset-0 bg-black/50"
312
+ onClick={() => setShowCreateModal(false)}
313
+ />
314
+ <div className="relative w-full max-w-md rounded-2xl bg-background p-6">
315
+ <h2 className="text-lg font-semibold text-text-primary mb-4">
316
+ Create API Key
317
+ </h2>
318
+ <form onSubmit={handleCreateKey}>
319
+ <div className="space-y-4">
320
+ <div>
321
+ <label className="block text-sm font-medium text-text-primary mb-2">
322
+ Name
323
+ </label>
324
+ <input
325
+ type="text"
326
+ value={newKeyName}
327
+ onChange={(e) => setNewKeyName(e.target.value)}
328
+ placeholder="e.g., My CLI Tool"
329
+ className="w-full rounded-lg border border-input-border bg-input-background px-3 py-2 text-text-primary placeholder-text-muted focus:border-primary focus:outline-none"
330
+ />
331
+ </div>
332
+
333
+ {showScopeSelector && (
334
+ <div>
335
+ <label className="block text-sm font-medium text-text-primary mb-2">
336
+ Scope
337
+ </label>
338
+ <select
339
+ value={newKeyScope}
340
+ onChange={(e) => setNewKeyScope(e.target.value)}
341
+ className="w-full rounded-lg border border-input-border bg-input-background px-3 py-2 text-text-primary focus:border-primary focus:outline-none"
342
+ >
343
+ <option value="">Personal</option>
344
+ {teams.map((team) => (
345
+ <option key={team.id} value={team.id}>
346
+ {team.name}
347
+ </option>
348
+ ))}
349
+ </select>
350
+ <p className="mt-1 text-xs text-text-muted">
351
+ Team-scoped keys operate in the team context for all API requests.
352
+ </p>
353
+ </div>
354
+ )}
355
+
356
+ <div>
357
+ <label className="block text-sm font-medium text-text-primary mb-2">
358
+ Expiration
359
+ </label>
360
+ <select
361
+ value={newKeyExpiration}
362
+ onChange={(e) => setNewKeyExpiration(e.target.value)}
363
+ className="w-full rounded-lg border border-input-border bg-input-background px-3 py-2 text-text-primary focus:border-primary focus:outline-none"
364
+ >
365
+ <option value="never">Never</option>
366
+ <option value="30d">30 days</option>
367
+ <option value="90d">90 days</option>
368
+ <option value="1y">1 year</option>
369
+ </select>
370
+ </div>
371
+ </div>
372
+
373
+ <div className="mt-6 flex justify-end gap-3">
374
+ <button
375
+ type="button"
376
+ onClick={() => setShowCreateModal(false)}
377
+ className="rounded-lg px-4 py-2 text-sm text-text-secondary hover:bg-background-secondary"
378
+ >
379
+ Cancel
380
+ </button>
381
+ <button
382
+ type="submit"
383
+ disabled={isCreating}
384
+ className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-50"
385
+ >
386
+ {isCreating && <Loader2 size={14} className="animate-spin" />}
387
+ Create Key
388
+ </button>
389
+ </div>
390
+ </form>
391
+ </div>
392
+ </div>
393
+ )}
394
+
395
+ {/* Created Key Modal */}
396
+ {createdKey && (
397
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
398
+ <div className="absolute inset-0 bg-black/50" />
399
+ <div className="relative w-full max-w-md rounded-2xl bg-background p-6">
400
+ <div className="flex items-center gap-2 mb-4">
401
+ <AlertTriangle size={20} className="text-warning" />
402
+ <h2 className="text-lg font-semibold text-text-primary">
403
+ Save Your API Key
404
+ </h2>
405
+ </div>
406
+
407
+ <div className="mb-4 rounded-lg bg-warning/10 border border-warning/30 p-3 text-sm text-warning">
408
+ Make sure to copy your API key now. You won't be able to see it again!
409
+ </div>
410
+
411
+ <div className="mb-4">
412
+ <label className="block text-sm font-medium text-text-primary mb-2">
413
+ Your API Key
414
+ </label>
415
+ <div className="flex items-center gap-2">
416
+ <input
417
+ type="text"
418
+ value={createdKey.secret}
419
+ readOnly
420
+ className="flex-1 rounded-lg border border-input-border bg-input-background px-3 py-2 text-sm font-mono text-text-primary"
421
+ />
422
+ <button
423
+ onClick={handleCopyKey}
424
+ className="flex items-center justify-center rounded-lg border border-border bg-background-secondary p-2 hover:bg-background"
425
+ >
426
+ {copied ? (
427
+ <Check size={18} className="text-success" />
428
+ ) : (
429
+ <Copy size={18} className="text-text-secondary" />
430
+ )}
431
+ </button>
432
+ </div>
433
+ </div>
434
+
435
+ <div className="flex justify-end">
436
+ <button
437
+ onClick={() => setCreatedKey(null)}
438
+ className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover"
439
+ >
440
+ Done
441
+ </button>
442
+ </div>
443
+ </div>
444
+ </div>
445
+ )}
446
+ </div>
447
+ </div>
448
+ );
449
+ }