feat: Schocken - Testmodus Spieler-Switcher für Admin

This commit is contained in:
2026-07-05 00:17:26 +02:00
parent 19619cb57c
commit 1806d0bdfe
2 changed files with 45 additions and 4 deletions

View File

@@ -102,7 +102,15 @@ function sendPush(userId, title, message) {
}).catch(() => {}); }).catch(() => {});
} }
const uid = req => req.user.id; const uid = req => {
// Testmodus: Admin kann als anderen User agieren
const testAs = req.headers['x-schocken-test-as'];
if (testAs && req.user?.role === 'admin') {
const testId = parseInt(testAs);
if (!isNaN(testId)) return testId;
}
return req.user.id;
};
// ── Alle Spiele des Users ───────────────────────────────────────────────────── // ── Alle Spiele des Users ─────────────────────────────────────────────────────
router.get('/', authenticate, (req, res) => { router.get('/', authenticate, (req, res) => {

View File

@@ -1,6 +1,20 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { api, S } from '../lib.js'; import { api, S } from '../lib.js';
// Testmodus: API-Call als anderer User (nur Admin, nur Schocken)
async function apiAs(userId, path, opts = {}) {
const token = localStorage.getItem('sk_token');
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` };
if (userId) headers['X-Schocken-Test-As'] = String(userId);
const res = await fetch(`/api${path}`, {
method: opts.method || (opts.body ? 'POST' : 'GET'),
headers,
body: opts.body ? JSON.stringify(opts.body) : undefined,
});
if (!res.ok) { const e = await res.json().catch(()=>({error:'Fehler'})); throw new Error(e.error||'Fehler'); }
return res.json();
}
const MY_COLOR = '#4ecdc4'; const MY_COLOR = '#4ecdc4';
function getMyId() { function getMyId() {
@@ -441,7 +455,7 @@ function GameList({ games, myId, onSelect, onNew }) {
} }
// ── Spielansicht ────────────────────────────────────────────────────────────── // ── Spielansicht ──────────────────────────────────────────────────────────────
function GameView({ game, myId, onRefresh, toast }) { function GameView({ game, myId, onRefresh, toast, isAdmin=false, onSeatChange }) {
const players = game.players; const players = game.players;
const chips = game.player_chips; const chips = game.player_chips;
const dl = game.drink_losses; const dl = game.drink_losses;
@@ -483,6 +497,21 @@ function GameView({ game, myId, onRefresh, toast }) {
<span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>Runde {game.round_number}</span> <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>Runde {game.round_number}</span>
</div> </div>
{/* Testmodus-Switcher (nur Admin) */}
{isAdmin && (
<div style={{marginBottom:12,padding:'8px 12px',background:'rgba(245,158,11,0.08)',border:'1px solid rgba(245,158,11,0.25)',borderRadius:8}}>
<div style={{color:'#f59e0b',fontFamily:'monospace',fontSize:10,letterSpacing:1,marginBottom:6}}>🧪 TESTMODUS SPIELER WECHSELN</div>
<div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
{players.map(p => (
<button key={p.id} onClick={() => onSeatChange(p.id)}
style={{...S.btn(myId===p.id?'#f59e0b':'#666666',true),fontSize:10}}>
{p.username}{myId===p.id?' ✓':''}
</button>
))}
</div>
</div>
)}
{/* Schockstock */} {/* Schockstock */}
<Schockstock stock={game.stock} players={players} chips={chips}/> <Schockstock stock={game.stock} players={players} chips={chips}/>
@@ -544,8 +573,12 @@ export default function Schocken({ toast }) {
const [game, setGame] = useState(null); const [game, setGame] = useState(null);
const [showNew, setShowNew] = useState(false); const [showNew, setShowNew] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [testSeat, setTestSeat] = useState(null); // null = eigener Account, sonst userId
const myId = getMyId(); const myRealId = getMyId();
const isAdmin = getMyRole() === 'admin';
// Im Testmodus: als anderen Spieler agieren
const myId = (isAdmin && testSeat) ? testSeat : myRealId;
const loadGames = useCallback(async () => { const loadGames = useCallback(async () => {
try { setGames(await api('/tools/schocken')); } try { setGames(await api('/tools/schocken')); }
@@ -585,7 +618,7 @@ export default function Schocken({ toast }) {
{!activeId {!activeId
? <GameList games={games} myId={myId} onSelect={setActiveId} onNew={()=>setShowNew(true)}/> ? <GameList games={games} myId={myId} onSelect={setActiveId} onNew={()=>setShowNew(true)}/>
: game && myId : game && myId
? <GameView game={game} myId={myId} onRefresh={loadGame} toast={toast}/> ? <GameView game={game} myId={myId} onRefresh={loadGame} toast={toast} isAdmin={isAdmin} onSeatChange={id=>{setTestSeat(id);}} />
: <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',textAlign:'center',paddingTop:40}}>Lade</div> : <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',textAlign:'center',paddingTop:40}}>Lade</div>
} }