|
| 1 | +# Treasury KPI header redesign |
| 2 | + |
| 3 | +Apply the evolved top section from the Claude Design mock |
| 4 | +(`Treasury.dc.html`, project `8b9a918a-e570-4c4f-ac96-32db36a3ebf6`) to |
| 5 | +`/treasury`. Scope is the header row and the KPI cards only — everything below |
| 6 | +(Allocation, Inflows, Sponsorship, charts, Token Holdings, NFT grid) stays as |
| 7 | +shipped in #273/#274. User approved this scope and the design below. |
| 8 | + |
| 9 | +## Header |
| 10 | + |
| 11 | +Title + description unchanged on the left. On the right, a "Synced Xm ago" |
| 12 | +pill: green dot, muted border/background, bound to the timestamp the treasury |
| 13 | +snapshot was generated (the page ISR-revalidates every 300s, so the badge honestly |
| 14 | +reads minutes, not hours). Server-rendered relative time; no client ticking. |
| 15 | +i18n: EN "Synced {time} ago" / PT "Sincronizado há {time}" via next-intl's |
| 16 | +relative formatting, both locales in the same change. |
| 17 | + |
| 18 | +## KPI row: one component, four cards |
| 19 | + |
| 20 | +Replace the three inline `<Card>` blocks in `page.tsx` with a single server |
| 21 | +component `TreasuryKpiRow` under one `Suspense`. It loads |
| 22 | +`loadTreasurySnapshot` once (today's three cards already share that load via |
| 23 | +React `cache`) plus the new `loadSubnetEarnings`, and renders four cards: |
| 24 | + |
| 25 | +| Card | Value | Note | Accent | Corner mark | |
| 26 | +| --- | --- | --- | --- | --- | |
| 27 | +| Total Treasury Value | usdTotal (fiat — BRL on PT, existing rule + fallback note) | "across {n} assets" (priced snapshot assets incl. ETH) | red | `public/red_noggles.png`, opacity .2 | |
| 28 | +| ETH Balance | ethBalance ETH | ETH in USD (fiat — converts on PT) | blue | ETH diamond SVG from mock | |
| 29 | +| Total Auction Sales | totalAuctionSales ETH | "{n} auctions settled" (real settled count from subgraph, not the mock's 12) | yellow | ETH diamond SVG | |
| 30 | +| Subnet Earnings | total USDC (token quantity — never converts to BRL, same rule as "6.8003 ETH") | "Morpheus · {n} claims" | green | USDC circle SVG from mock, value text green | |
| 31 | + |
| 32 | +Card chrome, per the mock: border `color-mix(in oklab, <accent> 26%, <default border>)`, |
| 33 | +radial tint `color-mix(in oklab, <accent> 16%, transparent)` from the bottom-right |
| 34 | +corner, mono tabular values (CountUp animation kept), 12px muted unit and note. |
| 35 | +Accents come from theme tokens/`color-mix`, not hardcoded dark-only colors, so |
| 36 | +light mode degrades gracefully. Grid: `grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`. |
| 37 | + |
| 38 | +## New data: subnet earnings |
| 39 | + |
| 40 | +The inflows feed is windowed (Alchemy pages of 50), so it cannot honestly sum |
| 41 | +all-time earnings. New service function `loadSubnetEarnings()` in |
| 42 | +`src/services/treasury-inflows.ts`: Alchemy `getAssetTransfers` filtered |
| 43 | +`fromAddress = SUBNET_FINAL_SPLIT`, `toAddress = treasury`, category erc20 |
| 44 | +(USDC), paginated to exhaustion (the claim history is small). Returns |
| 45 | +`{ totalUsdc, claimCount } | null`; `null` (fetch failure or no ALCHEMY_KEY) |
| 46 | +renders the card with an em dash value and no claim note — never $0. React |
| 47 | +`cache` for per-request dedup; page ISR (300s) is the refresh cadence. |
| 48 | + |
| 49 | +Auctions settled count: total count of settled auctions from the existing |
| 50 | +subgraph query path (aggregate count, not the mock's placeholder 12). |
| 51 | + |
| 52 | +## Error handling |
| 53 | + |
| 54 | +- Snapshot load failure: em-dash values in the affected cards (the page's |
| 55 | + "unavailable, not zero" convention), notes omitted. |
| 56 | +- Subnet earnings `null`: dash, no fabricated zero. |
| 57 | +- BRL rate outage: existing `FiatFallbackNote` appears in the Total card (the |
| 58 | + only KPI card showing converted fiat as its main value). |
| 59 | + |
| 60 | +## Testing |
| 61 | + |
| 62 | +- Unit: none new beyond existing fiat tests (presentation component + a fetch |
| 63 | + aggregator; the aggregation math is a two-line reduce). |
| 64 | +- Runtime: dev captures EN + PT, desktop + mobile, light + dark chrome checked |
| 65 | + in desktop capture; gates (tsc/eslint/prettier) clean; i18n parity for the |
| 66 | + new strings in both locales. |
0 commit comments