diff --git a/docs/api/sponsorship-requests.md b/docs/api/sponsorship-requests.md new file mode 100644 index 0000000..f1982a0 --- /dev/null +++ b/docs/api/sponsorship-requests.md @@ -0,0 +1,17 @@ +# Sponsorship config requests (#546) + +## Current behaviour + +- The dashboard home fetches sponsorship configs for the visible wallets in batches of five + (`SPONSORSHIP_CONCURRENCY`). +- The global sponsorship page already batches its requests in groups of five + (`CONFIG_CONCURRENCY`). + +Both still send one request per wallet, so the count grows with the number of wallets. Batching +only limits how many run at once. + +## Fix that needs the backend + +Return `sponsorship` with each wallet in the wallet list response, or add +`GET /v1/sponsorship?wallet_ids=...`. The client then makes a constant number of requests per +page. diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index eb3250a..34d9e92 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { AuthProvider } from "@/lib/useAuth"; export const dynamic = "force-dynamic"; @@ -14,5 +15,5 @@ export const metadata: Metadata = { }; export default function DashboardLayout({ children }: { children: React.ReactNode }) { - return children; + return {children}; } \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index bc08db4..2ecfca8 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -18,6 +18,9 @@ import { ErrorState } from "@/components/dashboard/WalletUI"; export const dynamic = "force-dynamic"; +// How many sponsorship configs to request at once for a page of wallets. +const SPONSORSHIP_CONCURRENCY = 5; + export default function DashboardHome() { const { user, token, loading, logout } = useAuth(); const [wallets, setWallets] = useState(null); @@ -49,9 +52,15 @@ export default function DashboardHome() { setWallets(page.data); setNextCursor(page.next_cursor); // Fetch sponsorship configs for the visible page in parallel. - const results = await Promise.allSettled( - page.data.map((w) => getSponsorshipConfig(asAuthToken(token), asWalletId(w.id))), - ); + const results: PromiseSettledResult[] = []; + for (let i = 0; i < page.data.length; i += SPONSORSHIP_CONCURRENCY) { + const batch = page.data.slice(i, i + SPONSORSHIP_CONCURRENCY); + results.push( + ...(await Promise.allSettled( + batch.map((w) => getSponsorshipConfig(asAuthToken(token), asWalletId(w.id))), + )), + ); + } if (aborted) return; const map = new Map(); page.data.forEach((w, i) => { diff --git a/src/components/dashboard/DashboardShell.tsx b/src/components/dashboard/DashboardShell.tsx index 415a840..4397931 100644 --- a/src/components/dashboard/DashboardShell.tsx +++ b/src/components/dashboard/DashboardShell.tsx @@ -6,6 +6,8 @@ import { DashboardBackground } from "./DashboardBackground"; import { IdleLock } from "./IdleLock"; import { network } from "@/lib/network"; import type { User } from "@/lib/auth"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; /** Dashboard chrome: network banner, idle-warning banner, sidebar, topbar, content. */ export function DashboardShell({ @@ -21,6 +23,14 @@ export function DashboardShell({ blockNavigation?: (href: string) => boolean; children: React.ReactNode; }) { + const pathname = usePathname(); + const [menuOpen, setMenuOpen] = useState(false); + + // Close the mobile menu after navigating. + useEffect(() => { + setMenuOpen(false); + }, [pathname]); + return (
@@ -37,12 +47,35 @@ export function DashboardShell({ )}
- + + {menuOpen && ( + <> +