import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { TrendingUp, TrendingDown } from 'lucide-react'; import { api, DashboardData, Match } from '../api/client'; import TipModal from '../components/TipModal'; import styles from './DashboardPage.module.css'; interface Props { devUser?: number; } function formatStreak(streak: number): string { if (streak >= 20) return `⚑${streak}`; if (streak >= 10) return `πŸ”₯πŸ”₯${streak}`; if (streak >= 3) return `πŸ”₯${streak}`; if (streak > 0) return String(streak); return '0'; } function formatKickoff(utcDate: string): string { return new Date(utcDate).toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Berlin', }); } function formatCountdown(minutes: number): string { if (minutes < 60) return `in ${Math.round(minutes)} Min`; if (minutes < 60 * 24) return `in ${Math.floor(minutes / 60)}h`; const days = Math.floor(minutes / (60 * 24)); return `in ${days} ${days === 1 ? 'Tag' : 'Tagen'}`; } export default function DashboardPage(_props: Props) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [tipMatch, setTipMatch] = useState(null); const navigate = useNavigate(); useEffect(() => { setLoading(true); setError(false); api.getDashboard() .then(d => { setData(d); setLoading(false); }) .catch(() => { setError(true); setLoading(false); }); }, []); // Keep stored streak up to date (hook must be before early returns) const STREAK_KEY = 'tippspiel_last_streak'; useEffect(() => { if (data && data.stats.streak > 0) { localStorage.setItem(STREAK_KEY, String(data.stats.streak)); } }, [data]); if (loading) return
Laden...
; if (error || !data) return
Dashboard konnte nicht geladen werden.
; const { hero, stats, nudges } = data; const lastRank = parseInt(localStorage.getItem('tippspiel_last_rank') || '0'); const rankDiff = lastRank > 0 && stats.rank !== null ? lastRank - stats.rank : 0; // Streak break detection via localStorage const lastStreak = parseInt(localStorage.getItem(STREAK_KEY) || '0'); const streakBroken = lastStreak >= 3 && stats.streak === 0; return (
{/* Hero Card */}
navigate('/spiele')}> {/* Radial glow background effect */}
NΓ€chstes Spiel {hero && ( {formatCountdown(hero.match.minutesUntilKickoff).toUpperCase()} )}
{hero ? ( <> {/* Teams with LED time in center */}
{hero.match.homeTeam.crest ? ( {hero.match.homeTeam.name} ) : ( 🏳️ )}
{hero.match.homeTeam.shortName}
{/* Center: LED time β€” each digit fixed-width for even spacing */}
{formatKickoff(hero.match.utcDate).split('').map((ch, i) => ( {ch} ))}
{hero.match.awayTeam.crest ? ( {hero.match.awayTeam.name} ) : ( 🏳️ )}
{hero.match.awayTeam.shortName}
{/* CTA or Tip Display */} {hero.userTip ? (
Dein Tipp: {hero.userTip.home}:{hero.userTip.away} βœ“
) : hero.tippable ? ( ) : null} ) : (

Keine anstehenden Spiele

)}
{/* Stats Row */}
{stats.rank !== null ? stats.rank : 'β€”'} {rankDiff > 0 && } {rankDiff < 0 && } Dein Rang
{stats.totalPoints} Punkte
{formatStreak(stats.streak)} Streak
{/* Nudges */} {(streakBroken || nudges.length > 0) && (
{streakBroken && (
{ localStorage.removeItem(STREAK_KEY); navigate('/spiele'); }} > πŸ’” Deine {lastStreak}er-Serie ist gerissen! Starte eine neue.
)} {nudges.map((nudge, i) => (
{ if (nudge.type === 'untipped') navigate('/spiele'); else if (nudge.type === 'leader') navigate('/rangliste'); }} > {nudge.type === 'untipped' ? 'πŸ“…' : nudge.type === 'leader' ? 'πŸ†' : '🎯'} {nudge.text}
))}
)} {tipMatch && ( setTipMatch(null)} onSaved={(_matchId, tipHome, tipAway) => { setTipMatch(null); if (data && data.hero) { setData({ ...data, hero: { ...data.hero, userTip: { home: tipHome, away: tipAway } }, }); } }} /> )}
); }