APIKeyRow.tsx 4.3 KB

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