From fd714f9de2659490a69cfc4a38f39f8e73a6cf5b Mon Sep 17 00:00:00 2001 From: Recoup Agent Date: Tue, 10 Mar 2026 20:33:10 +0000 Subject: [PATCH] agent: @U0AJM7X8FBR Admin page needs updated to show a table of accounts with s --- components/Accounts/AccountsTable.tsx | 75 +++++++++++++++++++ components/Accounts/AccountsTableSkeleton.tsx | 31 ++++++++ components/Home/AdminDashboard.tsx | 15 ++-- components/Home/HomeContent.tsx | 6 +- hooks/useAccountsWithSandboxes.ts | 39 ++++++++++ 5 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 components/Accounts/AccountsTable.tsx create mode 100644 components/Accounts/AccountsTableSkeleton.tsx create mode 100644 hooks/useAccountsWithSandboxes.ts diff --git a/components/Accounts/AccountsTable.tsx b/components/Accounts/AccountsTable.tsx new file mode 100644 index 0000000..9ba4bf6 --- /dev/null +++ b/components/Accounts/AccountsTable.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useAccountsWithSandboxes } from "@/hooks/useAccountsWithSandboxes"; +import AccountsTableSkeleton from "./AccountsTableSkeleton"; + +/** + * Formats a timestamp string to a readable date/time. + */ +function formatDate(dateStr: string | null): string { + if (!dateStr) return "—"; + return new Date(dateStr).toLocaleString("en-US", { + dateStyle: "medium", + timeStyle: "short", + }); +} + +export default function AccountsTable() { + const { data: accounts, isLoading, error } = useAccountsWithSandboxes(); + + if (isLoading) { + return ; + } + + if (error) { + return ( +

+ Failed to load accounts. +

+ ); + } + + if (!accounts || accounts.length === 0) { + return ( +

+ No accounts with sandboxes found. +

+ ); + } + + return ( +
+ + + + + + + + + + {accounts.map((account) => ( + + + + + + ))} + +
Account NameTotal SandboxesLast Created
+
+ + {account.account_name || "Unnamed"} + +

+ {account.account_id} +

+
+
+ {account.total_sandboxes} + + {formatDate(account.last_created_at)} +
+
+ ); +} diff --git a/components/Accounts/AccountsTableSkeleton.tsx b/components/Accounts/AccountsTableSkeleton.tsx new file mode 100644 index 0000000..5ce7c9d --- /dev/null +++ b/components/Accounts/AccountsTableSkeleton.tsx @@ -0,0 +1,31 @@ +export default function AccountsTableSkeleton() { + return ( +
+ + + + + + + + + + {Array.from({ length: 5 }).map((_, i) => ( + + + + + + ))} + +
Account NameTotal SandboxesLast Created
+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Home/AdminDashboard.tsx b/components/Home/AdminDashboard.tsx index d6c29a1..6fa679e 100644 --- a/components/Home/AdminDashboard.tsx +++ b/components/Home/AdminDashboard.tsx @@ -1,10 +1,15 @@ +import AccountsTable from "@/components/Accounts/AccountsTable"; + export default function AdminDashboard() { return ( -
-

Admin Dashboard

-

- Welcome back. You have admin access. -

+
+
+

Accounts with Sandboxes

+

+ All accounts that have created sandboxes on the platform. +

+
+
); } diff --git a/components/Home/HomeContent.tsx b/components/Home/HomeContent.tsx index 39d7882..dd21e4e 100644 --- a/components/Home/HomeContent.tsx +++ b/components/Home/HomeContent.tsx @@ -35,5 +35,9 @@ export default function HomeContent() { ); } - return ; + return ( +
+ +
+ ); } diff --git a/hooks/useAccountsWithSandboxes.ts b/hooks/useAccountsWithSandboxes.ts new file mode 100644 index 0000000..7f18071 --- /dev/null +++ b/hooks/useAccountsWithSandboxes.ts @@ -0,0 +1,39 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { usePrivy } from "@privy-io/react-auth"; +import { API_BASE_URL } from "@/lib/consts"; + +export interface AccountSandboxSummary { + account_id: string; + account_name: string | null; + total_sandboxes: number; + last_created_at: string | null; +} + +/** + * Fetches all accounts with sandbox summaries from the admin endpoint. + * Requires admin authentication. + */ +export function useAccountsWithSandboxes() { + const { ready, authenticated, getAccessToken } = usePrivy(); + + return useQuery({ + queryKey: ["accountsWithSandboxes"], + queryFn: async () => { + const token = await getAccessToken(); + if (!token) return []; + + const res = await fetch( + `${API_BASE_URL}/api/admins/accounts-with-sandboxes`, + { headers: { Authorization: `Bearer ${token}` } }, + ); + + if (!res.ok) return []; + + const data = await res.json(); + return data.accounts ?? []; + }, + enabled: ready && authenticated, + }); +}