feat(ui): player stats modal and admin button for ADMIN_USERS accounts

The admin dashboard exposed account stats, but players had no way to see
their own. Both gaps are closed:

Player stats modal (src/components/PlayerStats.vue):
- New "📊 Statistik" chip in the profile bar, available to logged-in
  players AND guests (guest progress lives in the same profile shape)
- Shows crystals, rounds played, wins incl. win rate, kills, total score,
  best wave, and a research progress bar (owned vs. total upgrade levels)
- Guests get a hint that their progress is local-only and transfers into
  an account on first login

Admin shortcut:
- Logged-in users whose account is listed in ADMIN_USERS (server already
  sets publicUser.admin) now get a "🛡️ Admin" chip in the profile bar
  that links to /admin with the existing session
- UserMetaProfile type gains the optional admin flag

Verified in the browser: guest sees the stats button but no admin chip;
after logging in as an ADMIN_USERS account the admin chip appears and
navigates to the dashboard (data loads, 1 account). Build clean, 54/54
tests green.
This commit is contained in:
Tronax 2026-08-17 15:18:43 +02:00
parent 69fbb015ab
commit 572f82a954
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o
5 changed files with 193 additions and 0 deletions

View file

@ -5,13 +5,20 @@ import { store } from '@/game/store'
const user = computed(() => store.auth.user)
const loggedIn = computed(() => !!user.value && user.value.id !== 'guest')
const isAdmin = computed(() => Boolean(user.value?.admin))
function openResearch(): void {
store.auth.showResearch = true
}
function openStats(): void {
store.auth.showStats = true
}
function openAuth(): void {
store.auth.showAuth = true
}
function openAdmin(): void {
window.location.href = '/admin'
}
async function doLogout(): Promise<void> {
await logout()
}
@ -24,11 +31,14 @@ async function doLogout(): Promise<void> {
</div>
<template v-if="loggedIn">
<span class="name" :title="user?.username">👤 {{ user?.displayName }}</span>
<button class="chip stats" @click="openStats">📊 Statistik</button>
<button class="chip research" @click="openResearch">🧪 Forschung</button>
<button v-if="isAdmin" class="chip admin" title="Admin-Dashboard öffnen" @click="openAdmin">🛡 Admin</button>
<button class="chip" @click="doLogout">Abmelden</button>
</template>
<template v-else>
<span class="name">👤 Gast</span>
<button class="chip stats" @click="openStats">📊 Statistik</button>
<button class="chip research" @click="openResearch">🧪 Forschung</button>
<button class="chip login" @click="openAuth">Anmelden</button>
</template>
@ -75,6 +85,13 @@ async function doLogout(): Promise<void> {
.chip.research {
border-color: #3d6f9e;
}
.chip.stats {
border-color: #6e5a9e;
}
.chip.admin {
border-color: #9e503d;
color: #ffb0a0;
}
.chip.login {
border-color: #3f8f37;
color: #b8e6b0;