From 572f82a95454a890ee582352e26cbb1d7baccc0f Mon Sep 17 00:00:00 2001 From: Tronax Date: Mon, 17 Aug 2026 15:18:43 +0200 Subject: [PATCH] feat(ui): player stats modal and admin button for ADMIN_USERS accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/App.vue | 2 + src/components/PlayerStats.vue | 171 ++++++++++++++++++++++++++++++ src/components/UserProfileBar.vue | 17 +++ src/game/store.ts | 1 + src/game/types.ts | 2 + 5 files changed, 193 insertions(+) create mode 100644 src/components/PlayerStats.vue diff --git a/src/App.vue b/src/App.vue index 72b3aec..91ca46c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,6 +9,7 @@ import MPBar from '@/components/MPBar.vue' import ObstaclePanel from '@/components/ObstaclePanel.vue' import PauseOverlay from '@/components/PauseOverlay.vue' import PiPCanvas from '@/components/PiPCanvas.vue' +import PlayerStats from '@/components/PlayerStats.vue' import ResearchTree from '@/components/ResearchTree.vue' import StartScreen from '@/components/StartScreen.vue' import TowerPanel from '@/components/TowerPanel.vue' @@ -86,6 +87,7 @@ const badgeColor = computed(() => { + diff --git a/src/components/PlayerStats.vue b/src/components/PlayerStats.vue new file mode 100644 index 0000000..8636830 --- /dev/null +++ b/src/components/PlayerStats.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/components/UserProfileBar.vue b/src/components/UserProfileBar.vue index fa91e5b..b1d0aa1 100644 --- a/src/components/UserProfileBar.vue +++ b/src/components/UserProfileBar.vue @@ -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 { await logout() } @@ -24,11 +31,14 @@ async function doLogout(): Promise { @@ -75,6 +85,13 @@ async function doLogout(): Promise { .chip.research { border-color: #3d6f9e; } +.chip.stats { + border-color: #6e5a9e; +} +.chip.admin { + border-color: #9e503d; + color: #ffb0a0; +} .chip.login { border-color: #3f8f37; color: #b8e6b0; diff --git a/src/game/store.ts b/src/game/store.ts index 6da64f2..45208a2 100644 --- a/src/game/store.ts +++ b/src/game/store.ts @@ -21,6 +21,7 @@ export const store = reactive({ oidcLabel: 'Mit Single Sign-On anmelden', showAuth: false, showResearch: false, + showStats: false, authStatus: '', }, diff --git a/src/game/types.ts b/src/game/types.ts index bca04b4..494ff1d 100644 --- a/src/game/types.ts +++ b/src/game/types.ts @@ -298,4 +298,6 @@ export interface UserMetaProfile { crystals: number upgrades: Partial> stats: UserStats + /** server-side flag: username is listed in ADMIN_USERS */ + admin?: boolean }