migration to new feed source

This commit is contained in:
2026-07-01 16:34:01 -05:00
parent cfb778776e
commit 4e4c72da7f
58 changed files with 444 additions and 48 deletions

View File

@@ -176,10 +176,13 @@ export default function Fixtures({
</div>
</div>
<div className="fx-meta">
{m.venue && <span className="fx-venue">📍 {m.venue}</span>}
{m.attendance != null && (
<span className="fx-att">👥 {m.attendance.toLocaleString(intlLocale)}</span>
)}
{(() => {
const stadium = [m.stadiumName, m.stadiumCity].filter(Boolean).join(", ");
const crowd = m.attendance != null ? m.attendance.toLocaleString(intlLocale) : "";
const sep = stadium && crowd ? " · " : "";
const text = stadium + sep + crowd;
return text ? <span className="fx-venue">{text}</span> : null;
})()}
</div>
</div>
);

View File

@@ -1,32 +1,27 @@
"use client";
import { useState } from "react";
import { Team } from "@/lib/types";
import { TLA_TO_ISO2 } from "@/lib/flags";
// Zeigt die Flagge/das Wappen eines Teams. Fällt auf ein neutrales Rund
// zurück, wenn keine crest-URL vorliegt oder das Bild nicht geladen werden kann.
// Zeigt die Flagge eines Teams aus lokalem circle-flags-Bestand.
// Fallback: kein Rendering bei fehlendem Team/Code (kein Broken-Image).
export default function Flag({
team, size = 22,
}: { team?: Team; size?: number }) {
const [imgFailed, setImgFailed] = useState(false);
const style = { width: size, height: size } as const;
if (team?.crest && !imgFailed) {
const iso2 = team?.code ? TLA_TO_ISO2[team.code.toUpperCase()] : undefined;
if (iso2) {
return (
<img
src={team.crest}
alt={team.code || team.localisedName || team.name}
src={`/flags/${iso2}.svg`}
alt={team!.code || team!.localisedName || team!.name}
className="flag"
style={style}
loading="lazy"
onError={() => setImgFailed(true)}
/>
);
}
// Fallback: Kreis mit Ländercode
return (
<span className="flag flag-fallback" style={style} aria-hidden>
{team?.code?.slice(0, 2) || "··"}
</span>
);
// Kein Team / kein Code: nichts rendern (kein Broken-Image)
return null;
}

View File

@@ -1,18 +1,17 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { fetchMatchesAndTeams, fetchOdds, attachOdds, attachKOOdds, fetchFifaScores, applyFifaScores, assignKONumbersBySlots, attachFifaGoals } from "@/lib/feeds";
import { fetchMatchesAndTeamsFifa, fetchOdds, attachOdds, attachKOOdds, fetchFifaScores, applyFifaScores, attachFifaGoals } from "@/lib/feeds";
import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings";
import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket";
import { securelyQualifiedThirdTeams } from "@/lib/third-place-security";
// Diese Route wird vom Frontend gepollt. Sie ist der einzige Ort, der die
// Upstream-Feeds berührt — gecacht, damit Rate Limits eingehalten werden.
// Diese Route wird vom Frontend gepollt. FIFA-API als Primärquelle.
export const dynamic = "force-dynamic";
export async function GET(request: NextRequest) {
const locale = request.nextUrl.searchParams.get("locale") || "de";
try {
const { matches: rawMatches, teams } = await fetchMatchesAndTeams();
const { matches: rawMatches, teams } = await fetchMatchesAndTeamsFifa(locale);
// Odds sind optional: fällt der Polymarket-Call aus, liefern wir trotzdem.
let matches = rawMatches;
@@ -24,13 +23,11 @@ export async function GET(request: NextRequest) {
console.error("[polymarket] fetchOdds fehlgeschlagen:", err);
}
// FIFA-Live-Scores (additiv, Fallback auf football-data)
assignKONumbersBySlots(matches, teams);
if (odds) {
matches = attachKOOdds(matches, teams, odds);
}
// FIFA-Live-Scores als häufiger gecachtes Overlay
try {
const fifaData = await fetchFifaScores(locale);
matches = applyFifaScores(matches, teams, fifaData);