This repository has been archived on 2026-05-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tippspiel/frontend/src/App.tsx
T
Ronny 7be0418a3e
Build & Deploy Tippspiel / build (push) Successful in 31s
style: Lucide Icons für Theme-Toggle + stärkerer Glossy-Effekt auf Flag-Boxen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:09:36 +02:00

130 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}