| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- import { MoreVertical, Trash } from 'lucide-react'
- import { parseAsStringLiteral, useQueryState } from 'nuqs'
- import { useMemo, useState } from 'react'
- import { toast } from 'sonner'
- import {
- Button,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- } from 'ui'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import { TableCell, TableRow } from 'ui/src/components/shadcn/ui/table'
- import {
- ACCESS_TOKEN_SORT_VALUES,
- AccessTokenSort,
- AccessTokenSortColumn,
- } from './AccessToken.types'
- import { filterAndSortTokens, handleSortChange } from './AccessToken.utils'
- import { RowLoading } from './AccessTokenTable/RowLoading'
- import { TableContainer } from './AccessTokenTable/TableContainer'
- import { ExpiresCell, LastUsedCell, TokenNameCell } from './AccessTokenTable/TokenCells'
- import AlertError from '@/components/ui/AlertError'
- import { useAccessTokenDeleteMutation } from '@/data/access-tokens/access-tokens-delete-mutation'
- import { AccessToken, useAccessTokensQuery } from '@/data/access-tokens/access-tokens-query'
- import { useTrack } from '@/lib/telemetry/track'
- export interface AccessTokenListProps {
- searchString?: string
- onDeleteSuccess: (id: number) => void
- }
- export const AccessTokenList = ({ searchString = '', onDeleteSuccess }: AccessTokenListProps) => {
- const track = useTrack()
- const [isOpen, setIsOpen] = useState(false)
- const [token, setToken] = useState<AccessToken | undefined>(undefined)
- const [sort, setSort] = useQueryState(
- 'sort',
- parseAsStringLiteral<AccessTokenSort>(ACCESS_TOKEN_SORT_VALUES).withDefault('created_at:desc')
- )
- const { data: tokens, error, isPending: isLoading, isError } = useAccessTokensQuery()
- const { mutate: deleteToken } = useAccessTokenDeleteMutation({
- onSuccess: (_, vars) => {
- track('access_token_removed', { tokenType: 'classic' })
- onDeleteSuccess(vars.id)
- toast.success('Successfully deleted access token')
- setIsOpen(false)
- },
- onError: (error) => {
- toast.error(`Failed to delete access token: ${error.message}`)
- },
- })
- const onSortChange = (column: AccessTokenSortColumn) => {
- handleSortChange(sort, column, setSort)
- }
- const filteredTokens = useMemo(
- () => filterAndSortTokens(tokens, searchString, sort),
- [tokens, searchString, sort]
- )
- const empty = filteredTokens?.length === 0 && !isLoading
- if (isError) {
- return (
- <TableContainer sort={sort} onSortChange={onSortChange}>
- <TableRow>
- <TableCell colSpan={4} className="p-0">
- <AlertError
- error={error}
- subject="Failed to retrieve access tokens"
- className="rounded-none border-0"
- />
- </TableCell>
- </TableRow>
- </TableContainer>
- )
- }
- if (isLoading) {
- return (
- <TableContainer sort={sort} onSortChange={onSortChange}>
- <RowLoading />
- <RowLoading />
- </TableContainer>
- )
- }
- if (empty) {
- return (
- <TableContainer sort={sort} onSortChange={onSortChange}>
- <TableRow>
- <TableCell colSpan={4} className="py-12">
- <p className="text-sm text-center text-foreground">No access tokens found</p>
- <p className="text-sm text-center text-foreground-light">
- You do not have any tokens created yet
- </p>
- </TableCell>
- </TableRow>
- </TableContainer>
- )
- }
- return (
- <>
- <TableContainer sort={sort} onSortChange={onSortChange}>
- {filteredTokens?.map((x) => (
- <TableRow key={x.token_alias}>
- <TokenNameCell name={x.name} tokenAlias={x.token_alias} />
- <LastUsedCell lastUsedAt={x.last_used_at} />
- <ExpiresCell expiresAt={x.expires_at} />
- <TableCell>
- <div className="flex items-center justify-end gap-x-2">
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- type="default"
- title="More options"
- className="w-7"
- icon={<MoreVertical />}
- />
- </DropdownMenuTrigger>
- <DropdownMenuContent side="bottom" align="end" className="w-40">
- <DropdownMenuItem
- className="gap-x-2"
- onClick={() => {
- setToken(x)
- setIsOpen(true)
- }}
- >
- <Trash size={12} />
- <p>Delete token</p>
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- </TableCell>
- </TableRow>
- ))}
- </TableContainer>
- <ConfirmationModal
- visible={isOpen}
- variant="destructive"
- title="Confirm to delete"
- confirmLabel="Delete"
- confirmLabelLoading="Deleting"
- onCancel={() => setIsOpen(false)}
- onConfirm={() => {
- if (token) deleteToken({ id: token.id })
- }}
- >
- <p className="py-4 text-sm text-foreground-light">
- This action cannot be undone. Are you sure you want to delete "{token?.name}" token?
- </p>
- </ConfirmationModal>
- </>
- )
- }
|