Skip to content
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";
import { shortenId } from "@/lib/shorten-id";
import { trpc } from "@/lib/trpc/client";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -449,9 +450,10 @@ export const KeyDetailsLogsTable = ({ keyspaceId, keyId, selectedLog, onLogSelec
hide: isLoading,
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{historicalLogs.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(historicalLogs.length)}</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>requests</span>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";
import { shortenId } from "@/lib/shorten-id";
import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema";
import { BookBookmark, Dots, Focus, Key } from "@unkey/icons";
Expand Down Expand Up @@ -28,7 +29,7 @@ import { getRowClassName } from "./utils/get-row-class";
const KeysTableActionPopover = dynamic(
() =>
import("./components/actions/keys-table-action.popover.constants").then(
(mod) => mod.KeysTableActions,
(mod) => mod.KeysTableActions
),
{
ssr: false,
Expand All @@ -37,13 +38,16 @@ const KeysTableActionPopover = dynamic(
type="button"
className={cn(
"group-data-[state=open]:bg-gray-6 group-hover:bg-gray-6 group size-5 p-0 rounded m-0 items-center flex justify-center",
"border border-gray-6 group-hover:border-gray-8 ring-2 ring-transparent focus-visible:ring-gray-7 focus-visible:border-gray-7",
"border border-gray-6 group-hover:border-gray-8 ring-2 ring-transparent focus-visible:ring-gray-7 focus-visible:border-gray-7"
)}
>
<Dots className="group-hover:text-gray-12 text-gray-11" size="sm-regular" />
<Dots
className="group-hover:text-gray-12 text-gray-11"
size="sm-regular"
/>
</button>
),
},
}
);

export const KeysList = ({
Expand All @@ -53,9 +57,10 @@ export const KeysList = ({
keyspaceId: string;
apiId: string;
}) => {
const { keys, isLoading, isLoadingMore, loadMore, totalCount, hasMore } = useKeysListQuery({
keyAuthId: keyspaceId,
});
const { keys, isLoading, isLoadingMore, loadMore, totalCount, hasMore } =
useKeysListQuery({
keyAuthId: keyspaceId,
});
const [selectedKey, setSelectedKey] = useState<KeyDetails | null>(null);
const [navigatingKeyId, setNavigatingKeyId] = useState<string | null>(null);
// Add state for selected keys
Expand Down Expand Up @@ -98,13 +103,17 @@ export const KeysList = ({
className={cn(
"size-5 rounded flex items-center justify-center cursor-pointer",
identity ? "bg-successA-3" : "bg-grayA-3",
isSelected && "bg-brand-5",
isSelected && "bg-brand-5"
)}
onMouseEnter={() => setHoveredKeyId(key.id)}
onMouseLeave={() => setHoveredKeyId(null)}
>
{isNavigating ? (
<div className={cn(identity ? "text-successA-11" : "text-grayA-11")}>
<div
className={cn(
identity ? "text-successA-11" : "text-grayA-11"
)}
>
<Loading size={18} />
</div>
) : (
Expand Down Expand Up @@ -155,17 +164,24 @@ export const KeysList = ({
rel="noopener noreferrer"
aria-disabled={isNavigating}
>
<span className="font-mono bg-gray-4 p-1 rounded">{identity}</span>
<span className="font-mono bg-gray-4 p-1 rounded">
{identity}
</span>
</Link>
) : (
<span className="font-mono bg-gray-4 p-1 rounded">{identity}</span>
<span className="font-mono bg-gray-4 p-1 rounded">
{identity}
</span>
)}
Comment on lines +177 to 185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Make the tooltip trigger focusable for keyboard users

iconContainer is a div; with asChild, the trigger won’t be focusable. Add tabIndex and an aria-label for a11y.

-                    {React.cloneElement(iconContainer, {
-                      className: cn(
-                        iconContainer.props.className,
-                        "cursor-pointer"
-                      ),
-                    })}
+                    {React.cloneElement(iconContainer, {
+                      className: cn(iconContainer.props.className, "cursor-pointer"),
+                      tabIndex: 0,
+                      role: "button",
+                      "aria-label": "Key identity info",
+                    })}

Also applies to: 179-184

🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
around lines 166-174 (and likewise 179-184), the tooltip trigger is using a div
asChild which is not keyboard-focusable; make the trigger accessible by either
replacing the div with a semantic focusable element (e.g., a button) or adding
tabIndex={0} and a descriptive aria-label to the existing element so it can
receive keyboard focus and convey its purpose to assistive tech; apply the same
change to the second occurrence at lines 179-184.

</>
}
asChild
>
{React.cloneElement(iconContainer, {
className: cn(iconContainer.props.className, "cursor-pointer"),
className: cn(
iconContainer.props.className,
"cursor-pointer"
),
})}
</InfoTooltip>
) : (
Expand Down Expand Up @@ -205,7 +221,11 @@ export const KeysList = ({
header: "Value",
width: "15%",
render: (key) => (
<HiddenValueCell value={key.start} title="Value" selected={selectedKey?.id === key.id} />
<HiddenValueCell
value={key.start}
title="Value"
selected={selectedKey?.id === key.id}
/>
),
},
{
Expand Down Expand Up @@ -266,7 +286,7 @@ export const KeysList = ({
selectedKeys,
toggleSelection,
hoveredKeyId,
],
]
);

const getSelectedKeysState = useCallback(() => {
Expand All @@ -291,17 +311,15 @@ export const KeysList = ({

// Early exit if we already found a mix
if (!allEnabled && !allDisabled) {
break;
return "mixed";
}
}

if (allEnabled) {
return "all-enabled";
}
if (allDisabled) {
return "all-disabled";
}
return "mixed";

return "all-disabled";
}, [selectedKeys, keys]);

return (
Expand Down Expand Up @@ -330,9 +348,12 @@ export const KeysList = ({
),
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{keys.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">
{formatNumberFull(keys.length)}
</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>keys</span>
</div>
Comment on lines +361 to 368

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reuse a memoized number formatter

Apply the shared formatter to keep consistency with the rest of the PR.

-              <span className="text-accent-12">
-                {new Intl.NumberFormat().format(keys.length)}
-              </span>
+              <span className="text-accent-12">{nf.format(keys.length)}</span>
...
-              {new Intl.NumberFormat().format(totalCount)}
+              {nf.format(totalCount)}

Support (outside selected lines):

import React, { useCallback, useMemo, useState } from "react";
const nf = useMemo(() => new Intl.NumberFormat(), []);
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
around lines 352 to 359, the code creates new Intl.NumberFormat instances inline
instead of reusing the shared memoized formatter; replace both occurrences of
new Intl.NumberFormat().format(...) with the existing nf.format(...) and ensure
nf is defined with useMemo (const nf = useMemo(() => new Intl.NumberFormat(),
[])) in the component scope so the shared, memoized formatter is used
consistently across the file.

),
Expand All @@ -343,8 +364,8 @@ export const KeysList = ({
<Empty.Icon className="w-auto" />
<Empty.Title>No API Keys Found</Empty.Title>
<Empty.Description className="text-left">
There are no API keys associated with this service yet. Create your first API key to
get started.
There are no API keys associated with this service yet. Create
your first API key to get started.
</Empty.Description>
<Empty.Actions className="mt-4 justify-start">
<a
Expand Down Expand Up @@ -374,7 +395,7 @@ export const KeysList = ({
className={cn(
"text-xs align-middle whitespace-nowrap pr-4",
idx === 0 ? "pl-[18px]" : "",
column.key === "key" ? "py-[6px]" : "py-1",
column.key === "key" ? "py-[6px]" : "py-1"
)}
style={{ height: `${rowHeight}px` }}
>
Expand All @@ -384,9 +405,17 @@ export const KeysList = ({
{column.key === "last_used" && <LastUsedColumnSkeleton />}
{column.key === "status" && <StatusColumnSkeleton />}
{column.key === "action" && <ActionColumnSkeleton />}
{!["select", "key", "value", "usage", "last_used", "status", "action"].includes(
column.key,
) && <div className="h-4 w-full bg-grayA-3 rounded animate-pulse" />}
{![
"select",
"key",
"value",
"usage",
"last_used",
"status",
"action",
].includes(column.key) && (
<div className="h-4 w-full bg-grayA-3 rounded animate-pulse" />
)}
</td>
))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { EmptyComponentSpacer } from "@/components/empty-component-spacer";
import { formatNumberFull } from "@/lib/fmt";
import { trpc } from "@/lib/trpc/client";
import { BookBookmark } from "@unkey/icons";
import { Button, Empty } from "@unkey/ui";
Expand Down Expand Up @@ -81,7 +82,8 @@ export const ApiListClient = () => {

<div className="flex flex-col items-center justify-center mt-8 space-y-4 pb-8">
<div className="text-center text-sm text-accent-11">
Showing {apiList.length} of {apisData?.pages[0]?.total || 0} APIs
Showing {formatNumberFull(apiList.length)} of{" "}
{formatNumberFull(apisData?.pages[0]?.total || 0)} APIs
</div>

{!isSearching && hasNextPage && (
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EmptyComponentSpacer } from "@/components/empty-component-spacer";
import { formatNumberFull } from "@/lib/fmt";
import type { ApiOverview } from "@/lib/trpc/routers/api/overview/query-overview/schemas";
import { ChevronDown } from "@unkey/icons";
import { Button, Empty } from "@unkey/ui";
Expand Down Expand Up @@ -43,7 +44,7 @@ export const ApiListGrid = ({

<div className="flex flex-col items-center justify-center mt-8 space-y-4 pb-8">
<div className="text-center text-sm text-accent-11">
Showing {apiList.length} of {total} APIs
Showing {formatNumberFull(apiList.length)} of {formatNumberFull(total)} APIs
</div>

{!isSearching && hasMore && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";
import type { Permission } from "@/lib/trpc/routers/authorization/permissions/query";
import { BookBookmark, Page2 } from "@unkey/icons";
import { Button, Checkbox, Empty } from "@unkey/ui";
Expand Down Expand Up @@ -182,9 +183,10 @@ export const PermissionsList = () => {
),
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{permissions.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(permissions.length)}</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>permissions</span>
</div>
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";
import type { RoleBasic } from "@/lib/trpc/routers/authorization/roles/query";
import { BookBookmark, Tag } from "@unkey/icons";
import { Button, Checkbox, Empty } from "@unkey/ui";
Expand Down Expand Up @@ -166,9 +167,10 @@ export const RolesList = () => {
),
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{roles.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(roles.length)}</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>roles</span>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
),
Expand Down
6 changes: 4 additions & 2 deletions apps/dashboard/app/(app)/logs/components/table/logs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";
import { cn } from "@/lib/utils";
import type { Log } from "@unkey/clickhouse/src/logs";
import { BookBookmark, TriangleWarning2 } from "@unkey/icons";
Expand Down Expand Up @@ -250,9 +251,10 @@ export const LogsTable = () => {
hasMore,
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{historicalLogs.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(historicalLogs.length)}</span>
<span>of</span>
{total}
{formatNumberFull(total)}
<span>requests</span>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { useIsMobile } from "@/hooks/use-mobile";
import { formatNumberFull } from "@/lib/fmt";
import { shortenId } from "@/lib/shorten-id";
import type { Deployment } from "@/lib/trpc/routers/deploy/project/deployment/list";
import { BookBookmark, Cloud, CodeBranch, Cube } from "@unkey/icons";
Expand Down Expand Up @@ -296,9 +297,10 @@ export const DeploymentsList = () => {
hasMore,
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{deployments.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(deployments.length)}</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>deployments</span>
</div>
),
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/app/(app)/projects/_components/list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LoadMoreFooter } from "@/components/virtual-table/components/loading-indicator";
import { formatNumberFull } from "@/lib/fmt";
import { BookBookmark, Dots } from "@unkey/icons";
import { Button, Empty } from "@unkey/ui";
import { useProjectsListQuery } from "./hooks/use-projects-list-query";
Expand Down Expand Up @@ -111,9 +112,9 @@ export const ProjectsList = () => {
countInfoText={
<div className="flex gap-2">
<span>Viewing</span>
<span className="text-accent-12">{projects.length}</span>
<span className="text-accent-12">{formatNumberFull(projects.length)}</span>
<span>of</span>
<span className="text-grayA-12">{totalCount}</span>
<span className="text-grayA-12">{formatNumberFull(totalCount)}</span>
<span>projects</span>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useSort } from "@/components/logs/hooks/use-sort";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumber } from "@/lib/fmt";
import { formatNumber, formatNumberFull } from "@/lib/fmt";
import { cn } from "@/lib/utils";
import type { RatelimitOverviewLog } from "@unkey/clickhouse/src/ratelimits";
import { Ban, BookBookmark } from "@unkey/icons";
Expand Down Expand Up @@ -218,8 +218,9 @@ export const RatelimitOverviewLogsTable = ({
hide: isLoading,
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{historicalLogs.length}</span>
<span>of {totalCount}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(historicalLogs.length)}</span>
<span>of {formatNumberFull(totalCount)}</span>
<span>rate limit identifiers</span>
</div>
Comment on lines +221 to 225

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use shared formatter for footer counts

You already import formatNumber; reuse it here too.

-            <span>Showing</span>{" "}
-            <span className="text-accent-12">
-              {new Intl.NumberFormat().format(historicalLogs.length)}
-            </span>
-            <span>of {new Intl.NumberFormat().format(totalCount)}</span>
+            <span>Showing</span> <span className="text-accent-12">{formatNumber(historicalLogs.length)}</span>
+            <span>of {formatNumber(totalCount)}</span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span>Showing</span>{" "}
<span className="text-accent-12">
{new Intl.NumberFormat().format(historicalLogs.length)}
</span>
<span>of {new Intl.NumberFormat().format(totalCount)}</span>
<span>rate limit identifiers</span>
</div>
<span>Showing</span> <span className="text-accent-12">{formatNumber(historicalLogs.length)}</span>
<span>of {formatNumber(totalCount)}</span>
<span>rate limit identifiers</span>
</div>
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/logs-table.tsx
around lines 235 to 241, the footer uses new Intl.NumberFormat().format(...) for
counts; replace both usages with the already-imported formatNumber(...) helper
so the component uses the shared formatter consistently, e.g. call
formatNumber(historicalLogs.length) and formatNumber(totalCount) in those spans.

),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { safeParseJson } from "@/app/(app)/logs/utils";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { formatNumberFull } from "@/lib/fmt";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don't force en-US; honor user locale and cache formatter

Import looks good, but current formatNumberFull uses Intl.NumberFormat("en-US"), which conflicts with the PR goal of “locale-appropriate” formatting and allocates a formatter per call. Switch the helper to default-locale (or param) and cache per locale.

Support (outside selected lines, apps/dashboard/lib/fmt.ts):

-export function formatNumberFull(n: number): string {
-  return Intl.NumberFormat("en-US").format(n);
-}
+const nfCache = new Map<string | undefined, Intl.NumberFormat>();
+function getNF(locale?: string) {
+  if (!nfCache.has(locale)) nfCache.set(locale, new Intl.NumberFormat(locale));
+  return nfCache.get(locale)!;
+}
+export function formatNumberFull(n: number, locale?: string): string {
+  return getNF(locale).format(n);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { formatNumberFull } from "@/lib/fmt";
// apps/dashboard/lib/fmt.ts
const nfCache = new Map<string | undefined, Intl.NumberFormat>();
function getNF(locale?: string) {
if (!nfCache.has(locale)) nfCache.set(locale, new Intl.NumberFormat(locale));
return nfCache.get(locale)!;
}
export function formatNumberFull(n: number, locale?: string): string {
return getNF(locale).format(n);
}

import { cn } from "@/lib/utils";
import type { RatelimitLog } from "@unkey/clickhouse/src/ratelimits";
import { BookBookmark } from "@unkey/icons";
Expand Down Expand Up @@ -218,9 +219,10 @@ export const RatelimitLogsTable = () => {
hide: isLoading,
countInfoText: (
<div className="flex gap-2">
<span>Showing</span> <span className="text-accent-12">{historicalLogs.length}</span>
<span>Showing</span>{" "}
<span className="text-accent-12">{formatNumberFull(historicalLogs.length)}</span>
<span>of</span>
{totalCount}
{formatNumberFull(totalCount)}
<span>ratelimit requests</span>
</div>
Comment on lines +224 to 229

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reuse a memoized number formatter

Apply the shared formatter here too.

-            <span className="text-accent-12">
-              {new Intl.NumberFormat().format(historicalLogs.length)}
-            </span>
+            <span className="text-accent-12">{nf.format(historicalLogs.length)}</span>
...
-            {new Intl.NumberFormat().format(totalCount)}
+            {nf.format(totalCount)}

Support (outside selected lines):

import { useMemo } from "react";
const nf = useMemo(() => new Intl.NumberFormat(), []);
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/logs-table.tsx
around lines 238 to 245, the code creates new Intl.NumberFormat() inline twice;
replace those inline constructors with a shared, memoized formatter (e.g.,
define const nf = useMemo(() => new Intl.NumberFormat(), [] ) at the top of the
component) and then use nf.format(...) for both historicalLogs.length and
totalCount; ensure useMemo is imported from react and that nf is declared inside
the component so it is reused across renders.

),
Expand Down
Loading