From 8941c4555595d8734e1fde17ae96705bde815fe3 Mon Sep 17 00:00:00 2001 From: brooktewabe Date: Mon, 2 Mar 2026 19:08:52 +0300 Subject: [PATCH] Refactor live events list and match detail view components - Updated LiveEventsList to use live data from the live store and improved event rendering with links to event details. - Enhanced MatchDetailView to accept API sections for dynamic market rendering and improved state management for expanded sections. - Modified SportsNav to utilize search parameters for active sport highlighting. - Refactored TopMatches to fetch live match data and odds from the API, replacing static fallback data. - Improved UI elements for better responsiveness and user experience across components. --- components/betting/events-list.tsx | 337 ++++++++++++++++------- components/betting/live-events-list.tsx | 190 ++++++------- components/betting/match-detail-view.tsx | 113 ++++---- components/betting/sport-home.tsx | 2 +- components/betting/sports-nav.tsx | 23 +- components/betting/top-matches.tsx | 125 +++++---- 6 files changed, 495 insertions(+), 295 deletions(-) diff --git a/components/betting/events-list.tsx b/components/betting/events-list.tsx index c322716..9fe52e8 100644 --- a/components/betting/events-list.tsx +++ b/components/betting/events-list.tsx @@ -5,8 +5,11 @@ import Link from "next/link" import { useSearchParams } from "next/navigation" import { useBetslipStore } from "@/lib/store/betslip-store" import { mockEvents, popularLeagues, type Event } from "@/lib/mock-data" +import { useBettingStore } from "@/lib/store/betting-store" +import type { AppEvent } from "@/lib/store/betting-types" +import { SPORT_SLUG_TO_ID, getMarketsForTab, type ApiOdds, type MarketTabKey } from "@/lib/store/betting-api" import { cn } from "@/lib/utils" -import { ChevronDown, BarChart2, TrendingUp, Plus } from "lucide-react" +import { ChevronDown, BarChart2, TrendingUp, Plus, Loader2 } from "lucide-react" function OddsButton({ odds, onClick, isSelected }: { odds: number @@ -26,7 +29,7 @@ function OddsButton({ odds, onClick, isSelected }: { ) } -function EventRow({ event }: { event: Event }) { +function EventRow({ event }: { event: Event | AppEvent }) { const { bets, addBet } = useBetslipStore() return ( @@ -82,22 +85,47 @@ function EventRow({ event }: { event: Event }) { ) } -const MARKET_HEADERS = ["1", "x", "2", "Over (2.5)", "Under (2.5)", "1X", "12", "X2", "Yes", "No"]; +const MARKET_HEADERS = ["1", "X", "2", "Over (2.5)", "Under (2.5)", "1X", "12", "X2", "Yes", "No"] -export function EventsList({ filter = "All", sport = "all", search = "" }: { +const ROW1_TABS: { key: MarketTabKey; label: string }[] = [ + { key: "main", label: "Main" }, + { key: "goals", label: "Goals" }, + { key: "handicap", label: "Handicap" }, + { key: "half_time", label: "Half Time / Full Time" }, + { key: "correct_score", label: "Correct Score" }, +] +const ROW2_TABS: { key: MarketTabKey; label: string }[] = [ + { key: "1st_half", label: "1st Half" }, + { key: "2nd_half", label: "2nd Half" }, + { key: "combo", label: "Combo" }, + { key: "chance_mix", label: "Chance Mix" }, + { key: "home", label: "Home" }, +] + +export function EventsList({ filter = "All", sport: sportProp = "all", search = "" }: { filter?: string sport?: string search?: string }) { const searchParams = useSearchParams() const leagueQuery = searchParams.get("league") + const sportQuery = searchParams.get("sport") ?? sportProp const [selectedLeague, setSelectedLeague] = useState(leagueQuery) + const [activeTab, setActiveTab] = useState("main") const { bets, addBet } = useBetslipStore() + const sportId = sportQuery === "all" ? null : (SPORT_SLUG_TO_ID[sportQuery] ?? null) + const leagueId = selectedLeague && !Number.isNaN(Number(selectedLeague)) ? selectedLeague : null + const { events: apiEvents, loading, error, hasMore, loadMore, setFilters } = useBettingStore() + useEffect(() => { setSelectedLeague(leagueQuery) }, [leagueQuery]) + useEffect(() => { + setFilters(sportId, leagueId) + }, [sportId, leagueId, setFilters]) + const handleClose = () => { const url = new URL(window.location.href) url.searchParams.delete("league") @@ -105,13 +133,17 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: { setSelectedLeague(null) } - const events = selectedLeague - ? mockEvents.filter(e => e.league.toLowerCase() === selectedLeague.toLowerCase()) - : mockEvents.filter((e) => { - if (filter === "Live" && !e.isLive) return false - if (sport !== "all" && e.sport.toLowerCase() !== sport.toLowerCase()) return false - return true - }) + const useApi = !(error && apiEvents.length === 0) + const events = useApi + ? (filter === "Live" ? apiEvents.filter((e) => e.isLive) : apiEvents) + : selectedLeague + ? mockEvents.filter((e) => e.league.toLowerCase() === selectedLeague.toLowerCase()) + : mockEvents.filter((e) => { + if (filter === "Live" && !e.isLive) return false + if (sportProp !== "all" && e.sport.toLowerCase() !== sportProp.toLowerCase()) return false + return true + }) + const showLoadMore = useApi && hasMore && events.length > 0 // Common Header Rendering const renderTableHeaders = () => ( @@ -135,75 +167,130 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: { ) - const renderColumnHeaders = () => ( -
-
Main
-
Over/Under
-
- {MARKET_HEADERS.map(h => {h})} -
-
-
- ) + const getHeadersForTab = (tab: MarketTabKey) => { + const first = events[0] + const rawOdds: ApiOdds[] = first && "rawOdds" in first && Array.isArray((first as AppEvent).rawOdds) ? (first as AppEvent).rawOdds! : [] + return getMarketsForTab(rawOdds, tab).headers + } - const renderEventItem = (event: Event) => ( -
- {/* Stats & Icons */} -
- + const renderColumnHeaders = (tab: MarketTabKey, eventList: (Event | AppEvent)[]) => { + let headers = eventList.length ? getHeadersForTab(tab) : getMarketsForTab([], tab).headers + if (!headers.length) headers = getMarketsForTab([], "main").headers + const n = Math.max(headers.length, 1) + return ( +
+
+
+
ID
+
Time
+
Event
+
+ {headers.map((h, i) => ( + + {h} + + ))} +
+
+
- {/* ID */} -
- {event.id} + ) + } + + const renderEventItem = (event: Event | AppEvent, tab: MarketTabKey) => { + const hasRawOdds = event && "rawOdds" in event && Array.isArray((event as AppEvent).rawOdds) + const rawOdds = hasRawOdds ? (event as AppEvent).rawOdds! : [] + const { cells } = getMarketsForTab(rawOdds, tab) + const useMainMarkets = !hasRawOdds && event.markets?.length && (tab === "main" || tab === "combo" || tab === "chance_mix" || tab === "home") + const displayCells = useMainMarkets + ? event.markets.slice(0, 10).map((m, i) => ({ id: m.id, label: MARKET_HEADERS[i] ?? m.label, odds: m.odds })) + : cells + const n = Math.max(displayCells.length, 1) + return ( +
+
+ +
+
+ {event.id} +
+
+ {event.time} + PM +
+ + {event.homeTeam} - {event.awayTeam} + +
+ {displayCells.map((cell) => { + const betId = `${event.id}-${cell.id}` + const isSelected = bets.some((b) => b.id === betId) + const hasOdds = cell.odds > 0 + return ( + + ) + })} +
+ + +
- {/* Time */} -
- {event.time} - PM + ) + } + + if (loading && events.length === 0) { + return ( +
+ +

Loading events and odds…

+ {/*

Resolving odds for each event

*/}
- {/* Event Name -> same route as + icon (match detail) */} - - {event.homeTeam} - {event.awayTeam} - - {/* Odds Grid */} -
- {event.markets.slice(0, 10).map((market) => { - const betId = `${event.id}-${market.id}` - const isSelected = bets.some((b) => b.id === betId) - return ( - - ) - })} + ) + } + + if (error && apiEvents.length === 0 && events.length === 0) { + return ( +
+

{error}

+

Check NEXT_PUBLIC_BETTING_API_BASE_URL and tenant.

- {/* More Button -> match detail page */} - - - -
- ) + ) + } if (selectedLeague) { // Group by date for league view @@ -211,7 +298,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: { if (!acc[event.date]) acc[event.date] = [] acc[event.date].push(event) return acc - }, {} as Record) + }, {} as Record) return (
@@ -231,26 +318,41 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
- {/* Large Market Tab Grid */} -
- {[ - { label: "Main", active: true }, { label: "Goals" }, { label: "Handicap" }, { label: "Half Time / Full Time" }, { label: "Correct Score" }, - { label: "1st Half" }, { label: "2nd Half" }, { label: "Asian Markets" }, { label: "Corners" }, { label: "Home" } - ].map((m, i) => ( - - ))} + {/* Market category tabs row 1: Main, Goals, Handicap, Half Time / Full Time, Correct Score */} +
+ {ROW1_TABS.map(({ key, label }) => ( + + ))} +
+ {/* Row 2: 1st Half, 2nd Half, Combo, Chance Mix, Home */} +
+ {ROW2_TABS.map(({ key, label }) => ( + + ))}
- {/* Column Headers */} - {renderColumnHeaders()} + {/* Column Headers (dynamic by tab) */} + {renderColumnHeaders(activeTab, events)} {/* Grouped Events */}
@@ -259,10 +361,29 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
{date}
- {dateEvents.map(event => renderEventItem(event))} + {dateEvents.map((event) => renderEventItem(event, activeTab))}
))}
+ {showLoadMore && ( +
+ +
+ )}
) } @@ -276,6 +397,11 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: { return (
+ {error && ( +
+ Showing sample data. API: {error} +
+ )}
{Object.entries(homeEventsByLeague).map(([leagueName, leagueEvents]) => (
@@ -312,11 +438,30 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: { {/* Matches in this league */}
- {leagueEvents.map(event => renderEventItem(event))} + {leagueEvents.map((event) => renderEventItem(event, "main"))}
))}
+ {showLoadMore && ( +
+ +
+ )}
) } diff --git a/components/betting/live-events-list.tsx b/components/betting/live-events-list.tsx index 6803c28..30c603f 100644 --- a/components/betting/live-events-list.tsx +++ b/components/betting/live-events-list.tsx @@ -1,17 +1,19 @@ "use client" +import { useEffect } from "react" +import Link from "next/link" import { useBetslipStore } from "@/lib/store/betslip-store" -import { mockEvents, type Event } from "@/lib/mock-data" +import { useLiveStore } from "@/lib/store/live-store" +import { SportEnum } from "@/lib/store/betting-types" +import { SPORT_ID_MAP } from "@/lib/store/betting-api" +import type { AppEvent } from "@/lib/store/betting-types" import { cn } from "@/lib/utils" -import { BarChart2, TrendingUp, Monitor, Tv } from "lucide-react" +import { BarChart2, Monitor, Loader2 } from "lucide-react" - -function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean }) { - const { bets, addBet } = useBetslipStore() - - // Dummy data for demonstration - const score = event.homeScore !== undefined ? `${event.homeScore} - ${event.awayScore}` : "0 - 0" - const time = event.liveMinute ? `${event.liveMinute}:00` : "83:10" +function LiveEventRow({ event, isNoOdds }: { event: AppEvent; isNoOdds?: boolean }) { + const { addBet } = useBetslipStore() + const score = event.score ?? "0 - 0" + const time = event.matchMinute != null ? `${String(event.matchMinute).padStart(2, "0")}:00` : "—" const period = "H2" return ( @@ -23,9 +25,12 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean }) {period}
- + {event.homeTeam} {score} {event.awayTeam} - +
@@ -71,64 +76,38 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean }) ) } -const liveSports = [ - { id: "soccer", label: "Soccer", icon: "⚽", count: 25, active: true }, - { id: "basketball", label: "Basketball", icon: "🏀", count: 39 }, - { id: "ice-hockey", label: "Ice Hockey", icon: "🏒", count: 3 }, - { id: "tennis", label: "Tennis", icon: "🎾", count: 4 }, - { id: "handball", label: "Handball", icon: "🤾", count: 10 }, - { id: "rugby", label: "Rugby", icon: "🏉", count: 2 }, - { id: "table-tennis", label: "Table Tennis", icon: "🏓", count: 8 }, - { id: "volleyball", label: "Volleyball", icon: "🏐", count: 7 }, - { id: "futsal", label: "Futsal", icon: "⚽", count: 2 }, - { id: "esport-counter-strike", label: "ESport Cou...", icon: "🎮", count: 2 }, - { id: "esport-league-of-legends", label: "ESport Lea...", icon: "🎮", count: 1 }, - { id: "esport-dota-2", label: "ESport Dota", icon: "🎮", count: 1 }, - { id: "efootball", label: "eFootball", icon: "⚽", count: 4 }, - { id: "ebasketball", label: "eBasketball", icon: "🏀", count: 1 }, -] +const LIVE_SPORT_IDS = [ + SportEnum.SOCCER, + SportEnum.BASKETBALL, + SportEnum.ICE_HOCKEY, + SportEnum.TENNIS, + SportEnum.HANDBALL, + SportEnum.RUGBY_UNION, + SportEnum.TABLE_TENNIS, + SportEnum.VOLLEYBALL, + SportEnum.FUTSAL, + SportEnum.E_SPORTS, +] as const export function LiveEventsList() { - // Enhanced mock data local to live view to match screenshot exactly - const liveMatches = [ - { - league: "Algeria - Ligue 1", - flag: "https://flagcdn.com/w20/dz.png", - matches: [ - { ...mockEvents[0], id: "l1", homeTeam: "Paradou AC", awayTeam: "Ben Aknoun", homeScore: 3, awayScore: 5, liveMinute: 91, noOdds: true } - ] - }, - { - league: "Australia - U23 Victoria NPL", - flag: "https://flagcdn.com/w20/au.png", - matches: [ - { ...mockEvents[1], id: "l2", homeTeam: "Oakleigh Cannons FC", awayTeam: "Altona Magic SC", homeScore: 5, awayScore: 1, liveMinute: 87, noOdds: true } - ] - }, - { - league: "Australia - U23 Victoria Premier League 1", - flag: "https://flagcdn.com/w20/au.png", - matches: [ - { ...mockEvents[2], id: "l3", homeTeam: "Northcote City FC", awayTeam: "Western United FC", homeScore: 4, awayScore: 0, liveMinute: 83, noOdds: false }, - { ...mockEvents[3], id: "l4", homeTeam: "Melbourne Knights FC", awayTeam: "Melbourne Victory FC", homeScore: 0, awayScore: 3, liveMinute: 81, noOdds: true } - ] - }, - { - league: "Australia - Victoria NPL, Women", - flag: "https://flagcdn.com/w20/au.png", - matches: [ - { ...mockEvents[4], id: "l5", homeTeam: "Preston Lions FC", awayTeam: "South Melbourne FC", homeScore: 1, awayScore: 1, liveMinute: 52, noOdds: true }, - { ...mockEvents[0], id: "l6", homeTeam: "Bentleigh Greens SC", awayTeam: "Box Hill United", homeScore: 0, awayScore: 6, liveMinute: 83, noOdds: true } - ] - } - ] + const { events, loading, error, sportId, setSportId, loadLiveEvents } = useLiveStore() + + useEffect(() => { + loadLiveEvents() + }, [loadLiveEvents]) + + const groupedByLeague = events.reduce((acc, ev) => { + const key = ev.league || "Other" + if (!acc[key]) acc[key] = [] + acc[key].push(ev) + return acc + }, {} as Record) return (
- {/* Sport Navigation Carousel */} + {/* Sport Navigation: SportEnum ids, no league — event?sport_id=1&first_start_time=RFC3339&is_live=true */}
- {/* Favourites & Prematch */} - - {/* Live Sports */} - {liveSports.map((sport) => ( - - ))} + {LIVE_SPORT_IDS.map((id) => { + const info = SPORT_ID_MAP[id] + if (!info) return null + const icon = id === SportEnum.SOCCER ? "⚽" : id === SportEnum.TENNIS ? "🎾" : id === SportEnum.BASKETBALL ? "🏀" : id === SportEnum.ICE_HOCKEY ? "🏒" : id === SportEnum.VOLLEYBALL ? "🏐" : id === SportEnum.HANDBALL ? "🤾" : id === SportEnum.E_SPORTS ? "🎮" : "⚽" + const active = sportId === id + return ( + + ) + })}
- {/* Category Header (Soccer) */} -
- -

Soccer

+ {/* Category Header */} +
+ {SPORT_ID_MAP[sportId]?.name === "Soccer" ? "⚽" : "•"} +

+ {SPORT_ID_MAP[sportId]?.name ?? "Live"} +

- {/* Grouped Live Matches */} + {loading && events.length === 0 ? ( +
+ + Loading live events… +
+ ) : error && events.length === 0 ? ( +
{error}
+ ) : (
- {liveMatches.map((group, gIdx) => ( -
- {/* League Header */} + {Object.entries(groupedByLeague).map(([leagueName, matches]) => ( +
- {group.league} - {group.league} + {leagueName}
- - {/* Matches in this league */}
- {group.matches.map((match, mIdx) => ( - + {matches.map((match) => ( + m.odds <= 0)} + /> ))}
))}
+ )}
) } diff --git a/components/betting/match-detail-view.tsx b/components/betting/match-detail-view.tsx index d73f1e0..65ded2e 100644 --- a/components/betting/match-detail-view.tsx +++ b/components/betting/match-detail-view.tsx @@ -9,6 +9,8 @@ import { type Event, type DetailMarketSection, } from "@/lib/mock-data" + +type ApiSection = { id: string; title: string; outcomes: { label: string; odds: number }[] } import { cn } from "@/lib/utils" import { ChevronDown, ChevronUp } from "lucide-react" @@ -77,11 +79,12 @@ function MarketSectionBlock({
{section.outcomes.length > 2 && section.outcomes.length % 2 === 0 ? (
- {section.outcomes.map((outcome) => { - const betId = `${event.id}-${section.id}-${outcome.label.replace(/\s/g, "-").toLowerCase()}` + {section.outcomes.map((outcome, i) => { + const oddsStr = typeof outcome.odds === "number" ? outcome.odds.toFixed(2) : String(outcome.odds) + const betId = `${event.id}-${section.id}-${i}-${outcome.label.replace(/\s/g, "-").toLowerCase()}-${oddsStr}` const isSelected = bets.some((b) => b.id === betId) return ( -
+
{outcome.label}
- {/* Two-column grid of market sections */} + {/* Two-column grid of market sections (split evenly so both columns are used) */}
{/* Left column */} @@ -247,21 +268,19 @@ export function MatchDetailView({ event }: { event: Event }) { /> ))}
- {/* Right column (Cards/Bookings only) */} - {rightSections.length > 0 && ( -
- {rightSections.map((section) => ( - toggleSection(section.id)} - /> - ))} -
- )} + {/* Right column */} +
+ {rightSections.map((section) => ( + toggleSection(section.id)} + /> + ))} +
diff --git a/components/betting/sport-home.tsx b/components/betting/sport-home.tsx index 172f5e6..f7e016e 100644 --- a/components/betting/sport-home.tsx +++ b/components/betting/sport-home.tsx @@ -21,7 +21,7 @@ export function SportHome() { )} - +
) } diff --git a/components/betting/sports-nav.tsx b/components/betting/sports-nav.tsx index ae3f6d8..40fffbc 100644 --- a/components/betting/sports-nav.tsx +++ b/components/betting/sports-nav.tsx @@ -1,4 +1,9 @@ -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" +"use client" + +import Link from "next/link" +import { useSearchParams } from "next/navigation" +import { TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Tabs } from "@/components/ui/tabs" const sports = [ { id: "football", name: "Football", icon: "⚽" }, @@ -14,17 +19,23 @@ const sports = [ ] export function SportsNav() { + const searchParams = useSearchParams() + const currentSport = searchParams.get("sport") ?? "football" + return ( - + {sports.map((sport) => ( - - {sport.icon} - {sport.name} + + {sport.icon} + {sport.name} + ))} diff --git a/components/betting/top-matches.tsx b/components/betting/top-matches.tsx index e1fefef..d255080 100644 --- a/components/betting/top-matches.tsx +++ b/components/betting/top-matches.tsx @@ -1,58 +1,91 @@ "use client" +import { useState, useEffect } from "react" import { ChevronRight } from "lucide-react" import { useBetslipStore } from "@/lib/store/betslip-store" +import { + fetchEvents, + fetchOddsForEvent, + get1X2FromOddsResponse, + TOP_LEAGUES, + type ApiEvent, +} from "@/lib/betting-api" import { cn } from "@/lib/utils" -const topMatches = [ - { - id: "tm1", - league: "England - Premier League", - time: "05:00 PM", - homeTeam: "Nottingham Forest", - awayTeam: "Liverpool", - odds: { home: 4.09, draw: 3.93, away: 1.82 } - }, - { - id: "tm2", - league: "England - Premier League", - time: "11:00 PM", - homeTeam: "Man City", - awayTeam: "Newcastle", - odds: { home: 1.50, draw: 5.17, away: 5.93 } - }, - { - id: "tm3", - league: "England - Premier League", - time: "06:00 PM", - homeTeam: "Chelsea", - awayTeam: "Burnley", - odds: { home: 1.21, draw: 6.91, away: 11.50 } - }, - { - id: "tm4", - league: "Spain - LaLiga", - time: "07:30 PM", - homeTeam: "Arsenal", - awayTeam: "Wolves", - odds: { home: 1.56, draw: 4.16, away: 5.80 } - }, - { - id: "tm5", - league: "Italy - Serie A", - time: "09:45 PM", - homeTeam: "Inter Milan", - awayTeam: "Napoli", - odds: { home: 1.85, draw: 3.60, away: 4.20 } - } +type TopMatch = { + id: string + league: string + time: string + homeTeam: string + awayTeam: string + odds: { home: number; draw: number; away: number } +} + +const FALLBACK_MATCHES: TopMatch[] = [ + { id: "tm1", league: "England - Premier League", time: "05:00 PM", homeTeam: "Nottingham Forest", awayTeam: "Liverpool", odds: { home: 4.09, draw: 3.93, away: 1.82 } }, + { id: "tm2", league: "England - Premier League", time: "11:00 PM", homeTeam: "Man City", awayTeam: "Newcastle", odds: { home: 1.50, draw: 5.17, away: 5.93 } }, + { id: "tm3", league: "England - Premier League", time: "06:00 PM", homeTeam: "Chelsea", awayTeam: "Burnley", odds: { home: 1.21, draw: 6.91, away: 11.50 } }, + { id: "tm4", league: "Spain - LaLiga", time: "07:30 PM", homeTeam: "Arsenal", awayTeam: "Wolves", odds: { home: 1.56, draw: 4.16, away: 5.80 } }, + { id: "tm5", league: "Italy - Serie A", time: "09:45 PM", homeTeam: "Inter Milan", awayTeam: "Napoli", odds: { home: 1.85, draw: 3.60, away: 4.20 } }, ] +function parseTime(iso: string): string { + try { + const d = new Date(iso) + return d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: true }) + } catch { + return "--:--" + } +} + export function TopMatches() { const { bets, addBet } = useBetslipStore() + const [matches, setMatches] = useState(FALLBACK_MATCHES) + + useEffect(() => { + let cancelled = false + const TOP_MATCHES_SIZE = 5 + const leagueIds = TOP_LEAGUES.slice(0, 4).map((l) => l.id) + const nameById = Object.fromEntries(TOP_LEAGUES.map((l) => [l.id, l.name])) + + Promise.all(leagueIds.map((league_id) => fetchEvents({ league_id, page_size: 2, page: 1 }))) + .then(async (leagueResponses) => { + if (cancelled) return + const list: TopMatch[] = [] + const eventMeta: { e: ApiEvent; leagueName: string }[] = [] + leagueResponses.forEach((res, i) => { + const leagueName = nameById[leagueIds[i]] ?? "" + const events = res.data ?? [] + for (const e of events) { + eventMeta.push({ e, leagueName }) + if (eventMeta.length >= TOP_MATCHES_SIZE) break + } + }) + const oddsResponses = await Promise.all( + eventMeta.slice(0, TOP_MATCHES_SIZE).map(({ e }) => fetchOddsForEvent(e.id).catch(() => ({ data: [] }))) + ) + eventMeta.slice(0, TOP_MATCHES_SIZE).forEach(({ e, leagueName }, i) => { + const mainOdds = get1X2FromOddsResponse(oddsResponses[i]?.data ?? []) + list.push({ + id: String(e.id), + league: leagueName, + time: parseTime(e.start_time), + homeTeam: e.home_team, + awayTeam: e.away_team, + odds: mainOdds + ? { home: mainOdds["1"], draw: mainOdds.X, away: mainOdds["2"] } + : { home: 0, draw: 0, away: 0 }, + }) + }) + if (list.length > 0) setMatches(list) + }) + .catch(() => {}) + return () => { cancelled = true } + }, []) return (
- {topMatches.map((match) => { + {matches.map((match) => { const eventName = `${match.homeTeam} - ${match.awayTeam}` const leagueForBet = `Football - ${match.league}` const outcomes = [ @@ -61,8 +94,8 @@ export function TopMatches() { { key: "2", label: "2", odds: match.odds.away }, ] as const return ( -
{/* Top Label Ribbon */} @@ -94,7 +127,7 @@ export function TopMatches() {
-
+
{outcomes.map(({ key, label, odds }) => { const betId = `${match.id}-${key}` const isSelected = bets.some((b) => b.id === betId) @@ -123,7 +156,7 @@ export function TopMatches() { {label} - {odds.toFixed(2)} + {odds > 0 ? odds.toFixed(2) : "—"} )