feat(admin): view a user's shard footprint at /admin/users/:id #56

Merged
whitlocktech merged 1 commits from feature/admin-user-view into main 2026-07-12 14:42:01 +00:00
Member

Summary

Adds a View action beside Edit in the admin users table. It opens a dedicated, deep-linkable, read-only page at /admin/users/:id showing everything the uo-link shard knows about that user, scoped to their linked game accounts:

  • Header (role badge, status, last login, joined)
  • Stat tiles (characters / online now / linked accounts)
  • Linked accounts + live character rosters
  • Online now — currently-online characters with their map location
  • Houses — houses owned by the user's accounts, IDOC flagged and sorted first
  • Recent vendor sales

The Edit flow (modal) is untouched.

Backend

All endpoints sit under the existing adminRouter.use('/users', adminOnly) gate, so editors/moderators get 403.

  • GET /admin/users/:id — single sanitized user (new; needed so the page survives a refresh)
  • GET /admin/users/:id/shard/{accounts,sales,houses,online} — DB-backed, scoped to the user's linked accounts
  • shardState: listHousesByAccounts / listOnlineByAccounts (+ model shapers; extracted a shapeHouse helper out of listIdoc)
  • Extracted salesForAccounts into utils/shardSales.js and pointed the existing player getSales at it (no logic duplication)

Live character rosters reuse the existing admin-bypass /admin/shard/roster|vendors|char endpoints (which already read any account), so no new routes were needed for them.

Frontend

  • New UserDetail.jsx, reusing the shared CharacterStats / GameAccounts / VendorSales components
  • GameAccounts gains a readOnly prop that drops the "link an account" form and self-voice copy for the admin-viewing-another-user case
  • api.admin.getUser + api.admin.userShard(id) scope; route in App.jsx; dynamic page title in AdminLayout

Testing

  • New server/test/adminUserShard.test.js: 404 on unknown user, account-scoping, empty-accounts, and the salesForAccounts cap/filter.
  • Full server suite: 164 pass. Client builds clean.
  • Verified end-to-end in the browser against a live DB (seeded shard data): all four sections render correctly; the roster degrades to the graceful "shard restarting" state when the sidecar is offline, as designed.

🤖 Generated with Claude Code

## Summary Adds a **View** action beside **Edit** in the admin users table. It opens a dedicated, deep-linkable, read-only page at `/admin/users/:id` showing everything the uo-link shard knows about that user, scoped to their linked game accounts: - Header (role badge, status, last login, joined) - Stat tiles (characters / online now / linked accounts) - Linked accounts + live character rosters - **Online now** — currently-online characters with their map location - **Houses** — houses owned by the user's accounts, IDOC flagged and sorted first - Recent vendor sales The **Edit** flow (modal) is untouched. ## Backend All endpoints sit under the existing `adminRouter.use('/users', adminOnly)` gate, so editors/moderators get 403. - `GET /admin/users/:id` — single sanitized user (new; needed so the page survives a refresh) - `GET /admin/users/:id/shard/{accounts,sales,houses,online}` — DB-backed, scoped to the user's linked accounts - `shardState`: `listHousesByAccounts` / `listOnlineByAccounts` (+ model shapers; extracted a `shapeHouse` helper out of `listIdoc`) - Extracted `salesForAccounts` into `utils/shardSales.js` and pointed the existing player `getSales` at it (no logic duplication) Live character rosters reuse the **existing admin-bypass** `/admin/shard/roster|vendors|char` endpoints (which already read any account), so no new routes were needed for them. ## Frontend - New `UserDetail.jsx`, reusing the shared `CharacterStats` / `GameAccounts` / `VendorSales` components - `GameAccounts` gains a `readOnly` prop that drops the "link an account" form and self-voice copy for the admin-viewing-another-user case - `api.admin.getUser` + `api.admin.userShard(id)` scope; route in `App.jsx`; dynamic page title in `AdminLayout` ## Testing - New `server/test/adminUserShard.test.js`: 404 on unknown user, account-scoping, empty-accounts, and the `salesForAccounts` cap/filter. - **Full server suite: 164 pass.** Client builds clean. - Verified end-to-end in the browser against a live DB (seeded shard data): all four sections render correctly; the roster degrades to the graceful "shard restarting" state when the sidecar is offline, as designed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 1 commit 2026-07-12 14:37:16 +00:00
Add a "View" action beside Edit in the users table that opens a dedicated,
read-only page showing everything the uo-link shard knows about a user,
scoped to their linked game accounts: character rosters, currently-online
characters, houses (IDOC-first), and recent vendor sales.

Backend (admin-only, under the existing /users adminOnly gate):
- GET /admin/users/:id — single sanitized user (page is deep-linkable)
- GET /admin/users/:id/shard/{accounts,sales,houses,online}
- shardState: listHousesByAccounts / listOnlineByAccounts (+ model shapers)
- Extract salesForAccounts into utils/shardSales; reuse in player getSales
- Live rosters reuse the existing admin-bypass /admin/shard/* endpoints,
  so no new routes for roster/vendors/char

Frontend:
- UserDetail page reusing CharacterStats / GameAccounts / VendorSales
- GameAccounts gains a readOnly prop (drops link form + self-voice copy)
- api.admin.getUser + api.admin.userShard(id) scope; route + layout title

Tests: adminUserShard.test.js (404, account scoping, empty accounts,
salesForAccounts cap/filter). Full server suite 164 pass; client builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
whitlocktech approved these changes 2026-07-12 14:41:50 +00:00
whitlocktech merged commit 4fe90ea368 into main 2026-07-12 14:42:01 +00:00
whitlocktech deleted branch feature/admin-user-view 2026-07-12 14:42:02 +00:00
Sign in to join this conversation.
No description provided.