Skip to content

Commit 873db8f

Browse files
committed
fix: remove alert-dialog dep, use existing Dialog for revoke confirm
alert-dialog was missing from ui/ and caused Docker build failure. Replaced with existing Dialog + Button (destructive variant). Zero new dependencies -- Docker cache safe.
1 parent a2d991d commit 873db8f

1 file changed

Lines changed: 41 additions & 110 deletions

File tree

frontend/src/pages/APIKeysPage.tsx

Lines changed: 41 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ import {
1414
DialogHeader,
1515
DialogTitle,
1616
} from '@/components/ui/dialog'
17-
import {
18-
AlertDialog,
19-
AlertDialogAction,
20-
AlertDialogCancel,
21-
AlertDialogContent,
22-
AlertDialogDescription,
23-
AlertDialogFooter,
24-
AlertDialogHeader,
25-
AlertDialogTitle,
26-
} from '@/components/ui/alert-dialog'
2717
import { toast } from 'sonner'
2818
import { API_URL } from '@/config/api'
2919
import { cn } from '@/lib/utils'
@@ -57,8 +47,6 @@ async function fetchKeys(token: string): Promise<APIKey[]> {
5747
return data.keys || []
5848
}
5949

60-
// -- Sub-components --
61-
6250
function TierBadge({ tier }: { tier: string }) {
6351
const styles: Record<string, string> = {
6452
enterprise: 'bg-violet-500/10 text-violet-400 border-violet-500/20',
@@ -153,52 +141,29 @@ function EmptyState({ onGenerate }: { onGenerate: () => void }) {
153141
)
154142
}
155143

156-
// Table row -- hover reveals revoke button, revoked rows go 50% opacity
157-
function KeyRow({
158-
apiKey,
159-
onRevoke,
160-
revoking,
161-
}: {
162-
apiKey: APIKey
163-
onRevoke: () => void
164-
revoking: boolean
165-
}) {
144+
function KeyRow({ apiKey, onRevoke, revoking }: { apiKey: APIKey; onRevoke: () => void; revoking: boolean }) {
166145
const isRevoked = !apiKey.active
167146
return (
168147
<div className={cn(
169148
'group grid items-center gap-4 px-4 py-3.5 border-b border-border last:border-0 transition-colors',
170149
'grid-cols-[1fr_200px_80px_110px_120px]',
171150
isRevoked ? 'opacity-50' : 'hover:bg-muted/20',
172151
)}>
173-
{/* Name + created */}
174152
<div className="min-w-0">
175153
<p className="text-sm font-medium text-foreground truncate">{apiKey.name}</p>
176154
<p className="text-[11px] text-muted-foreground mt-0.5 flex items-center gap-1">
177155
<Clock className="w-3 h-3" />
178156
Created {timeAgo(apiKey.created_at)}
179157
</p>
180158
</div>
181-
182-
{/* Key preview + inline copy */}
183159
<div className="flex items-center gap-2 min-w-0">
184-
<code className="text-xs font-mono text-muted-foreground truncate flex-1">
185-
{apiKey.key_preview}
186-
</code>
160+
<code className="text-xs font-mono text-muted-foreground truncate flex-1">{apiKey.key_preview}</code>
187161
{!isRevoked && <CopyButton value={apiKey.key_preview} />}
188162
</div>
189-
190-
{/* Tier */}
191163
<div><TierBadge tier={apiKey.tier} /></div>
192-
193-
{/* Last used */}
194164
<span className="text-xs text-muted-foreground">
195-
{apiKey.last_used_at
196-
? timeAgo(apiKey.last_used_at)
197-
: <span className="text-muted-foreground/40">Never</span>
198-
}
165+
{apiKey.last_used_at ? timeAgo(apiKey.last_used_at) : <span className="text-muted-foreground/40">Never</span>}
199166
</span>
200-
201-
{/* Status + hover-reveal revoke */}
202167
<div className="flex items-center justify-end gap-3">
203168
<span className="inline-flex items-center gap-1.5">
204169
<span className={cn('w-1.5 h-1.5 rounded-full', apiKey.active ? 'bg-emerald-400' : 'bg-zinc-600')} />
@@ -221,7 +186,6 @@ function KeyRow({
221186
)
222187
}
223188

224-
// One-time key reveal -- shown immediately after generation
225189
function NewKeyModal({ apiKey, onClose }: { apiKey: string | null; onClose: () => void }) {
226190
if (!apiKey) return null
227191
return (
@@ -239,9 +203,7 @@ function NewKeyModal({ apiKey, onClose }: { apiKey: string | null; onClose: () =
239203
<div className="space-y-3">
240204
<div className="rounded-lg border border-border bg-muted/30 overflow-hidden">
241205
<div className="px-4 py-3">
242-
<code className="text-xs font-mono text-foreground break-all leading-relaxed select-all">
243-
{apiKey}
244-
</code>
206+
<code className="text-xs font-mono text-foreground break-all leading-relaxed select-all">{apiKey}</code>
245207
</div>
246208
<div className="border-t border-border px-4 py-2 flex justify-end bg-muted/50">
247209
<CopyButton value={apiKey} />
@@ -272,17 +234,8 @@ function NewKeyModal({ apiKey, onClose }: { apiKey: string | null; onClose: () =
272234
)
273235
}
274236

275-
// Name-only generate dialog
276-
function GenerateKeyModal({
277-
open,
278-
onClose,
279-
onGenerate,
280-
generating,
281-
}: {
282-
open: boolean
283-
onClose: () => void
284-
onGenerate: (name: string) => void
285-
generating: boolean
237+
function GenerateKeyModal({ open, onClose, onGenerate, generating }: {
238+
open: boolean; onClose: () => void; onGenerate: (name: string) => void; generating: boolean
286239
}) {
287240
const [name, setName] = useState('')
288241
const handleSubmit = () => {
@@ -321,7 +274,35 @@ function GenerateKeyModal({
321274
)
322275
}
323276

324-
// -- Main export (named, matches existing import in Dashboard.tsx) --
277+
// Revoke confirm -- uses existing Dialog, no new deps needed
278+
function RevokeConfirmModal({ target, onConfirm, onClose }: {
279+
target: APIKey | null; onConfirm: () => void; onClose: () => void
280+
}) {
281+
return (
282+
<Dialog open={!!target} onOpenChange={(open) => !open && onClose()}>
283+
<DialogContent className="sm:max-w-sm">
284+
<DialogHeader>
285+
<DialogTitle className="text-base">Revoke key</DialogTitle>
286+
<DialogDescription>
287+
Revoke <span className="font-medium text-foreground">{target?.name}</span>?
288+
Any services using this key will lose access immediately. This cannot be undone.
289+
</DialogDescription>
290+
</DialogHeader>
291+
<DialogFooter>
292+
<Button variant="outline" size="sm" onClick={onClose} className="h-8 text-xs">Cancel</Button>
293+
<Button
294+
variant="destructive"
295+
size="sm"
296+
onClick={onConfirm}
297+
className="h-8 text-xs"
298+
>
299+
Revoke key
300+
</Button>
301+
</DialogFooter>
302+
</DialogContent>
303+
</Dialog>
304+
)
305+
}
325306

326307
export function APIKeysPage() {
327308
const { session } = useAuth()
@@ -386,7 +367,6 @@ export function APIKeysPage() {
386367
const activeKeys = keys.filter((k) => k.active)
387368

388369
if (isLoading) return <APIKeysSkeleton />
389-
390370
if (isError) {
391371
return (
392372
<div className="flex items-center justify-center min-h-[300px] text-muted-foreground text-sm">
@@ -397,33 +377,23 @@ export function APIKeysPage() {
397377

398378
return (
399379
<div className="space-y-4 max-w-4xl">
400-
401-
{/* Header -- same pattern as UsagePage */}
402380
<div className="flex items-start justify-between gap-4">
403381
<div className="flex items-center gap-3">
404382
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center shrink-0">
405383
<Key className="w-5 h-5 text-primary" />
406384
</div>
407385
<div>
408386
<h1 className="text-lg font-semibold text-foreground">API Keys</h1>
409-
<p className="text-sm text-muted-foreground">
410-
Manage keys for MCP, Claude Desktop, and API access
411-
</p>
387+
<p className="text-sm text-muted-foreground">Manage keys for MCP, Claude Desktop, and API access</p>
412388
</div>
413389
</div>
414-
<Button
415-
size="sm"
416-
onClick={() => setGenerateOpen(true)}
417-
disabled={activeKeys.length >= 5}
418-
className="gap-2 h-9 text-sm shrink-0"
419-
>
390+
<Button size="sm" onClick={() => setGenerateOpen(true)} disabled={activeKeys.length >= 5} className="gap-2 h-9 text-sm shrink-0">
420391
<Plus className="w-4 h-4" />
421392
Generate key
422393
{activeKeys.length >= 5 && <span className="text-xs opacity-60 ml-1">(limit reached)</span>}
423394
</Button>
424395
</div>
425396

426-
{/* Security note */}
427397
<div className="flex items-start gap-3 rounded-lg border border-border bg-muted/20 px-4 py-3.5">
428398
<Shield className="w-4 h-4 text-muted-foreground shrink-0 mt-0.5" />
429399
<div>
@@ -435,14 +405,12 @@ export function APIKeysPage() {
435405
</div>
436406
</div>
437407

438-
{/* Table or empty state */}
439408
{keys.length === 0 ? (
440409
<div className="rounded-lg border border-border">
441410
<EmptyState onGenerate={() => setGenerateOpen(true)} />
442411
</div>
443412
) : (
444413
<div className="rounded-lg border border-border overflow-hidden">
445-
{/* Column headers */}
446414
<div className="grid gap-4 px-4 py-2.5 bg-muted/40 border-b border-border grid-cols-[1fr_200px_80px_110px_120px]">
447415
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">Name</span>
448416
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">Key</span>
@@ -451,14 +419,8 @@ export function APIKeysPage() {
451419
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider text-right">Status</span>
452420
</div>
453421
{keys.map((k) => (
454-
<KeyRow
455-
key={k.id}
456-
apiKey={k}
457-
onRevoke={() => setRevokeTarget(k)}
458-
revoking={revoking === k.id}
459-
/>
422+
<KeyRow key={k.id} apiKey={k} onRevoke={() => setRevokeTarget(k)} revoking={revoking === k.id} />
460423
))}
461-
{/* Footer */}
462424
<div className="px-4 py-2.5 border-t border-border bg-muted/20 flex items-center justify-between">
463425
<span className="text-[11px] text-muted-foreground">
464426
{activeKeys.length} active{activeKeys.length !== keys.length && ` / ${keys.length} total`}
@@ -470,40 +432,9 @@ export function APIKeysPage() {
470432
</div>
471433
)}
472434

473-
{/* Modals */}
474-
<GenerateKeyModal
475-
open={generateOpen}
476-
onClose={() => setGenerateOpen(false)}
477-
onGenerate={handleGenerate}
478-
generating={generating}
479-
/>
480-
481-
<NewKeyModal
482-
apiKey={generatedKey}
483-
onClose={() => setGeneratedKey(null)}
484-
/>
485-
486-
<AlertDialog open={!!revokeTarget} onOpenChange={(open) => !open && setRevokeTarget(null)}>
487-
<AlertDialogContent className="max-w-sm">
488-
<AlertDialogHeader>
489-
<AlertDialogTitle className="text-base">Revoke key</AlertDialogTitle>
490-
<AlertDialogDescription>
491-
Revoke <span className="font-medium text-foreground">{revokeTarget?.name}</span>?
492-
Any services using this key will lose access immediately. This cannot be undone.
493-
</AlertDialogDescription>
494-
</AlertDialogHeader>
495-
<AlertDialogFooter>
496-
<AlertDialogCancel className="h-8 text-xs">Cancel</AlertDialogCancel>
497-
<AlertDialogAction
498-
onClick={handleRevoke}
499-
className="h-8 text-xs bg-destructive hover:bg-destructive/90 text-destructive-foreground"
500-
>
501-
Revoke key
502-
</AlertDialogAction>
503-
</AlertDialogFooter>
504-
</AlertDialogContent>
505-
</AlertDialog>
506-
435+
<GenerateKeyModal open={generateOpen} onClose={() => setGenerateOpen(false)} onGenerate={handleGenerate} generating={generating} />
436+
<NewKeyModal apiKey={generatedKey} onClose={() => setGeneratedKey(null)} />
437+
<RevokeConfirmModal target={revokeTarget} onConfirm={handleRevoke} onClose={() => setRevokeTarget(null)} />
507438
</div>
508439
)
509440
}

0 commit comments

Comments
 (0)