const BASE = '/api'; function withDevUser(path: string): string { const devUser = new URLSearchParams(window.location.search).get('devUser'); if (!devUser) return path; const sep = path.includes('?') ? '&' : '?'; return `${path}${sep}devUser=${devUser}`; } async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE}${withDevUser(path)}`, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }); if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })); throw new Error(err.message || err.error || 'Request failed'); } return res.json(); } export const api = { // Matches getMatches: (params?: { stage?: string; group?: string }) => { const q = new URLSearchParams(params as Record).toString(); return request<{ matches: Match[]; count: number }>(`/matches${q ? '?' + q : ''}`); }, // Tips submitTip: (matchId: number, tipHome: number, tipAway: number) => request<{ success: boolean; tip: TipInfo; message: string }>('/tips', { method: 'POST', body: JSON.stringify({ matchId, tipHome, tipAway }), }), getMyTips: () => request<{ tips: MyTip[]; count: number }>('/tips'), // Leaderboard getLeaderboard: () => request('/leaderboard'), getMyStats: () => request('/leaderboard/me'), // Profile updateTeam: (team: string) => request<{ success: boolean; team: string }>('/profile/team', { method: 'PATCH', body: JSON.stringify({ team }), }), // Dashboard getDashboard: () => request('/dashboard'), // Achievements getAchievements: () => request('/achievements'), // Admin syncMatches: () => request<{ success: boolean; total: number; created: number; updated: number }>( '/admin/sync', { method: 'POST' } ), evaluateTips: () => request<{ success: boolean; matchesEvaluated: number; tipsUpdated: number }>( '/admin/evaluate', { method: 'POST' } ), }; // Types (gespiegelt vom Backend) export interface DashboardData { hero: { match: { id: number; homeTeam: { name: string; shortName: string; crest: string | null }; awayTeam: { name: string; shortName: string; crest: string | null }; utcDate: string; status: string; minutesUntilKickoff: number; }; userTip: { home: number; away: number } | null; tippable: boolean; } | null; stats: { rank: number | null; totalPoints: number; streak: number }; nudges: Array<{ type: string; text: string; matchId?: number }>; } export interface Match { id: number; externalId: number; utcDate: string; status: string; stage: string; group: string | null; homeTeam: { name: string; shortName: string; crest: string | null }; awayTeam: { name: string; shortName: string; crest: string | null }; score: { home: number | null; away: number | null }; userTip: TipInfo | null; minutesUntilKickoff: number; tippable: boolean; } export interface TipInfo { home: number; away: number; points: number | null; } export interface MyTip { match_id: number; tip_home: number; tip_away: number; points: number | null; utc_date: string; home_team_short: string; away_team_short: string; status: string; } export interface LeaderboardEntry { user_id: string; full_name: string; team: string | null; total_points: number; tips_count: number; exact_count: number; tendency_count: number; rank: number; } export interface LeaderboardResponse { entries: LeaderboardEntry[]; currentUserRank: number | null; currentUserId: string | null; totalParticipants: number; lastUpdated: string; } export interface UserStats { userId: string; fullName: string; team: string | null; totalPoints: number; rank: number | null; tipsCount: number; exactCount: number; tendencyCount: number; wrongCount: number; accuracy: number; } export interface Achievement { id: string; name: string; description: string; icon: string; color: string; rankLabel: string; unlocked: boolean; progress: number; current: number; target: number; } export interface AchievementsData { achievements: Achievement[]; unlockedCount: number; total: number; }