From b629d5a8a8b95c0fafebdc14482139ee35db83d8 Mon Sep 17 00:00:00 2001 From: Developing-Gamer Date: Wed, 27 May 2026 12:47:47 -0700 Subject: [PATCH] Add API key table for dashboard account settings. Co-authored-by: Cursor --- .../supporting/api-key-table.tsx | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-table.tsx diff --git a/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-table.tsx b/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-table.tsx new file mode 100644 index 000000000..e5fa1181e --- /dev/null +++ b/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-table.tsx @@ -0,0 +1,126 @@ +'use client'; +import { ActionCell, ActionDialog, BadgeCell, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from "@stackframe/stack-ui"; +import { ColumnDef, Row, Table } from "@tanstack/react-table"; +import { useMemo, useState } from "react"; +import { ApiKey } from "./types"; + +type ExtendedApiKey = ApiKey & { + status: 'valid' | 'expired' | 'revoked', +}; + +function toolbarRender(table: Table) { + return ( + <> + + ({ + value: provider, + label: provider, + }))} + /> + + ); +} + +function RevokeDialog(props: { + apiKey: ExtendedApiKey, + open: boolean, + onOpenChange: (open: boolean) => void, +}) { + return { await props.apiKey.revoke(); } }} + confirmText="I understand this will unlink all the apps using this API key" + > + {`Are you sure you want to revoke API key *****${props.apiKey.value.lastFour}?`} + ; +} + +function Actions({ row }: { row: Row }) { + const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false); + return ( + <> + + setIsRevokeModalOpen(true), + }]} + /> + + ); +} + +const columns: ColumnDef[] = [ + { + accessorKey: "description", + header: ({ column }) => , + cell: ({ row }) => {row.original.description}, + }, + { + accessorKey: "status", + header: ({ column }) => , + cell: ({ row }) => , + filterFn: standardFilterFn, + }, + { + id: "value", + accessorFn: (row) => row.value.lastFour, + header: ({ column }) => , + cell: ({ row }) => *******{row.original.value.lastFour}, + enableSorting: false, + }, + { + accessorKey: "expiresAt", + header: ({ column }) => , + cell: ({ row }) => { + if (row.original.status === 'revoked') return -; + return row.original.expiresAt ? : Never; + }, + }, + { + accessorKey: "createdAt", + header: ({ column }) => , + cell: ({ row }) => + }, + { + id: "actions", + cell: ({ row }) => , + }, +]; + +export function ApiKeyTable(props: { apiKeys: ApiKey[] }) { + const extendedApiKeys = useMemo(() => { + const keys = props.apiKeys.map((apiKey) => { + const map = { 'valid': 'valid', 'manually-revoked': 'revoked', 'expired': 'expired' } as const; + const why = apiKey.whyInvalid() || 'valid'; + return { + ...apiKey, + status: map[why as keyof typeof map], + } satisfies ExtendedApiKey; + }); + // first sort based on status, then by createdAt + return keys.sort((a, b) => { + if (a.status === b.status) { + return a.createdAt < b.createdAt ? 1 : -1; + } + return a.status === 'valid' ? -1 : 1; + }); + }, [props.apiKeys]); + + return ; +}