7be0418a3e
Build & Deploy Tippspiel / build (push) Successful in 31s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
130 lines
4.6 KiB
TypeScript
130 lines
4.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { Routes, Route, NavLink } from 'react-router-dom';
|
||
import { Sun, Moon } from 'lucide-react';
|
||
import MatchesPage from './pages/MatchesPage';
|
||
import LeaderboardPage from './pages/LeaderboardPage';
|
||
import ProfilePage from './pages/ProfilePage';
|
||
import AdminPage from './pages/AdminPage';
|
||
import AgentChat from './components/AgentChat';
|
||
import styles from './App.module.css';
|
||
|
||
const IS_DEV = import.meta.env.DEV || import.meta.env.VITE_TEST_MODE === 'true';
|
||
|
||
// Lazy-load DevPanel in Development/Test-Mode
|
||
let DevPanel: React.ComponentType<any> | null = null;
|
||
// VITE_TEST_MODE wird erst zur Laufzeit geprüft, daher Import immer einbinden
|
||
import('./components/DevPanel').then(m => { DevPanel = m.default; }).catch(() => {});
|
||
|
||
type Theme = 'dark' | 'light';
|
||
|
||
function getInitialTheme(): Theme {
|
||
try {
|
||
const stored = localStorage.getItem('theme') as Theme | null;
|
||
if (stored === 'light' || stored === 'dark') return stored;
|
||
} catch {}
|
||
return 'dark';
|
||
}
|
||
|
||
export default function App() {
|
||
const [theme, setTheme] = useState<Theme>(getInitialTheme);
|
||
const [devUser, setDevUser] = useState(1);
|
||
const [devMatches, setDevMatches] = useState<any[]>([]);
|
||
const [refreshKey, setRefreshKey] = useState(0);
|
||
|
||
// Theme auf <html> setzen und in localStorage speichern
|
||
useEffect(() => {
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
try { localStorage.setItem('theme', theme); } catch {}
|
||
}, [theme]);
|
||
|
||
function toggleTheme() {
|
||
setTheme(t => t === 'dark' ? 'light' : 'dark');
|
||
}
|
||
|
||
// DevUser als Query-Parameter im API-Fetch setzen
|
||
useEffect(() => {
|
||
if (!IS_DEV) return;
|
||
// Patch fetch für Dev-Mode: devUser Query-Param anhängen
|
||
const origFetch = window.fetch;
|
||
window._devUser = devUser;
|
||
window.fetch = (input, init) => {
|
||
if (typeof input === 'string' && input.startsWith('/api')) {
|
||
const url = new URL(input, window.location.origin);
|
||
url.searchParams.set('devUser', String(window._devUser ?? 1));
|
||
return origFetch(url.toString(), init);
|
||
}
|
||
return origFetch(input, init);
|
||
};
|
||
return () => { window.fetch = origFetch; };
|
||
}, [devUser]);
|
||
|
||
// Matches für DevPanel laden
|
||
useEffect(() => {
|
||
if (!IS_DEV) return;
|
||
fetch('/api/matches').then(r => r.json()).then(d => setDevMatches(d.matches ?? [])).catch(() => {});
|
||
}, [refreshKey, devUser]);
|
||
|
||
function handleDevRefresh() {
|
||
setRefreshKey(k => k + 1);
|
||
}
|
||
|
||
return (
|
||
<div className={styles.app}>
|
||
<header className={styles.header}>
|
||
<div className={styles.headerInner}>
|
||
<div className={styles.logo}>
|
||
<span className={styles.logoFlag}>🏆</span>
|
||
<span className={styles.logoText}>WM 2026 Tippspiel</span>
|
||
{IS_DEV && (
|
||
<span className={styles.devBadge}>DEV · User {devUser}</span>
|
||
)}
|
||
</div>
|
||
<nav className={styles.nav}>
|
||
<NavLink to="/" end className={({ isActive }) => isActive ? styles.navLinkActive : styles.navLink}>
|
||
Spielplan
|
||
</NavLink>
|
||
<NavLink to="/rangliste" className={({ isActive }) => isActive ? styles.navLinkActive : styles.navLink}>
|
||
Rangliste
|
||
</NavLink>
|
||
<NavLink to="/profil" className={({ isActive }) => isActive ? styles.navLinkActive : styles.navLink}>
|
||
Mein Profil
|
||
</NavLink>
|
||
<NavLink to="/admin" className={({ isActive }) => isActive ? styles.navLinkActive : styles.navLink}>
|
||
Admin
|
||
</NavLink>
|
||
<button
|
||
className={styles.themeToggle}
|
||
onClick={toggleTheme}
|
||
title={theme === 'dark' ? 'Light Mode aktivieren' : 'Dark Mode aktivieren'}
|
||
aria-label="Theme wechseln"
|
||
>
|
||
{theme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main className={styles.main}>
|
||
<Routes>
|
||
<Route path="/" element={<MatchesPage key={refreshKey} devUser={devUser} />} />
|
||
<Route path="/rangliste" element={<LeaderboardPage key={refreshKey} />} />
|
||
<Route path="/profil" element={<ProfilePage key={refreshKey} />} />
|
||
<Route path="/admin" element={<AdminPage />} />
|
||
</Routes>
|
||
</main>
|
||
|
||
{IS_DEV && DevPanel && (
|
||
<DevPanel
|
||
currentUser={devUser}
|
||
onUserChange={(u: number) => { setDevUser(u); setRefreshKey(k => k + 1); }}
|
||
matches={devMatches}
|
||
onRefresh={handleDevRefresh}
|
||
/>
|
||
)}
|
||
|
||
{/* Fußball-Experte Chat-Widget – immer sichtbar */}
|
||
<AgentChat />
|
||
</div>
|
||
);
|
||
}
|