feat: WM2026 Tippspiel - Initial Backend + Frontend

This commit is contained in:
Ronny Müller
2026-04-03 21:41:19 +02:00
commit 1c685b90a0
2507 changed files with 997210 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
import { useState, useEffect } from 'react';
import { api, UserStats } from '../api/client';
import styles from './ProfilePage.module.css';
export default function ProfilePage() {
const [stats, setStats] = useState<UserStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
api.getMyStats().then(setStats).finally(() => setLoading(false));
}, []);
if (loading) return <div className={styles.loading}><div className={styles.spinner} /></div>;
if (!stats) return <div className={styles.empty}>Profil nicht verfügbar.</div>;
const evaluated = stats.exactCount + stats.tendencyCount + stats.wrongCount;
return (
<div className={styles.page}>
<div className={`card ${styles.heroCard}`}>
<div className={styles.avatar}>{stats.fullName.charAt(0).toUpperCase()}</div>
<div className={styles.heroInfo}>
<h1 className={`font-display ${styles.name}`}>{stats.fullName}</h1>
{stats.rank && (
<div className={styles.rankBadge}>🏆 Platz {stats.rank}</div>
)}
</div>
<div className={styles.heroPoints}>
<span className={`font-display ${styles.pointsVal}`}>{stats.totalPoints}</span>
<span className={styles.pointsLbl}>Punkte</span>
</div>
</div>
<div className={styles.statsGrid}>
<div className={`card ${styles.statCard}`}>
<span className={`font-display ${styles.statVal} text-gold`}>{stats.exactCount}</span>
<span className={styles.statLbl}>🎯 Exakt</span>
</div>
<div className={`card ${styles.statCard}`}>
<span className={`font-display ${styles.statVal} text-primary`}>{stats.tendencyCount}</span>
<span className={styles.statLbl}> Tendenz</span>
</div>
<div className={`card ${styles.statCard}`}>
<span className={`font-display ${styles.statVal}`} style={{ color: 'var(--error)' }}>{stats.wrongCount}</span>
<span className={styles.statLbl}> Falsch</span>
</div>
<div className={`card ${styles.statCard}`}>
<span className={`font-display ${styles.statVal}`}>{stats.tipsCount}</span>
<span className={styles.statLbl}>Tipps gesamt</span>
</div>
</div>
{evaluated > 0 && (
<div className={`card ${styles.accuracyCard}`}>
<div className={styles.accuracyHeader}>
<span className={styles.accuracyLabel}>Trefferquote</span>
<span className={`font-display ${styles.accuracyVal}`}>{stats.accuracy}%</span>
</div>
<div className={styles.bar}>
<div className={`${styles.barFill} ${styles.exact}`}
style={{ width: `${(stats.exactCount / evaluated) * 100}%` }} />
<div className={`${styles.barFill} ${styles.tendency}`}
style={{ width: `${(stats.tendencyCount / evaluated) * 100}%` }} />
</div>
<div className={styles.barLegend}>
<span><span className={styles.dot} style={{ background: 'var(--gold)' }} /> Exakt</span>
<span><span className={styles.dot} style={{ background: 'var(--primary)' }} /> Tendenz</span>
<span><span className={styles.dot} style={{ background: 'var(--surface-high)' }} /> Falsch</span>
</div>
</div>
)}
</div>
);
}