feat: add Agent Sign-Ups admin page#26
Conversation
New admin page at /agent-signups that displays API key sign-up records from AI agents. Includes line chart over time, pie chart by email, sortable data table, and period filtering. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughA new "Agent Sign-Ups" admin page feature is introduced, comprising route configuration, page components, data fetching hooks, table rendering with TanStack React Table, utility functions for data aggregation, and TypeScript type definitions. The admin dashboard navigation is updated to include a link to the new page. Changes
Sequence DiagramsequenceDiagram
participant Client as AgentSignupsPage
participant Hook as useAgentSignups
participant Privy as Privy Auth
participant API as fetchAgentSignups
participant Server as Backend API
participant Table as AgentSignupsTable
Client->>Hook: useAgentSignups(period)
Hook->>Privy: usePrivy()
Privy-->>Hook: ready, authenticated, getAccessToken
alt When ready && authenticated
Hook->>Hook: useQuery enabled
Hook->>API: Call with token & period
API->>Privy: getAccessToken()
Privy-->>API: Access token
API->>Server: GET /api/admins/agent-signups?period=X<br/>(Authorization: Bearer token)
Server-->>API: AgentSignupsResponse
API-->>Hook: Parsed data or error
end
Hook-->>Client: {data, isLoading, error}
alt Loading state
Client->>Client: Show ChartSkeleton & TableSkeleton
else Error state
Client->>Client: Display error message
else Empty state
Client->>Client: Show "No agent sign-ups found"
else Success state
Client->>Client: Transform signups via getSignupsByDate()
Client->>Table: Pass signups array
Table-->>Client: Rendered table with sorting
Client->>Client: Render AdminLineChart with date/count data
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
lib/recoup/fetchAgentSignups.ts (1)
12-14: Prefer explicit no-store for admin analytics fetches.Line 12 currently relies on default fetch caching semantics. For admin analytics, being explicit helps avoid stale reads.
♻️ Proposed fix
const res = await fetch(url.toString(), { + cache: "no-store", headers: { Authorization: `Bearer ${accessToken}` }, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/recoup/fetchAgentSignups.ts` around lines 12 - 14, The fetch call that retrieves admin analytics in lib/recoup/fetchAgentSignups.ts (the call assigning to res using url and accessToken) should explicitly disable caching to avoid stale reads; update the fetch options for that request to include cache: 'no-store' alongside the Authorization header so the request is always fetched fresh.components/AgentSignups/AgentSignupsStats.tsx (1)
11-11: Format totals for readability.Consider
toLocaleString()fordata.totalso larger counts are easier to scan in the stats bar.♻️ Proposed tweak
- <span className="font-semibold text-gray-900 dark:text-gray-100">{data.total}</span> sign-ups + <span className="font-semibold text-gray-900 dark:text-gray-100"> + {data.total.toLocaleString()} + </span>{" "} + sign-ups🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/AgentSignups/AgentSignupsStats.tsx` at line 11, Format the displayed total using toLocaleString() for readability: update the rendering in AgentSignupsStats (the span currently rendering {data.total}) to call data.total?.toLocaleString() or (Number(data.total) || 0).toLocaleString() so large counts show with separators and you safely handle null/undefined values; keep the surrounding markup and className on the same span.lib/agent-signups/getSignupsByEmail.ts (1)
15-16: Align fallback logic with the declared type contract.Line 15 treats
AgentSignup.emailis required. Either tighten runtime assumptions (no fallback) or relax the type to match real API variability.♻️ Proposed fix (strict contract path)
- const key = signup.email || "Unknown"; + const key = signup.email; counts[key] = (counts[key] || 0) + 1;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/agent-signups/getSignupsByEmail.ts` around lines 15 - 16, The code currently falls back to "Unknown" for signup.email even though AgentSignup.email is declared required; update getSignupsByEmail logic to trust the type contract by removing the fallback (do not use "|| 'Unknown'") and use signup.email directly as the key, and add a runtime guard in the same function (e.g., throw or log and skip) if signup.email is unexpectedly undefined/null to fail fast; reference the variables signup.email, key, and counts so you can locate and update that assignment accordingly.components/Home/AdminDashboard.tsx (1)
17-17: Prevent nav overflow as links keep growing.Adding this button increases the chance of horizontal overflow because the nav stays single-row (Line 11). Consider enabling wrapping for better small-screen behavior.
♻️ Proposed fix
- <nav className="flex gap-4"> + <nav className="flex flex-wrap justify-center gap-4">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/Home/AdminDashboard.tsx` at line 17, The new NavButton ("Agent Sign-Ups") can cause horizontal overflow because the nav container in AdminDashboard is a single-row flex container; update the nav/container element that wraps NavButton components in AdminDashboard to allow wrapping (e.g., enable flex-wrap or equivalent CSS) and add appropriate gap/margin so buttons stack to multiple rows on small screens; ensure the NavButton items still align and spacing remains consistent after enabling wrap.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/AgentSignups/agentSignupsColumns.tsx`:
- Around line 25-29: The column uses accessorKey: "created_at" (producing
strings) but declares sortingFn: "datetime" which expects Date objects; change
the column to keep id: "created_at" but replace accessorKey with accessorFn that
parses the row value into a Date (e.g., new Date(row.created_at)) so the sorter
can call .getTime(); update the cell renderer to treat getValue as a Date (call
getValue<Date>() and then .toLocaleString()) and keep header as SortableHeader
to preserve UI behavior.
---
Nitpick comments:
In `@components/AgentSignups/AgentSignupsStats.tsx`:
- Line 11: Format the displayed total using toLocaleString() for readability:
update the rendering in AgentSignupsStats (the span currently rendering
{data.total}) to call data.total?.toLocaleString() or (Number(data.total) ||
0).toLocaleString() so large counts show with separators and you safely handle
null/undefined values; keep the surrounding markup and className on the same
span.
In `@components/Home/AdminDashboard.tsx`:
- Line 17: The new NavButton ("Agent Sign-Ups") can cause horizontal overflow
because the nav container in AdminDashboard is a single-row flex container;
update the nav/container element that wraps NavButton components in
AdminDashboard to allow wrapping (e.g., enable flex-wrap or equivalent CSS) and
add appropriate gap/margin so buttons stack to multiple rows on small screens;
ensure the NavButton items still align and spacing remains consistent after
enabling wrap.
In `@lib/agent-signups/getSignupsByEmail.ts`:
- Around line 15-16: The code currently falls back to "Unknown" for signup.email
even though AgentSignup.email is declared required; update getSignupsByEmail
logic to trust the type contract by removing the fallback (do not use "||
'Unknown'") and use signup.email directly as the key, and add a runtime guard in
the same function (e.g., throw or log and skip) if signup.email is unexpectedly
undefined/null to fail fast; reference the variables signup.email, key, and
counts so you can locate and update that assignment accordingly.
In `@lib/recoup/fetchAgentSignups.ts`:
- Around line 12-14: The fetch call that retrieves admin analytics in
lib/recoup/fetchAgentSignups.ts (the call assigning to res using url and
accessToken) should explicitly disable caching to avoid stale reads; update the
fetch options for that request to include cache: 'no-store' alongside the
Authorization header so the request is always fetched fresh.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 08480d54-c2e2-4b2f-af07-175a9780d9b3
📒 Files selected for processing (11)
app/agent-signups/page.tsxcomponents/AgentSignups/AgentSignupsPage.tsxcomponents/AgentSignups/AgentSignupsStats.tsxcomponents/AgentSignups/AgentSignupsTable.tsxcomponents/AgentSignups/agentSignupsColumns.tsxcomponents/Home/AdminDashboard.tsxhooks/useAgentSignups.tslib/agent-signups/getSignupsByDate.tslib/agent-signups/getSignupsByEmail.tslib/recoup/fetchAgentSignups.tstypes/agentSignups.ts
| id: "created_at", | ||
| accessorKey: "created_at", | ||
| header: ({ column }) => <SortableHeader column={column} label="Created At" />, | ||
| cell: ({ getValue }) => new Date(getValue<string>()).toLocaleString(), | ||
| sortingFn: "datetime", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In @tanstack/react-tablev8, does built-insortingFn: "datetime"require accessor values to beDate objects, or is ISO date string input officially supported?
💡 Result:
No, the built-in sortingFn: "datetime" in @tanstack/react-table v8 requires accessor values to be Date objects. ISO date strings are not officially supported by this sorting function. The official TanStack Table v8 documentation explicitly states: "datetime - Sorts by time, use this if your values are Date objects." This appears consistently across the Sorting Guide and Sorting APIs documentation. Evidence from community discussions and issues confirms this: - Users report errors like "a.getTime is not a function" when using strings with datetime sortingFn, indicating it calls .getTime expecting Date objects. - Recommended pattern: Keep raw Date objects as accessor values for sorting, and format them only in the cell renderer (e.g., using accessorKey: 'dateField', sortingFn: 'datetime', cell: ({ value }) => formatDate(value)). - Auto-detection in getAutoSortingFn checks Object.prototype.toString.call(value) === '[object Date]' before selecting sortingFns.datetime. - For strings (including ISO), it falls back to text/alphanumeric/basic sorting, which works correctly for ISO strings due to lexical sorting properties but is not the datetime fn. While ISO strings may sort correctly with other fns like basic (due to ISO format), using sortingFn: "datetime" with strings will fail or behave unexpectedly, as it is designed exclusively for Date objects.
Citations:
- 1: https://tanstack.com/table/v8/docs/guide/sorting
- 2: https://tanstack.com/table/v8/docs/api/features/sorting?from=reactTableV7
- 3: https://tanstack.com/table/v8/docs/api/features/sorting
- 4: https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowSorting.ts
- 5: displaying and sorting by date TanStack/table#2941
- 6: Automatic datetime sorting function crashes when null values exist TanStack/table#4041
🏁 Script executed:
# Find package.json or similar to check TanStack version
find . -name "package.json" -o -name "package-lock.json" -o -name "yarn.lock" | head -5Repository: recoupable/admin
Length of output: 389
🏁 Script executed:
# Check for `@tanstack/react-table` dependency
rg "@tanstack/react-table" --max-count 5 -A 1Repository: recoupable/admin
Length of output: 3064
🏁 Script executed:
# Search for other uses of accessorFn in the codebase to verify the pattern
rg "accessorFn:" -B 2 -A 2Repository: recoupable/admin
Length of output: 1224
🏁 Script executed:
# Read the agentSignupsColumns.tsx file to see the full context
cat -n components/AgentSignups/agentSignupsColumns.tsxRepository: recoupable/admin
Length of output: 1232
🏁 Script executed:
# Check the AgentSignup type definition to confirm created_at is a string
rg "type AgentSignup" -A 10Repository: recoupable/admin
Length of output: 538
Fix datetime sorting with Date accessor, not string accessor.
The sortingFn: "datetime" on line 29 requires Date objects but receives string values from accessorKey: "created_at" (line 26). TanStack Table v8's datetime sorter calls .getTime() on values, which will fail for strings. Use accessorFn to convert the string to a Date object for correct sorting:
Corrected column definition
{
id: "created_at",
- accessorKey: "created_at",
+ accessorFn: (row) => new Date(row.created_at),
header: ({ column }) => <SortableHeader column={column} label="Created At" />,
- cell: ({ getValue }) => new Date(getValue<string>()).toLocaleString(),
+ cell: ({ row }) => new Date(row.original.created_at).toLocaleString(),
sortingFn: "datetime",
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/AgentSignups/agentSignupsColumns.tsx` around lines 25 - 29, The
column uses accessorKey: "created_at" (producing strings) but declares
sortingFn: "datetime" which expects Date objects; change the column to keep id:
"created_at" but replace accessorKey with accessorFn that parses the row value
into a Date (e.g., new Date(row.created_at)) so the sorter can call .getTime();
update the cell renderer to treat getValue as a Date (call getValue<Date>() and
then .toLocaleString()) and keep header as SortableHeader to preserve UI
behavior.
There was a problem hiding this comment.
2 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/agent-signups/getSignupsByEmail.ts">
<violation number="1" location="lib/agent-signups/getSignupsByEmail.ts:11">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
This function is dead code referencing a non-existent pie chart. The JSDoc claims it's "for pie chart display" but no pie chart exists in the codebase, and the PR scope is a line chart + data table. The function itself is never imported anywhere. Remove this file or, if the aggregation is actually needed, correct the comment and wire it into the page.</violation>
</file>
<file name="components/AgentSignups/AgentSignupsTable.tsx">
<violation number="1" location="components/AgentSignups/AgentSignupsTable.tsx:26">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
This entire file is a copy-paste of `PrivyLoginsTable.tsx` (and `ContentSlackTable.tsx`, etc.) with only entity names changed. Extract a shared generic `DataTable` component that accepts `data`, `columns`, and a default sort field — then all 6+ copies collapse to one-line usages.
(Based on your team's feedback about preferring shared components over duplicate wrappers.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| @@ -0,0 +1,22 @@ | |||
| import type { AgentSignup } from "@/types/agentSignups"; | |||
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
This function is dead code referencing a non-existent pie chart. The JSDoc claims it's "for pie chart display" but no pie chart exists in the codebase, and the PR scope is a line chart + data table. The function itself is never imported anywhere. Remove this file or, if the aggregation is actually needed, correct the comment and wire it into the page.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/agent-signups/getSignupsByEmail.ts, line 11:
<comment>This function is dead code referencing a non-existent pie chart. The JSDoc claims it's "for pie chart display" but no pie chart exists in the codebase, and the PR scope is a line chart + data table. The function itself is never imported anywhere. Remove this file or, if the aggregation is actually needed, correct the comment and wire it into the page.</comment>
<file context>
@@ -0,0 +1,22 @@
+/**
+ * Aggregates agent sign-ups by email address for pie chart display.
+ */
+export function getSignupsByEmail(signups: AgentSignup[]): SignupsByEmailEntry[] {
+ const counts: Record<string, number> = {};
+
</file context>
| @@ -0,0 +1,78 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
This entire file is a copy-paste of PrivyLoginsTable.tsx (and ContentSlackTable.tsx, etc.) with only entity names changed. Extract a shared generic DataTable component that accepts data, columns, and a default sort field — then all 6+ copies collapse to one-line usages.
(Based on your team's feedback about preferring shared components over duplicate wrappers.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/AgentSignups/AgentSignupsTable.tsx, line 26:
<comment>This entire file is a copy-paste of `PrivyLoginsTable.tsx` (and `ContentSlackTable.tsx`, etc.) with only entity names changed. Extract a shared generic `DataTable` component that accepts `data`, `columns`, and a default sort field — then all 6+ copies collapse to one-line usages.
(Based on your team's feedback about preferring shared components over duplicate wrappers.) </comment>
<file context>
@@ -0,0 +1,78 @@
+ signups: AgentSignup[];
+}
+
+export default function AgentSignupsTable({ signups }: AgentSignupsTableProps) {
+ const [sorting, setSorting] = useState<SortingState>([
+ { id: "created_at", desc: true },
</file context>
Summary
/agent-signupspage to the admin dashboard/contentpage (period selector, stats bar, skeleton loaders)GET /api/admins/agent-signupsendpointTest plan
🤖 Generated with Claude Code
Summary by cubic
Add a new Agent Sign-Ups admin page to track API key sign-ups over time with a line chart, total count, and a sortable table. Also adds a dashboard nav link and secure fetching from the admins API.
useAgentSignupsusing@tanstack/react-queryand@privy-io/react-auth; calls GET /api/admins/agent-signups?period=...Written for commit 3a2efd6. Summary will update on new commits.
Summary by CodeRabbit