Skip to content

feat: add Agent Sign-Ups admin page#26

Open
recoup-coding-agent wants to merge 2 commits intomainfrom
feature/agent-signups-page
Open

feat: add Agent Sign-Ups admin page#26
recoup-coding-agent wants to merge 2 commits intomainfrom
feature/agent-signups-page

Conversation

@recoup-coding-agent
Copy link
Copy Markdown
Collaborator

@recoup-coding-agent recoup-coding-agent commented Apr 12, 2026

Summary

  • Adds new /agent-signups page to the admin dashboard
  • Displays agent API key signups over time with line chart and data table
  • Follows the same pattern as the /content page (period selector, stats bar, skeleton loaders)
  • Fetches data from GET /api/admins/agent-signups endpoint

Test plan

  • Verify the page loads and displays agent signup data
  • Test period filtering (all, daily, weekly, monthly)
  • Confirm chart and table render correctly
  • Check loading/error/empty states

🤖 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.

  • New Features
    • New page at /agent-signups with period selector (all/daily/weekly/monthly) and total sign-ups.
    • Line chart of sign-ups over time and sortable table (Email, Key Name, Created At) with loading/error/empty states.
    • Data fetching via useAgentSignups using @tanstack/react-query and @privy-io/react-auth; calls GET /api/admins/agent-signups?period=...
    • Helpers for aggregating by date and typed response models; updated admin dashboard nav.

Written for commit 3a2efd6. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added Agent Sign-Ups section to the admin dashboard
    • View sign-up data with interactive charts and searchable agent table
    • Filter sign-ups by custom time periods
    • Display total statistics and agent details including email and signup date

Sr Dev Agent and others added 2 commits April 12, 2026 00:05
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>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin Ready Ready Preview Apr 12, 2026 7:05pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

A 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

Cohort / File(s) Summary
Route & Page Structure
app/agent-signups/page.tsx, components/AgentSignups/AgentSignupsPage.tsx
New route page with metadata and default export; main page component with state management, data fetching via useAgentSignups, conditional rendering for loading/error/empty/success states, and integration of sub-components including chart, table, stats, and period selector.
Table & Display Components
components/AgentSignups/AgentSignupsTable.tsx, components/AgentSignups/agentSignupsColumns.tsx, components/AgentSignups/AgentSignupsStats.tsx
Table component using TanStack React Table with sorting; column definitions including email (sortable), name (truncated), and created_at (datetime sortable); stats component displaying total signup count.
Data Fetching & Hooks
hooks/useAgentSignups.ts, lib/recoup/fetchAgentSignups.ts
React Query hook integrating Privy authentication; async fetch function making bearer-token authorized requests to /api/admins/agent-signups with period parameter and error handling.
Data Utilities
lib/agent-signups/getSignupsByDate.ts, lib/agent-signups/getSignupsByEmail.ts
Helper functions to aggregate signups by date (for time-series charting) and by email (for potential pie-chart usage), each returning sorted arrays of aggregated entries.
Type Definitions
types/agentSignups.ts
New TypeScript types defining AgentSignup record structure and AgentSignupsResponse API response shape with status, total count, and signup array.
Navigation
components/Home/AdminDashboard.tsx
Single navigation button added linking to /agent-signups route.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • PR #2: Client components invoke usePrivy() and depend on the PrivyProvider authentication infrastructure introduced in that PR.
  • PR #20: Introduces parallel admin analytics patterns including PeriodSelector, AdminPeriod type, React Query hooks, table/column components, and getSignupsByDate-style helpers.
  • PR #9: Modifies AdminDashboard navigation component alongside new app route additions; both PRs extend the admin dashboard structure.

Poem

🐰 A signup page hops into view,
With tables and charts, so fresh and new!
Period selectors dance all around,
Agent signups counted and proudly found. ✨📊

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Agent Sign-Ups admin page' directly and accurately summarizes the main change: introducing a new admin dashboard page for Agent Sign-Ups.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/agent-signups-page

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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() for data.total so 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 email as optional/falsy, but AgentSignup.email is 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

📥 Commits

Reviewing files that changed from the base of the PR and between b425199 and 3a2efd6.

📒 Files selected for processing (11)
  • app/agent-signups/page.tsx
  • components/AgentSignups/AgentSignupsPage.tsx
  • components/AgentSignups/AgentSignupsStats.tsx
  • components/AgentSignups/AgentSignupsTable.tsx
  • components/AgentSignups/agentSignupsColumns.tsx
  • components/Home/AdminDashboard.tsx
  • hooks/useAgentSignups.ts
  • lib/agent-signups/getSignupsByDate.ts
  • lib/agent-signups/getSignupsByEmail.ts
  • lib/recoup/fetchAgentSignups.ts
  • types/agentSignups.ts

Comment on lines +25 to +29
id: "created_at",
accessorKey: "created_at",
header: ({ column }) => <SortableHeader column={column} label="Created At" />,
cell: ({ getValue }) => new Date(getValue<string>()).toLocaleString(),
sortingFn: "datetime",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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:


🏁 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 -5

Repository: recoupable/admin

Length of output: 389


🏁 Script executed:

# Check for `@tanstack/react-table` dependency
rg "@tanstack/react-table" --max-count 5 -A 1

Repository: 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 2

Repository: recoupable/admin

Length of output: 1224


🏁 Script executed:

# Read the agentSignupsColumns.tsx file to see the full context
cat -n components/AgentSignups/agentSignupsColumns.tsx

Repository: recoupable/admin

Length of output: 1232


🏁 Script executed:

# Check the AgentSignup type definition to confirm created_at is a string
rg "type AgentSignup" -A 10

Repository: 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.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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";
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 12, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

@@ -0,0 +1,78 @@
"use client";
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 12, 2026

Choose a reason for hiding this comment

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

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.)

View Feedback

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>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant