@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.
- package/dist/favicon.svg +11 -0
- package/dist/index.html +17 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
- package/dist/lib/extensions.js +10 -0
- package/dist/lib/extensions.js.map +1 -0
- package/dist/lib/favicon.svg +11 -0
- package/dist/lib/index.js +74126 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logo.svg +12 -0
- package/dist/lib/routes/AcceptInviteRoute.js +19 -0
- package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
- package/dist/lib/routes/AdminDashboardRoute.js +19 -0
- package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamRoute.js +19 -0
- package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamsRoute.js +19 -0
- package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
- package/dist/lib/routes/AdminUsersRoute.js +19 -0
- package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
- package/dist/lib/routes/ApiKeysRoute.js +19 -0
- package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
- package/dist/lib/routes/AutomationsRoute.js +19 -0
- package/dist/lib/routes/AutomationsRoute.js.map +1 -0
- package/dist/lib/routes/ChatRoute.js +19 -0
- package/dist/lib/routes/ChatRoute.js.map +1 -0
- package/dist/lib/routes/DocumentsRoute.js +19 -0
- package/dist/lib/routes/DocumentsRoute.js.map +1 -0
- package/dist/lib/routes/OAuthConsentRoute.js +19 -0
- package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
- package/dist/lib/routes/PricingRoute.js +19 -0
- package/dist/lib/routes/PricingRoute.js.map +1 -0
- package/dist/lib/routes/PrivacyRoute.js +19 -0
- package/dist/lib/routes/PrivacyRoute.js.map +1 -0
- package/dist/lib/routes/TeamSettingsRoute.js +19 -0
- package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
- package/dist/lib/routes/TermsRoute.js +19 -0
- package/dist/lib/routes/TermsRoute.js.map +1 -0
- package/dist/lib/routes/VerifyEmailRoute.js +19 -0
- package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
- package/dist/lib/routes.js +79 -0
- package/dist/lib/routes.js.map +1 -0
- package/dist/lib/ssr-utils.js +29 -0
- package/dist/lib/ssr-utils.js.map +1 -0
- package/dist/lib/ssr.js +60 -0
- package/dist/lib/ssr.js.map +1 -0
- package/dist/lib/styles.css +2410 -0
- package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
- package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
- package/dist/logo.svg +12 -0
- package/package.json +84 -0
- package/src/components/AgentSelector.tsx +90 -0
- package/src/components/BranchModal.tsx +129 -0
- package/src/components/ClientOnly.tsx +27 -0
- package/src/components/ExportMenu.tsx +122 -0
- package/src/components/LoadingSkeletons.tsx +110 -0
- package/src/components/MCPCredentialsSection.tsx +309 -0
- package/src/components/MentionChip.tsx +149 -0
- package/src/components/MentionDropdown.tsx +175 -0
- package/src/components/MentionInput.tsx +293 -0
- package/src/components/MessageItem.tsx +300 -0
- package/src/components/MessageList.tsx +159 -0
- package/src/components/OAuthAppsSection.tsx +124 -0
- package/src/components/ProjectFolder.tsx +141 -0
- package/src/components/ProjectModal.tsx +296 -0
- package/src/components/SSRMessageList.tsx +153 -0
- package/src/components/SearchModal.tsx +173 -0
- package/src/components/SettingsModal.tsx +412 -0
- package/src/components/ShareModal.tsx +280 -0
- package/src/components/Sidebar.tsx +491 -0
- package/src/components/TeamSwitcher.tsx +273 -0
- package/src/components/ToolCallDisplay.tsx +473 -0
- package/src/components/ToolConfirmationModal.tsx +130 -0
- package/src/components/UsageChart.tsx +177 -0
- package/src/components/content/CodeBlock.tsx +69 -0
- package/src/components/content/MarkdownRenderer.tsx +64 -0
- package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
- package/src/contexts/AuthContext.tsx +119 -0
- package/src/contexts/ConfigContext.tsx +214 -0
- package/src/contexts/ProjectContext.tsx +167 -0
- package/src/contexts/ServerConfigProvider.tsx +41 -0
- package/src/contexts/ServerThemeProvider.tsx +47 -0
- package/src/contexts/TeamContext.tsx +255 -0
- package/src/contexts/ThemeContext.tsx +113 -0
- package/src/extensions/index.ts +15 -0
- package/src/extensions/registry.ts +187 -0
- package/src/extensions/useExtensions.ts +52 -0
- package/src/hooks/useAppPath.ts +34 -0
- package/src/hooks/useBasePath.ts +13 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -0
- package/src/hooks/useMentionSearch.ts +106 -0
- package/src/index.tsx +116 -0
- package/src/layouts/MainLayout.tsx +98 -0
- package/src/pages/AcceptInvitePage.tsx +175 -0
- package/src/pages/AdminDashboardPage.tsx +362 -0
- package/src/pages/AdminTeamPage.tsx +304 -0
- package/src/pages/AdminTeamsPage.tsx +242 -0
- package/src/pages/AdminUsersPage.tsx +385 -0
- package/src/pages/ApiKeysPage.tsx +449 -0
- package/src/pages/ChatPage.tsx +310 -0
- package/src/pages/DocumentsPage.tsx +577 -0
- package/src/pages/LoginPage.tsx +232 -0
- package/src/pages/OAuthConsentPage.tsx +234 -0
- package/src/pages/PricingPage.tsx +314 -0
- package/src/pages/PrivacyPage.tsx +65 -0
- package/src/pages/RegisterPage.tsx +153 -0
- package/src/pages/ScheduledPromptsPage.tsx +702 -0
- package/src/pages/SharedThreadPage.tsx +116 -0
- package/src/pages/TeamSettingsPage.tsx +1085 -0
- package/src/pages/TermsPage.tsx +82 -0
- package/src/pages/VerifyEmailPage.tsx +202 -0
- package/src/routes/AcceptInviteRoute.tsx +24 -0
- package/src/routes/AdminDashboardRoute.tsx +24 -0
- package/src/routes/AdminTeamRoute.tsx +24 -0
- package/src/routes/AdminTeamsRoute.tsx +24 -0
- package/src/routes/AdminUsersRoute.tsx +24 -0
- package/src/routes/ApiKeysRoute.tsx +24 -0
- package/src/routes/AutomationsRoute.tsx +24 -0
- package/src/routes/ChatRoute.tsx +28 -0
- package/src/routes/DocumentsRoute.tsx +24 -0
- package/src/routes/OAuthConsentRoute.tsx +24 -0
- package/src/routes/PricingRoute.tsx +24 -0
- package/src/routes/PrivacyRoute.tsx +24 -0
- package/src/routes/TeamSettingsRoute.tsx +24 -0
- package/src/routes/TermsRoute.tsx +24 -0
- package/src/routes/VerifyEmailRoute.tsx +24 -0
- package/src/routes/index.ts +57 -0
- package/src/ssr-utils.tsx +84 -0
- package/src/ssr.ts +123 -0
- package/src/stores/chatStore.ts +670 -0
- package/src/styles/index.css +254 -0
- package/src/utils/api.ts +78 -0
- 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
|
+
}
|