Fortune-PlayLogic/components/betting/live-events-list.tsx
brooktewabe 8941c45555 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.
2026-03-02 19:08:52 +03:00

188 lines
8.3 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useEffect } from "react"
import Link from "next/link"
import { useBetslipStore } from "@/lib/store/betslip-store"
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, Monitor, Loader2 } from "lucide-react"
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 (
<div className="bg-brand-bg border-b border-white/5 hover:bg-white/5 transition-colors h-[50px] flex items-center group">
{/* Match Info Column (Time & Score) */}
<div className="flex items-center gap-3 px-3 w-[360px] shrink-0 h-full border-r border-white/5">
<div className="flex flex-col text-[10px] font-black leading-tight italic w-[55px] shrink-0 tabular-nums">
<span className="text-brand-primary">{time}</span>
<span className="text-white/40">{period}</span>
</div>
<div className="flex items-center min-w-0 flex-1 gap-2">
<Link
href={`/event/${event.id}`}
className="text-[11.5px] font-black text-white truncate italic uppercase hover:text-brand-primary transition-colors"
>
{event.homeTeam} <span className="text-brand-primary mx-1 tabular-nums">{score}</span> {event.awayTeam}
</Link>
</div>
<div className="flex items-center gap-2 shrink-0 px-1 opacity-20 group-hover:opacity-100 transition-opacity">
<BarChart2 className="size-3.5 text-white" />
<Monitor className="size-3.5 text-white" />
</div>
</div>
{/* Odds Grid or Placeholder */}
<div className="flex-1 h-full flex items-center">
{isNoOdds ? (
<div className="flex-1 h-full bg-brand-surface-light flex items-center justify-center text-[10.5px] font-black text-white/20 uppercase italic tracking-tight">
Sorry, no odds for this match
</div>
) : (
<div className="flex-1 grid grid-cols-3 h-full">
{event.markets.slice(0, 3).map((m, idx) => {
const labels = ["Home", "Draw", "Away"]
return (
<button
key={m.id}
onClick={() => addBet({
id: `${event.id}-${m.id}`,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: "1X2",
selection: m.label,
odds: m.odds,
})}
className="bg-brand-bg hover:bg-white/5 flex items-center justify-between px-4 h-full border-r border-white/5 transition-colors group/btn"
>
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white uppercase">{labels[idx]}</span>
<span className="text-[11px] font-black text-brand-primary tabular-nums">{m.odds.toFixed(2)}</span>
</button>
)
})}
</div>
)}
</div>
{/* Right Indicator */}
<div className="w-[4px] bg-brand-primary h-full" />
</div>
)
}
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() {
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<string, AppEvent[]>)
return (
<div className="flex flex-col min-h-screen bg-brand-bg">
{/* Sport Navigation: SportEnum ids, no league — event?sport_id=1&first_start_time=RFC3339&is_live=true */}
<div className="bg-brand-surface border-b border-border/20 px-2 flex items-center h-[54px] overflow-x-auto scrollbar-hide">
<div className="flex items-center gap-0 h-full">
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
<span className="text-[14px]"></span>
<span className="text-[9px] font-bold text-white/40 uppercase mt-0.5">Favourites</span>
</button>
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
<span className="text-[14px]"></span>
<span className="text-[9px] font-bold text-white/40 uppercase mt-0.5">Prematch</span>
</button>
{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 (
<button
key={id}
type="button"
onClick={() => setSportId(id)}
className={cn(
"flex flex-col items-center justify-center px-3 h-full border-r border-white/5 min-w-[75px] relative transition-colors",
active ? "bg-white/5" : "hover:bg-white/5"
)}
>
<span className="text-[16px]">{icon}</span>
<span className={cn(
"text-[9px] font-bold uppercase mt-1 tracking-tighter whitespace-nowrap",
active ? "text-brand-primary" : "text-white/40"
)}>
{info.name}
</span>
{active && <div className="absolute bottom-0 left-0 right-0 h-[2px] bg-brand-primary" />}
</button>
)
})}
</div>
</div>
{/* Category Header */}
<div className="bg-brand-primary px-3 py-1.5 flex items-center gap-2 border-l-4 border-brand-primary">
<span className="text-[16px]">{SPORT_ID_MAP[sportId]?.name === "Soccer" ? "⚽" : "•"}</span>
<h2 className="text-[14px] font-black text-white uppercase tracking-tight">
{SPORT_ID_MAP[sportId]?.name ?? "Live"}
</h2>
</div>
{loading && events.length === 0 ? (
<div className="flex items-center justify-center py-16 gap-2 text-white/60">
<Loader2 className="size-5 animate-spin" />
<span className="text-sm">Loading live events</span>
</div>
) : error && events.length === 0 ? (
<div className="py-8 px-4 text-center text-white/60 text-sm">{error}</div>
) : (
<div className="flex flex-col mb-10">
{Object.entries(groupedByLeague).map(([leagueName, matches]) => (
<div key={leagueName} className="flex flex-col">
<div className="bg-brand-surface px-3 py-1 border-b border-border/10 flex items-center gap-2">
<span className="text-[9.5px] font-black text-white/60 uppercase tracking-widest leading-none">
{leagueName}
</span>
</div>
<div className="flex flex-col">
{matches.map((match) => (
<LiveEventRow
key={match.id}
event={match}
isNoOdds={!match.markets?.length || match.markets.every((m) => m.odds <= 0)}
/>
))}
</div>
</div>
))}
</div>
)}
</div>
)
}