Let staff link their own characters + share the game-accounts UI

- Backend: /admin/shard/{link,accounts,roster/:account,vendors/:account} —
  staff self-service, reusing the player/shard controller (it keys off
  req.user.id, so the same handlers serve any logged-in role). Swagger under
  Admin · Account; spec regenerated.
- components/GameAccounts.jsx: the link-prompt + character-roster UI extracted
  into one reusable component parametrized by an api scope and a charTo(serial)
  route builder.
- PlayerCharacters now renders it (player scope → /player/char/:serial).
- Admin: "My Characters" nav item + /admin/characters (AdminCharacters) and
  /admin/characters/:serial (AdminCharacter, in-shell sheet), using the admin
  self-service scope. api.admin.shard.* added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3
This commit is contained in:
2026-07-11 03:05:43 -05:00
parent 49ce230c3a
commit 74d2ead958
9 changed files with 473 additions and 159 deletions

View File

@@ -0,0 +1,27 @@
import { useParams, Link } from 'react-router-dom'
import { Loading, ErrorState } from '../../../components/PageState.jsx'
import CharacterSheet from '../../../components/CharacterSheet.jsx'
import { useAsync } from '../../../lib/useAsync.js'
import { api } from '../../../api/client.js'
// A staff member's character sheet inside the admin shell. Character data is
// public MMO data, so it uses the same cached public endpoint.
export default function AdminCharacter() {
const { serial } = useParams()
const { loading, error, data } = useAsync(() => api.shard.char(serial), [serial])
const restarting = error && error.status === 503
return (
<div style={{ maxWidth: 760 }}>
<p style={{ margin: '0 0 18px' }}>
<Link to="/admin/characters" className="sans" style={{ color: 'var(--accent)', textDecoration: 'none', fontSize: '0.86rem' }}>
Back to my characters
</Link>
</p>
{loading && <Loading />}
{restarting && <ErrorState message="The game server is restarting — try again shortly." />}
{error && !restarting && <ErrorState message="Could not load that character right now." />}
{!loading && !error && data && <CharacterSheet char={data} />}
</div>
)
}

View File

@@ -0,0 +1,15 @@
import GameAccounts from '../../../components/GameAccounts.jsx'
import { api } from '../../../api/client.js'
// Staff link their OWN in-game account and view their characters — the same
// shared component players use, pointed at the staff self-service endpoints.
export default function AdminCharacters() {
return (
<section style={{ maxWidth: 760 }}>
<p className="sans" style={{ marginTop: 0, marginBottom: 22, color: 'var(--muted)', fontSize: '0.92rem', lineHeight: 1.6 }}>
Link your own game account to view your characters, stats, skills and vendors.
</p>
<GameAccounts scope={api.admin.shard} charTo={(serial) => `/admin/characters/${serial}`} />
</section>
)
}