APIKeyRow.tsx130 lines · main
1import { useFlag } from 'common'
2import { motion } from 'framer-motion'
3import { MoreVertical } from 'lucide-react'
4import {
5 Button,
6 DropdownMenu,
7 DropdownMenuContent,
8 DropdownMenuTrigger,
9 TableCell,
10 TableRow,
11} from 'ui'
12import { ShimmeringLoader, TimestampInfo } from 'ui-patterns'
13
14import { APIKeyDeleteDialog } from './APIKeyDeleteDialog'
15import { ApiKeyPill } from './ApiKeyPill'
16import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper'
17import type { APIKeysData } from '@/data/api-keys/api-keys-query'
18
19export const APIKeyRow = ({
20 apiKey,
21 lastSeen,
22 isDeleting,
23 isDeleteModalOpen,
24 isLoadingLastSeen = false,
25 showLastSeen = true,
26 onDelete,
27 setKeyToDelete,
28}: {
29 apiKey: Extract<APIKeysData[number], { type: 'secret' | 'publishable' }>
30 lastSeen?: { timestamp: number; relative: string }
31 showLastSeen?: boolean
32 isDeleting: boolean
33 isDeleteModalOpen: boolean
34 isLoadingLastSeen?: boolean
35 onDelete: () => void
36 setKeyToDelete: (id: string | null) => void
37}) => {
38 const MotionTableRow = motion.create(TableRow)
39 const showApiKeysLastUsed = useFlag('showApiKeysLastUsed')
40
41 return (
42 <>
43 <MotionTableRow
44 layout
45 initial={{ opacity: 0, height: 0 }}
46 animate={{ opacity: 1, height: 'auto' }}
47 exit={{ opacity: 0, height: 0 }}
48 transition={{
49 type: 'spring',
50 stiffness: 500,
51 damping: 50,
52 mass: 1,
53 }}
54 >
55 <TableCell className="py-2 w-56">
56 <div className="flex flex-col">
57 <span className="font-medium">{apiKey.name}</span>
58 <div className="text-sm text-foreground-lighter">
59 {apiKey.description || <span className="text-foreground-muted">No description</span>}
60 </div>
61 </div>
62 </TableCell>
63
64 <TableCell className="py-2">
65 <div className="flex flex-row gap-2">
66 <ApiKeyPill apiKey={apiKey} />
67 </div>
68 </TableCell>
69
70 {showLastSeen && showApiKeysLastUsed && (
71 <TableCell className="py-2 min-w-0 whitespace-nowrap hidden lg:table-cell">
72 <div className="truncate" title={lastSeen?.timestamp.toString() || 'Never used'}>
73 {isLoadingLastSeen ? (
74 <ShimmeringLoader />
75 ) : lastSeen?.timestamp ? (
76 <TimestampInfo
77 className="text-sm"
78 utcTimestamp={lastSeen?.timestamp}
79 label={lastSeen.relative}
80 />
81 ) : (
82 <span className="text-foreground-lighter">Never used</span>
83 )}
84 </div>
85 </TableCell>
86 )}
87
88 <TableCell className="py-2">
89 <div className="flex justify-end">
90 <DropdownMenu>
91 <DropdownMenuTrigger className="px-1 focus-visible:outline-hidden" asChild>
92 <Button
93 type="text"
94 size="tiny"
95 icon={
96 <MoreVertical
97 size="14"
98 className="text-foreground-light hover:text-foreground"
99 />
100 }
101 />
102 </DropdownMenuTrigger>
103 <DropdownMenuContent className="max-w-40" align="end">
104 <APIKeyDeleteDialog apiKey={apiKey} setKeyToDelete={setKeyToDelete} />
105 </DropdownMenuContent>
106 </DropdownMenu>
107 </div>
108 </TableCell>
109 </MotionTableRow>
110
111 <TextConfirmModal
112 visible={isDeleteModalOpen}
113 onCancel={() => setKeyToDelete(null)}
114 onConfirm={onDelete}
115 title={`Delete ${apiKey.type} API key: ${apiKey.name}`}
116 confirmString={apiKey.name}
117 confirmLabel="Yes, irreversibly delete this API key"
118 confirmPlaceholder="Type the name of the API key to confirm"
119 loading={isDeleting}
120 variant="destructive"
121 alert={{
122 title: 'This cannot be undone',
123 description: lastSeen
124 ? `This API key was used ${lastSeen.timestamp}. Make sure all backend components using it have been updated. Deletion will cause them to receive HTTP 401 Unauthorized status codes on all Briven APIs.`
125 : `This API key has not been used in the past 24 hours. Make sure you've updated all backend components using it before deletion.`,
126 }}
127 />
128 </>
129 )
130}