betting and ui components

This commit is contained in:
brooktewabe 2026-03-01 14:23:23 +03:00
parent 5587dff57c
commit 9029fb4f6a
14 changed files with 892 additions and 241 deletions

View File

@ -2,183 +2,203 @@
import { useState } from "react"
import { useBetslipStore } from "@/lib/store/betslip-store"
import { X, ChevronDown, Trash2 } from "lucide-react"
import { X, Save, Trash2 } from "lucide-react"
import { cn } from "@/lib/utils"
const quickStakes = [5, 10, 20, 50, 100, 200]
const quickStakes = [10, 50, 100, 1000, 2000, 5000]
export function Betslip() {
const { bets, removeBet, clearBets, updateStake, getPotentialWin, getTotalOdds } = useBetslipStore()
const [activeTab, setActiveTab] = useState<"single" | "accumulator">("single")
const [globalStake, setGlobalStake] = useState("10")
const potentialWin = getPotentialWin()
const totalOdds = getTotalOdds()
const isMulti = bets.length > 1
const potentialWin = isMulti
? Number(globalStake) * totalOdds
: getPotentialWin()
return (
<div className="bg-card rounded border border-border overflow-hidden">
{/* Header */}
<div className="bg-primary px-3 py-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-[11px] font-bold text-primary-foreground uppercase">Betslip</span>
{bets.length > 0 && (
<span className="bg-primary-foreground text-primary text-[10px] font-bold px-1.5 py-0.5 rounded-full min-w-[18px] text-center">
{bets.length}
</span>
)}
</div>
{bets.length > 0 && (
<button
onClick={clearBets}
className="text-primary-foreground/70 hover:text-primary-foreground transition-colors"
>
<Trash2 className="size-3.5" />
</button>
)}
</div>
{/* Tabs */}
{bets.length > 1 && (
<div className="flex border-b border-border">
{(["single", "accumulator"] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={cn(
"flex-1 py-1.5 text-[10px] font-semibold uppercase transition-colors",
activeTab === tab
? "bg-muted text-foreground border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
)}
>
{tab}
</button>
))}
</div>
)}
{/* Content */}
<div className="p-2 space-y-2">
<div className="overflow-hidden">
<div className="space-y-3">
{bets.length === 0 ? (
<div className="py-6 text-center">
<div className="text-2xl mb-2">🎯</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
No bets selected. Click on odds to add selections.
<div className="py-10 px-4 text-center">
<p className="text-[11px] text-muted-foreground font-medium leading-relaxed">
No bet has been selected. To select a bet, please click on the respective odds
</p>
</div>
) : (
<>
{/* Bet items */}
{/* Bet cards */}
<div className="space-y-2">
{bets.map((bet) => (
<div key={bet.id} className="bg-secondary/50 rounded border border-border/50 p-2">
<div className="flex items-start justify-between gap-2 mb-2">
<div key={bet.id} className="bg-brand-surface-light border border-border/30 p-2.5">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="text-[11px] font-semibold text-foreground truncate">{bet.event}</div>
<div className="text-[10px] text-muted-foreground truncate">{bet.league}</div>
<div className="text-[10px] text-primary font-medium mt-0.5">
{bet.market}: <span className="text-foreground">{bet.selection}</span>
<div className="text-[11px] font-bold text-white uppercase leading-tight truncate">
{bet.event}
</div>
<div className="text-[10px] text-white/50 uppercase mt-0.5 truncate">
{bet.league}
</div>
<div className="flex items-center justify-between gap-2 mt-1.5">
<span className="text-[10px] text-white/80">
{bet.market}: <span className="font-bold text-white">{bet.selection}</span>
</span>
<span className="text-[11px] font-bold text-brand-primary shrink-0">{bet.odds.toFixed(2)}</span>
</div>
{!isMulti && (
<div className="flex justify-between text-[10px] text-white/50 mt-0.5">
<span>Odds</span>
<span className="text-brand-primary font-bold">{bet.odds.toFixed(2)}</span>
</div>
)}
</div>
<div className="flex items-center gap-1.5 shrink-0">
<span className="text-sm font-bold text-primary">{bet.odds.toFixed(2)}</span>
<button
onClick={() => removeBet(bet.id)}
className="text-muted-foreground hover:text-destructive transition-colors"
>
<X className="size-3.5" />
</button>
</div>
<button
onClick={() => removeBet(bet.id)}
className="text-white/50 hover:text-white transition-colors shrink-0 p-0.5"
aria-label="Remove bet"
>
<X className="size-3.5" />
</button>
</div>
{/* Stake input for single */}
{(activeTab === "single" || bets.length === 1) && (
<div>
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
<div className="flex gap-1">
{/* Single bet: stake on card */}
{!isMulti && (
<div className="mt-3 pt-3 border-t border-border/20">
<div className="flex items-center gap-1 mb-2">
<button
type="button"
onClick={() => updateStake(bet.id, Math.max(1, (bet.stake ?? 10) - 1))}
className="w-7 h-7 flex items-center justify-center rounded bg-white/10 text-white text-sm font-bold hover:bg-white/20"
>
</button>
<input
type="number"
value={bet.stake ?? 10}
onChange={(e) => updateStake(bet.id, Number(e.target.value))}
className="flex-1 bg-input border border-border rounded px-2 py-1 text-xs text-foreground w-full min-w-0 focus:outline-none focus:border-primary"
onChange={(e) => updateStake(bet.id, Number(e.target.value) || 10)}
className="flex-1 h-7 bg-brand-bg border border-border/40 px-2 text-[11px] text-white text-center font-bold focus:outline-none focus:border-brand-primary"
min="1"
/>
<button
type="button"
onClick={() => updateStake(bet.id, (bet.stake ?? 10) + 1)}
className="w-7 h-7 flex items-center justify-center rounded bg-white/10 text-white text-sm font-bold hover:bg-white/20"
>
+
</button>
</div>
<div className="flex gap-1 mt-1 flex-wrap">
<div className="flex flex-wrap gap-1.5">
{quickStakes.map((s) => (
<button
key={s}
onClick={() => updateStake(bet.id, s)}
className={cn(
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
"size-7 rounded-full text-[10px] font-bold transition-colors",
(bet.stake ?? 10) === s
? "bg-primary text-primary-foreground border-primary"
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
? "bg-brand-primary text-black"
: "bg-brand-primary/20 text-brand-primary hover:bg-brand-primary/30"
)}
>
{s}
</button>
))}
</div>
{/* Potential win */}
<div className="mt-1.5 flex justify-between text-[10px]">
<span className="text-muted-foreground">Potential win:</span>
<span className="text-primary font-semibold">
{((bet.stake ?? 10) * bet.odds).toFixed(2)} ETB
</span>
</div>
</div>
)}
</div>
))}
</div>
{/* Accumulator stake section */}
{activeTab === "accumulator" && bets.length > 1 && (
<div className="bg-secondary/50 rounded border border-border/50 p-2 space-y-2">
<div className="flex justify-between text-[11px]">
<span className="text-muted-foreground">Total Odds:</span>
<span className="font-bold text-primary">{totalOdds.toFixed(2)}</span>
{/* Single bet: potential winning below card */}
{!isMulti && (
<div className="flex justify-between items-center text-[11px] font-bold text-white">
<span className="text-white/70">Potential winning</span>
<span>{potentialWin.toFixed(2)} ETB</span>
</div>
)}
{/* Multiple bets: combined Odds, one stake, quick buttons, potential winning */}
{isMulti && (
<div className="bg-brand-surface-light border border-border/30 p-2.5 space-y-2.5">
<div className="flex justify-between text-[11px] font-bold text-white">
<span className="text-white/70">Odds</span>
<span className="text-brand-primary">{totalOdds.toFixed(2)}</span>
</div>
<div>
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
<div className="flex items-center gap-1">
<button
type="button"
onClick={() => setGlobalStake(String(Math.max(1, Number(globalStake) - 1)))}
className="w-7 h-7 flex items-center justify-center rounded bg-white/10 text-white text-sm font-bold hover:bg-white/20"
>
</button>
<input
type="number"
value={globalStake}
onChange={(e) => setGlobalStake(e.target.value)}
className="w-full bg-input border border-border rounded px-2 py-1 text-xs text-foreground focus:outline-none focus:border-primary"
className="flex-1 h-7 bg-brand-bg border border-border/40 px-2 text-[11px] text-white text-center font-bold focus:outline-none focus:border-brand-primary"
min="1"
/>
<div className="flex gap-1 mt-1 flex-wrap">
{quickStakes.map((s) => (
<button
key={s}
onClick={() => setGlobalStake(String(s))}
className={cn(
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
globalStake === String(s)
? "bg-primary text-primary-foreground border-primary"
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
)}
>
{s}
</button>
))}
</div>
<div className="mt-1.5 flex justify-between text-[10px]">
<span className="text-muted-foreground">Potential win:</span>
<span className="text-primary font-semibold">
{(Number(globalStake) * totalOdds).toFixed(2)} ETB
</span>
</div>
<button
type="button"
onClick={() => setGlobalStake(String(Number(globalStake) + 1))}
className="w-7 h-7 flex items-center justify-center rounded bg-white/10 text-white text-sm font-bold hover:bg-white/20"
>
+
</button>
</div>
<div className="flex flex-wrap gap-1.5">
{quickStakes.map((s) => (
<button
key={s}
onClick={() => setGlobalStake(String(s))}
className={cn(
"size-7 rounded-full text-[10px] font-bold transition-colors",
Number(globalStake) === s
? "bg-brand-primary text-black"
: "bg-brand-primary/20 text-brand-primary hover:bg-brand-primary/30"
)}
>
{s}
</button>
))}
</div>
<div className="flex justify-between text-[11px] font-bold text-white pt-0.5">
<span className="text-white/70">Potential winning</span>
<span className="text-brand-primary">{(Number(globalStake) * totalOdds).toFixed(2)} ETB</span>
</div>
</div>
)}
{/* Place bet button */}
<button className="w-full bg-primary text-primary-foreground text-[12px] font-bold py-2.5 rounded hover:opacity-90 active:scale-95 transition-all uppercase tracking-wide">
Place Bet
</button>
<div className="flex items-center gap-2 py-2 px-1 text-[10px] text-brand-primary/90">
<svg viewBox="0 0 24 24" className="size-4 shrink-0 fill-brand-primary" aria-hidden><path d="M12 2L1 21h22L12 2zm0 3.99L19.53 19H4.47L12 5.99zM11 10v4h2v-2h-2v-2zm0 6v2h2v-2h-2z"/></svg>
<span>You need to login to be able to place a bet.</span>
</div>
<div className="flex gap-2">
<button
type="button"
className="flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded text-[11px] font-bold text-white bg-white/10 hover:bg-white/15 border border-border/40"
>
<Save className="size-3.5" />
Save
</button>
<button
type="button"
onClick={clearBets}
className="flex items-center justify-center gap-1.5 py-2 px-3 rounded text-[11px] font-bold text-white bg-red-600/80 hover:bg-red-600 border border-red-500/50"
>
<Trash2 className="size-3.5" />
Clear
</button>
<button
type="button"
className="flex-1 py-2 px-3 rounded text-[11px] font-bold text-white bg-green-600 hover:bg-green-500"
>
Bet
</button>
</div>
</>
)}
</div>

View File

@ -16,19 +16,19 @@ export function CheckYourBet() {
return (
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
<div className="px-1 text-center space-y-3">
<h3 className="text-[12px] font-bold uppercase text-[#ff9800]">Check Your Bet</h3>
<h3 className="text-[12px] font-bold uppercase text-brand-primary">Check Your Bet</h3>
<p className="text-[11px] text-white">Your bet ID</p>
<div className="flex gap-1">
<input
type="text"
value={betId}
onChange={(e) => setBetId(e.target.value)}
className="flex-1 bg-[#121212] border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-primary"
className="flex-1 bg-brand-bg border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-brand-primary"
onKeyDown={(e) => e.key === "Enter" && handleCheck()}
/>
<button
onClick={handleCheck}
className="bg-[#ff9800] text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-[#ffa726] transition-colors"
className="bg-brand-primary text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-brand-primary-hover transition-colors"
>
<svg viewBox="0 0 24 24" className="size-5 fill-current"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</button>

View File

@ -1,6 +1,7 @@
"use client"
import { useState, useEffect } from "react"
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"
@ -16,11 +17,11 @@ function OddsButton({ odds, onClick, isSelected }: {
<button
onClick={onClick}
className={cn(
"flex items-center justify-center py-2 px-0.5 border-r border-border/10 text-[10px] transition-all min-w-0 bg-[#262626] hover:bg-[#333] h-full",
isSelected && "bg-[#ff9800] text-black font-bold border-none"
"flex items-center justify-center py-2 px-0.5 border-r border-border/10 text-[10px] transition-all min-w-0 bg-brand-surface hover:bg-white/5 h-full",
isSelected && "bg-brand-primary text-black font-bold border-none"
)}
>
<span className={cn("font-bold tracking-tighter", isSelected ? "text-black" : "text-[#ff9800]")}>{odds.toFixed(2)}</span>
<span className={cn("font-bold tracking-tighter", isSelected ? "text-black" : "text-brand-primary")}>{odds.toFixed(2)}</span>
</button>
)
}
@ -29,12 +30,12 @@ function EventRow({ event }: { event: Event }) {
const { bets, addBet } = useBetslipStore()
return (
<div className="bg-[#1a1a1a] border-b border-border/20 hover:bg-[#222] transition-colors h-[38px] flex items-center">
<div className="bg-brand-surface border-b border-border/20 hover:bg-white/5 transition-colors h-[38px] flex items-center">
{/* Small Icons & ID Column */}
<div className="flex items-center gap-1.5 px-2 w-[80px] shrink-0 border-r border-border/10 h-full">
<BarChart2 className="size-3 text-muted-foreground hover:text-primary cursor-pointer shrink-0" />
<TrendingUp className="size-3 text-muted-foreground hover:text-primary cursor-pointer shrink-0" />
<span className="text-[9.5px] text-[#ff9800] font-bold tabular-nums italic ml-0.5">{event.id || "01682"}</span>
<span className="text-[9.5px] text-brand-primary font-bold tabular-nums italic ml-0.5">{event.id || "01682"}</span>
</div>
{/* Time & Team Column */}
@ -58,10 +59,10 @@ function EventRow({ event }: { event: Event }) {
isSelected={isSelected}
onClick={() => addBet({
id: betId,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: "1X2",
selection: `${event.homeTeam} (${market.label})`,
selection: market.label,
odds: market.odds,
})}
/>
@ -69,10 +70,14 @@ function EventRow({ event }: { event: Event }) {
})}
</div>
{/* More Markets Button */}
<button className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-border/10 group">
{/* More Markets Button -> match detail page */}
<Link
href={`/event/${event.id}`}
className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-border/10 group"
aria-label="View all markets"
>
<Plus className="size-3 text-white group-hover:scale-110 transition-transform" />
</button>
</Link>
</div>
)
}
@ -112,26 +117,26 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
const renderTableHeaders = () => (
<>
{/* Table Header Categories */}
<div className="bg-[#1a1a1a] border-b border-border/40 grid grid-cols-5 text-[11px] font-bold text-white uppercase text-center items-center h-9">
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Main</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Goals</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Handicap</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20 text-[9px] leading-tight">Half Time / Full Time</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer">Correct Score</div>
<div className="bg-brand-surface border-b border-border/40 grid grid-cols-5 text-[11px] font-bold text-white uppercase text-center items-center h-9">
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">Main</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">Goals</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">Handicap</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20 text-[9px] leading-tight">Half Time / Full Time</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer">Correct Score</div>
</div>
{/* Sub Headers */}
<div className="bg-[#1a1a1a] border-b border-border/40 grid grid-cols-5 text-[10px] font-bold text-white uppercase text-center items-center h-8">
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">1st Half</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">2nd Half</div>
<div className="h-full flex items-center justify-center bg-[#ff9800] text-black border-r border-border/10">Combo</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Chance Mix</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer">Home</div>
<div className="bg-brand-surface border-b border-border/40 grid grid-cols-5 text-[10px] font-bold text-white uppercase text-center items-center h-8">
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">1st Half</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">2nd Half</div>
<div className="h-full flex items-center justify-center bg-brand-primary text-black border-r border-border/10">Combo</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer border-r border-border/20">Chance Mix</div>
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer">Home</div>
</div>
</>
)
const renderColumnHeaders = () => (
<div className="bg-[#222] border-b border-white/5 h-8 flex items-center text-[9px] font-black text-white/40 uppercase">
<div className="bg-brand-surface border-b border-white/5 h-8 flex items-center text-[9px] font-black text-white/40 uppercase">
<div className="w-[180px] px-3 flex items-center gap-1.5 border-r border-border/10 h-full">Main</div>
<div className="w-[180px] flex items-center justify-center border-r border-border/10 h-full">Over/Under</div>
<div className="flex-1 grid grid-cols-10 text-center h-full items-center tracking-tighter">
@ -142,13 +147,13 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
)
const renderEventItem = (event: Event) => (
<div key={event.id} className="h-[34px] group flex items-center border-b border-white/5 bg-[#121212] hover:bg-white/5 transition-colors">
<div key={event.id} className="h-[34px] group flex items-center border-b border-white/5 bg-brand-bg hover:bg-white/5 transition-colors">
{/* Stats & Icons */}
<div className="w-[35px] flex items-center justify-center gap-1 px-2 border-r border-white/5 h-full opacity-40 group-hover:opacity-100">
<BarChart2 className="size-3 cursor-pointer hover:text-primary" />
</div>
{/* ID */}
<div className="w-[45px] text-[10px] font-black text-[#ff9800] italic tabular-nums text-center border-r border-white/5 h-full flex items-center justify-center">
<div className="w-[45px] text-[10px] font-black text-brand-primary italic tabular-nums text-center border-r border-white/5 h-full flex items-center justify-center">
{event.id}
</div>
{/* Time */}
@ -156,10 +161,13 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
<span>{event.time}</span>
<span className="text-[7px] text-white/30 uppercase mt-0.5">PM</span>
</div>
{/* Event Name */}
<div className="flex-1 px-4 text-[10.5px] font-black text-white truncate max-w-[200px]">
{/* Event Name -> same route as + icon (match detail) */}
<Link
href={`/event/${event.id}`}
className="flex-1 px-4 text-[10.5px] font-black text-white truncate max-w-[200px] hover:text-brand-primary transition-colors"
>
{event.homeTeam} - {event.awayTeam}
</div>
</Link>
{/* Odds Grid */}
<div className="flex-1 grid grid-cols-10 h-full">
{event.markets.slice(0, 10).map((market) => {
@ -170,15 +178,15 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
key={market.id}
onClick={() => addBet({
id: betId,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: "1X2",
selection: `${event.homeTeam} (${market.label})`,
selection: market.label,
odds: market.odds,
})}
className={cn(
"flex items-center justify-center text-[10.5px] font-black tabular-nums transition-all border-r border-white/5",
isSelected ? "bg-[#ff9800] text-black" : "text-[#ff9800] hover:bg-white/5"
isSelected ? "bg-brand-primary text-black" : "text-brand-primary hover:bg-white/5"
)}
>
{market.odds.toFixed(2)}
@ -186,10 +194,14 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
)
})}
</div>
{/* More Button */}
<button className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-white/5 text-white/40">
{/* More Button -> match detail page */}
<Link
href={`/event/${event.id}`}
className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-white/5 text-white/40 hover:text-white"
aria-label="View all markets"
>
<Plus className="size-3" />
</button>
</Link>
</div>
)
@ -202,9 +214,9 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
}, {} as Record<string, Event[]>)
return (
<div className="flex flex-col bg-[#121212] rounded overflow-hidden shadow-2xl">
<div className="flex flex-col bg-brand-bg rounded overflow-hidden shadow-2xl">
{/* League Header / Breadcrumbs */}
<div className="bg-[#1a1a1a] px-3 py-2 flex items-center justify-between border-b border-border/20">
<div className="bg-brand-surface px-3 py-2 flex items-center justify-between border-b border-border/20">
<div className="flex items-center gap-2 text-[11px] font-bold text-white/60">
<Plus className="size-3 cursor-pointer hover:text-white" />
<div className="flex items-center gap-1.5">
@ -220,7 +232,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
</div>
{/* Large Market Tab Grid */}
<div className="grid grid-cols-5 bg-[#121212] border-b border-border/10">
<div className="grid grid-cols-5 bg-brand-bg border-b border-border/10">
{[
{ 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" }
@ -229,7 +241,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
key={i}
className={cn(
"h-8 border-r border-b border-border/10 flex items-center justify-center text-[10px] font-black uppercase transition-all",
m.active ? "bg-[#ff9800] text-black" : "text-white/60 hover:bg-[#222]"
m.active ? "bg-brand-primary text-black" : "text-white/60 hover:bg-brand-surface"
)}
>
{m.label}
@ -244,7 +256,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
<div className="overflow-y-auto max-h-[700px]">
{Object.entries(groupedEvents).map(([date, dateEvents]) => (
<div key={date} className="flex flex-col">
<div className="bg-[#1a1a1a] px-2 py-1 text-[10px] font-black text-white border-b border-white/5">
<div className="bg-brand-surface px-2 py-1 text-[10px] font-black text-white border-b border-white/5">
{date}
</div>
{dateEvents.map(event => renderEventItem(event))}
@ -263,12 +275,12 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
}, {} as Record<string, Event[]>)
return (
<div className="flex flex-col bg-[#121212] rounded overflow-hidden">
<div className="flex flex-col bg-brand-bg rounded overflow-hidden">
<div className="flex flex-col">
{Object.entries(homeEventsByLeague).map(([leagueName, leagueEvents]) => (
<div key={leagueName} className="flex flex-col border-b border-white/5 last:border-none">
{/* League Box Header */}
<div className="bg-[#2a2a2a] px-3 py-1.5 text-[10px] font-bold text-[#ff9800] uppercase border-y border-border/20 flex items-center justify-between">
<div className="bg-brand-surface-light px-3 py-1.5 text-[10px] font-bold text-brand-primary uppercase border-y border-border/20 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-[14px]">
{popularLeagues.find(l => l.name === leagueName)?.icon ||
@ -285,7 +297,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
</div>
{/* Column Headers for each league box */}
<div className="bg-[#222] px-3 py-1 flex items-center text-[8px] font-black text-white/40 uppercase border-b border-border/20">
<div className="bg-brand-surface px-3 py-1 flex items-center text-[8px] font-black text-white/40 uppercase border-b border-border/20">
<div className="w-[180px] flex gap-4">
<span className="w-5 text-center">Stats</span>
<span className="w-6">ID</span>

View File

@ -19,7 +19,7 @@ export function HeroBanner() {
}, [])
return (
<div className="w-full h-[240px] md:h-[300px] relative overflow-hidden rounded-none group shadow-lg bg-[#111]">
<div className="w-full h-[240px] md:h-[300px] relative overflow-hidden rounded-none group shadow-lg bg-brand-bg">
{images.map((src, index) => (
<img
key={src}
@ -52,7 +52,7 @@ export function HeroBanner() {
key={index}
onClick={() => setCurrentIndex(index)}
className={`w-1.5 h-1.5 rounded-full transition-all ${
index === currentIndex ? "bg-[#ff9800] scale-125" : "bg-white/40"
index === currentIndex ? "bg-brand-primary scale-125" : "bg-white/40"
}`}
/>
))}

View File

@ -1,3 +1,6 @@
"use client"
import { useState } from "react"
import { InPlayHeader } from "@/components/betting/in-play-header"
import { QuickFilterBar } from "@/components/betting/quick-filter-bar"
import { SearchEvent } from "@/components/betting/search-event"
@ -5,11 +8,14 @@ import { BetServices } from "@/components/betting/bet-services"
import { EventsList } from "@/components/betting/events-list"
export function InPlayPage() {
const [activeFilter, setActiveFilter] = useState("All")
const [searchQuery, setSearchQuery] = useState("")
return (
<div className="flex flex-col gap-3">
<InPlayHeader />
<QuickFilterBar />
<SearchEvent />
<QuickFilterBar active={activeFilter} onChange={setActiveFilter} />
<SearchEvent value={searchQuery} onChange={setSearchQuery} />
<BetServices />
<EventsList />
</div>

View File

@ -15,16 +15,16 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
const period = "H2"
return (
<div className="bg-[#121212] border-b border-white/5 hover:bg-white/5 transition-colors h-[50px] flex items-center group">
<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-[#ff9800]">{time}</span>
<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">
<span className="text-[11.5px] font-black text-white truncate italic uppercase">
{event.homeTeam} <span className="text-[#ff9800] mx-1 tabular-nums">{score}</span> {event.awayTeam}
{event.homeTeam} <span className="text-brand-primary mx-1 tabular-nums">{score}</span> {event.awayTeam}
</span>
</div>
<div className="flex items-center gap-2 shrink-0 px-1 opacity-20 group-hover:opacity-100 transition-opacity">
@ -36,7 +36,7 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
{/* Odds Grid or Placeholder */}
<div className="flex-1 h-full flex items-center">
{isNoOdds ? (
<div className="flex-1 h-full bg-[#161616] flex items-center justify-center text-[10.5px] font-black text-white/20 uppercase italic tracking-tight">
<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>
) : (
@ -48,16 +48,16 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
key={m.id}
onClick={() => addBet({
id: `${event.id}-${m.id}`,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: "1X2",
selection: `${m.label}`,
selection: m.label,
odds: m.odds,
})}
className="bg-[#1a1a1a] hover:bg-[#2a2a2a] flex items-center justify-between px-4 h-full border-r border-white/5 transition-colors group/btn"
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-[#ff9800] tabular-nums">{m.odds.toFixed(2)}</span>
<span className="text-[11px] font-black text-brand-primary tabular-nums">{m.odds.toFixed(2)}</span>
</button>
)
})}
@ -66,7 +66,7 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
</div>
{/* Right Indicator */}
<div className="w-[4px] bg-[#ff9800] h-full" />
<div className="w-[4px] bg-brand-primary h-full" />
</div>
)
}
@ -124,9 +124,9 @@ export function LiveEventsList() {
]
return (
<div className="flex flex-col min-h-screen bg-[#111]">
<div className="flex flex-col min-h-screen bg-brand-bg">
{/* Sport Navigation Carousel */}
<div className="bg-[#1a1a1a] border-b border-border/20 px-2 flex items-center h-[54px] overflow-x-auto scrollbar-hide">
<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">
{/* Favourites & Prematch */}
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
@ -144,19 +144,19 @@ export function LiveEventsList() {
key={sport.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",
sport.active ? "bg-black/20" : "hover:bg-white/5"
sport.active ? "bg-white/5" : "hover:bg-white/5"
)}
>
<span className="absolute top-1 right-2 text-[8.5px] font-black text-white/40">{sport.count}</span>
<span className="text-[16px]">{sport.icon}</span>
<span className={cn(
"text-[9px] font-bold uppercase mt-1 tracking-tighter whitespace-nowrap",
sport.active ? "text-[#ff9800]" : "text-white/40"
sport.active ? "text-brand-primary" : "text-white/40"
)}>
{sport.label}
</span>
{sport.active && (
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-[#ff9800]" />
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-brand-primary" />
)}
</button>
))}
@ -164,7 +164,7 @@ export function LiveEventsList() {
</div>
{/* Category Header (Soccer) */}
<div className="bg-[#009688] px-3 py-1.5 flex items-center gap-2 border-l-[4px] border-[#ff9800]">
<div className="bg-brand-primary px-3 py-1.5 flex items-center gap-2 border-l-[4px] border-brand-primary">
<span className="text-[16px]"></span>
<h2 className="text-[14px] font-black text-white uppercase tracking-tight">Soccer</h2>
</div>
@ -174,7 +174,7 @@ export function LiveEventsList() {
{liveMatches.map((group, gIdx) => (
<div key={gIdx} className="flex flex-col">
{/* League Header */}
<div className="bg-[#1a1a1a] px-3 py-1 border-b border-border/10 flex items-center gap-2">
<div className="bg-brand-surface px-3 py-1 border-b border-border/10 flex items-center gap-2">
<img src={group.flag} width="14" alt={group.league} className="rounded-sm opacity-60" />
<span className="text-[9.5px] font-black text-white/60 uppercase tracking-widest leading-none">
{group.league}

View File

@ -0,0 +1,269 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { useBetslipStore } from "@/lib/store/betslip-store"
import {
getEventDetailMarkets,
getCardsBookingsMarkets,
type Event,
type DetailMarketSection,
} from "@/lib/mock-data"
import { cn } from "@/lib/utils"
import { ChevronDown, ChevronUp } from "lucide-react"
const MARKET_CATEGORIES = [
"Betbuilder",
"All",
"Main",
"Goals",
"Handicap",
"1st Half",
"2nd Half",
"Combo",
"Chance Mix",
"Home",
"Half Time / Full Time",
"Away",
"Correct Score",
"Asian Markets",
"Corners",
"Minutes",
"Cards/Bookings",
"Points Handicap",
"Total Points",
"Team 1",
"Team 2",
"Other",
"Handicap Goals",
"Total Goals",
"Combo",
"Specials",
]
function MarketSectionBlock({
section,
event,
marketName,
isExpanded,
onToggle,
}: {
section: DetailMarketSection
event: Event
marketName: string
isExpanded: boolean
onToggle: () => void
}) {
const { bets, addBet } = useBetslipStore()
const hasOutcomes = section.outcomes.length > 0
return (
<div className="bg-brand-surface-light border-b border-white/5">
<button
type="button"
onClick={onToggle}
className="w-full flex items-center justify-between px-3 py-2.5 text-left hover:bg-white/5 transition-colors"
>
<span className="text-[11px] font-bold text-white uppercase">
{section.title}
</span>
{isExpanded ? (
<ChevronUp className="size-4 text-white/60" />
) : (
<ChevronDown className="size-4 text-white/60" />
)}
</button>
{isExpanded && hasOutcomes && (
<div className="px-3 pb-3 space-y-1.5">
{section.outcomes.length > 2 && section.outcomes.length % 2 === 0 ? (
<div className="grid grid-cols-2 gap-x-4 gap-y-1.5">
{section.outcomes.map((outcome) => {
const betId = `${event.id}-${section.id}-${outcome.label.replace(/\s/g, "-").toLowerCase()}`
const isSelected = bets.some((b) => b.id === betId)
return (
<div key={outcome.label} className="flex items-center justify-between gap-2">
<span className="text-[11px] text-white/90 truncate">{outcome.label}</span>
<button
type="button"
onClick={() =>
addBet({
id: betId,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: marketName,
selection: outcome.label,
odds: outcome.odds,
})
}
className={cn(
"min-w-[52px] px-2 py-1 rounded text-[11px] font-bold tabular-nums text-center transition-all shrink-0",
isSelected ? "bg-brand-primary text-black" : "text-brand-primary hover:bg-white/5"
)}
>
{outcome.odds.toFixed(2)}
</button>
</div>
)
})}
</div>
) : (
section.outcomes.map((outcome) => {
const betId = `${event.id}-${section.id}-${outcome.label.replace(/\s/g, "-").toLowerCase()}`
const isSelected = bets.some((b) => b.id === betId)
return (
<div
key={outcome.label}
className="flex items-center justify-between gap-3 py-1"
>
<span className="text-[11px] text-white/90">{outcome.label}</span>
<button
type="button"
onClick={() =>
addBet({
id: betId,
event: `${event.homeTeam} - ${event.awayTeam}`,
league: `${event.sport} - ${event.country} - ${event.league}`,
market: marketName,
selection: outcome.label,
odds: outcome.odds,
})
}
className={cn(
"min-w-[52px] px-2 py-1 rounded text-[11px] font-bold tabular-nums text-center transition-all shrink-0",
isSelected ? "bg-brand-primary text-black" : "text-brand-primary hover:bg-white/5"
)}
>
{outcome.odds.toFixed(2)}
</button>
</div>
)
})
)}
</div>
)}
</div>
)
}
export function MatchDetailView({ event }: { event: Event }) {
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({
"bookings-1x2": true,
"sending-off": true,
"1st-booking": true,
"1st-half-bookings-1x2": true,
"booking-points-ou": true,
"1st-half-1st-booking": true,
})
const [activeCategory, setActiveCategory] = useState("Cards/Bookings")
const detailMarkets = getEventDetailMarkets(event.id)
const cardsBookings = getCardsBookingsMarkets(event.id)
const toggleSection = (id: string) => {
setExpandedSections((prev) => ({ ...prev, [id]: !prev[id] }))
}
const breadcrumbLeague =
event.league === "Premier League"
? "England - Premier League"
: `${event.country} - ${event.league}`
const isCardsBookings = activeCategory === "Cards/Bookings"
const leftSections = isCardsBookings ? cardsBookings.left : detailMarkets
const rightSections = isCardsBookings ? cardsBookings.right : []
return (
<div className="flex flex-col bg-brand-bg rounded overflow-hidden">
{/* Breadcrumb: back arrow, ellipsis, path */}
<div className="bg-brand-surface px-3 py-2 border-b border-border/20">
<Link
href="/"
className="flex items-center gap-2 text-[11px] font-bold text-white/70 hover:text-brand-primary transition-colors"
>
<span className="text-white/80">&lt;</span>
<span>...</span>
<span>Football {breadcrumbLeague} / {event.homeTeam} vs. {event.awayTeam}</span>
</Link>
<h1 className="text-[15px] font-bold text-white mt-2">
{breadcrumbLeague}
</h1>
</div>
{/* Match header */}
<div className="bg-brand-surface px-4 py-5 border-b border-border/20">
<div className="flex items-center justify-center gap-10">
<div className="flex flex-col items-center gap-2">
<div className="w-16 h-20 rounded-md bg-brand-bg border border-white/10 flex items-center justify-center">
<span className="text-[10px] font-black text-white/60 uppercase">
{event.homeTeam.slice(0, 2)}
</span>
</div>
<span className="text-[13px] font-bold text-white">{event.homeTeam}</span>
</div>
<span className="text-[12px] font-black text-white/50 uppercase">VS</span>
<div className="flex flex-col items-center gap-2">
<div className="w-16 h-20 rounded-md bg-brand-bg border border-white/10 flex items-center justify-center">
<span className="text-[10px] font-black text-white/60 uppercase">
{event.awayTeam.slice(0, 2)}
</span>
</div>
<span className="text-[13px] font-bold text-white">{event.awayTeam}</span>
</div>
</div>
</div>
{/* Category tabs: horizontal scroll, selected = darker grey */}
<div className="flex overflow-x-auto gap-1 p-2 bg-brand-bg border-b border-border/20 scrollbar-hide">
{MARKET_CATEGORIES.map((label) => (
<button
key={label}
type="button"
onClick={() => setActiveCategory(label)}
className={cn(
"px-3 py-1.5 text-[10px] font-bold uppercase whitespace-nowrap rounded transition-colors shrink-0",
activeCategory === label
? "bg-brand-surface-light text-white border border-white/10"
: "text-white/60 hover:text-white hover:bg-white/5"
)}
>
{label}
</button>
))}
</div>
{/* Two-column grid of market sections */}
<div className="flex-1 min-h-0 overflow-y-auto">
<div className="grid grid-cols-1 md:grid-cols-2 gap-0 bg-brand-surface-light">
{/* Left column */}
<div className="border-r border-white/5">
{leftSections.map((section) => (
<MarketSectionBlock
key={section.id}
section={section}
event={event}
marketName={section.title}
isExpanded={expandedSections[section.id] ?? false}
onToggle={() => toggleSection(section.id)}
/>
))}
</div>
{/* Right column (Cards/Bookings only) */}
{rightSections.length > 0 && (
<div>
{rightSections.map((section) => (
<MarketSectionBlock
key={section.id}
section={section}
event={event}
marketName={section.title}
isExpanded={expandedSections[section.id] ?? false}
onToggle={() => toggleSection(section.id)}
/>
))}
</div>
)}
</div>
</div>
</div>
)
}

View File

@ -8,16 +8,16 @@ export function ReloadTicket() {
return (
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
<div className="px-1 text-center space-y-3">
<h3 className="text-[12px] font-bold uppercase text-[#ff9800]">Reload Ticket</h3>
<h3 className="text-[12px] font-bold uppercase text-brand-primary">Reload Ticket</h3>
<p className="text-[11px] text-white">Insert the code to load</p>
<div className="flex gap-1">
<input
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
className="flex-1 bg-[#121212] border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-primary"
className="flex-1 bg-brand-bg border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-brand-primary"
/>
<button className="bg-[#ff9800] text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-[#ffa726] transition-colors">
<button className="bg-brand-primary text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-brand-primary-hover transition-colors">
<svg viewBox="0 0 24 24" className="size-5 fill-current"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</button>
</div>

View File

@ -16,7 +16,7 @@ const sports = [
export function SportsNav() {
return (
<Tabs defaultValue="football" className="w-full">
<TabsList variant="hs-nav" className="h-auto">
<TabsList variant="hs-nav" className="min-h-14! h-auto! py-2">
{sports.map((sport) => (
<TabsTrigger
key={sport.id}

View File

@ -1,5 +1,8 @@
"use client"
import { ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useBetslipStore } from "@/lib/store/betslip-store"
import { cn } from "@/lib/utils"
const topMatches = [
{
@ -45,58 +48,90 @@ const topMatches = [
]
export function TopMatches() {
const { bets, addBet } = useBetslipStore()
return (
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide -mx-1 px-1">
{topMatches.map((match) => (
<div
key={match.id}
className="min-w-[280px] bg-[#222] border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
>
{/* Top Label Ribbon */}
<div className="absolute top-0 right-0 w-12 h-12 overflow-hidden z-10 pointer-events-none">
<div className="absolute top-[6px] right-[-14px] bg-[#ff9800] text-black text-[8px] font-black py-0.5 px-6 rotate-45 shadow-sm uppercase tracking-tighter">
TOP
{topMatches.map((match) => {
const eventName = `${match.homeTeam} - ${match.awayTeam}`
const leagueForBet = `Football - ${match.league}`
const outcomes = [
{ key: "1", label: "1", odds: match.odds.home },
{ key: "x", label: "X", odds: match.odds.draw },
{ key: "2", label: "2", odds: match.odds.away },
] as const
return (
<div
key={match.id}
className="min-w-[280px] bg-brand-surface border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
>
{/* Top Label Ribbon */}
<div className="absolute top-0 right-0 w-12 h-12 overflow-hidden z-10 pointer-events-none">
<div className="absolute top-[6px] right-[-14px] bg-brand-primary text-black text-[8px] font-black py-0.5 px-6 rotate-45 shadow-sm uppercase tracking-tighter">
TOP
</div>
</div>
<div className="bg-brand-bg px-3 py-1.5 flex items-center justify-between text-[10px] text-muted-foreground border-b border-border/10">
<div className="flex items-center gap-1">
<span className="font-bold text-brand-primary">{match.league}</span>
<span className="font-black text-white ml-1 italic">{match.time}</span>
</div>
<ChevronRight className="size-3 text-white/20 mr-4" />
</div>
<div className="p-3 flex flex-col gap-3">
<div className="flex items-center justify-between gap-1">
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
<span className="text-[11px] font-black text-white truncate uppercase italic">{match.homeTeam}</span>
</div>
<span className="text-brand-primary text-[9.5px] font-black italic shrink-0 px-2 opacity-60">VS</span>
<div className="flex items-center gap-2 flex-1 min-w-0 justify-end">
<span className="text-[11px] font-black text-white truncate uppercase italic text-right">{match.awayTeam}</span>
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-[1px] bg-white/5 mx-2 mb-2 rounded-sm overflow-hidden border border-white/5">
{outcomes.map(({ key, label, odds }) => {
const betId = `${match.id}-${key}`
const isSelected = bets.some((b) => b.id === betId)
return (
<button
key={key}
type="button"
onClick={() =>
addBet({
id: betId,
event: eventName,
league: leagueForBet,
market: "1X2",
selection: label,
odds,
})
}
className={cn(
"flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn border-x border-white/5 first:border-x-0",
isSelected
? "bg-brand-primary text-black"
: "bg-brand-bg hover:bg-brand-surface-light"
)}
>
<span className={cn("text-[10px] font-black", isSelected ? "text-black" : "text-white/40 group-hover/btn:text-white")}>
{label}
</span>
<span className={cn("text-[11px] font-black tabular-nums", isSelected ? "text-black" : "text-brand-primary")}>
{odds.toFixed(2)}
</span>
</button>
)
})}
</div>
</div>
<div className="bg-[#1a1a1a] px-3 py-1.5 flex items-center justify-between text-[10px] text-muted-foreground border-b border-border/10">
<div className="flex items-center gap-1">
<span className="font-bold text-[#ff9800]">{match.league}</span>
<span className="font-black text-white ml-1 italic">{match.time}</span>
</div>
<ChevronRight className="size-3 text-white/20 mr-4" />
</div>
<div className="p-3 flex flex-col gap-3">
<div className="flex items-center justify-between gap-1">
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
<span className="text-[11px] font-black text-white truncate uppercase italic">{match.homeTeam}</span>
</div>
<span className="text-[#ff9800] text-[9.5px] font-black italic shrink-0 px-2 opacity-60">VS</span>
<div className="flex items-center gap-2 flex-1 min-w-0 justify-end">
<span className="text-[11px] font-black text-white truncate uppercase italic text-right">{match.awayTeam}</span>
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-[1px] bg-white/5 mx-2 mb-2 rounded-sm overflow-hidden border border-white/5">
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">1</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.home.toFixed(2)}</span>
</button>
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors border-x border-white/5 group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">X</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.draw.toFixed(2)}</span>
</button>
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">2</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.away.toFixed(2)}</span>
</button>
</div>
</div>
))}
)
})}
</div>
)
}

View File

@ -0,0 +1,40 @@
"use client"
import Image from "next/image"
import { cn } from "@/lib/utils"
interface GameCardProps {
id: string
title: string
image: string
provider?: string
}
export function GameCard({ id, title, image, provider }: GameCardProps) {
return (
<div className="group cursor-pointer shrink-0 w-[160px] md:w-[180px]">
<div className="relative aspect-[4/3] rounded-md overflow-hidden bg-brand-surface border border-white/5 transition-transform duration-300 group-hover:scale-[1.02] group-hover:border-brand-primary/50">
<Image
src={image}
alt={title}
fill
className="object-cover"
unoptimized
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex flex-col justify-end p-2">
<button className="bg-brand-primary text-black text-[10px] font-black py-1 px-3 rounded uppercase self-center hover:bg-brand-primary-hover transition-colors">
Play Now
</button>
</div>
</div>
<div className="mt-2 text-center">
<h3 className="text-[11px] font-bold text-white/90 truncate group-hover:text-brand-primary transition-colors">
{title}
</h3>
{provider && (
<p className="text-[9px] text-white/40 uppercase font-medium mt-0.5">{provider}</p>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,95 @@
"use client"
import { useRef } from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { GameCard } from "./game-card"
import { cn } from "@/lib/utils"
interface GameRowProps {
title: string
games: any[]
showSeeMore?: boolean
rows?: 1 | 2 | 3
}
export function GameRow({ title, games, showSeeMore = true, rows = 1 }: GameRowProps) {
const scrollRef = useRef<HTMLDivElement>(null)
const scroll = (direction: "left" | "right") => {
if (scrollRef.current) {
const scrollAmount = direction === "left" ? -600 : 600
scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" })
}
}
// Chunk games into rows if rows > 1
const renderGames = () => {
if (rows === 1) {
return games.map((game, idx) => (
<div key={idx} style={{ scrollSnapAlign: "start" }}>
<GameCard {...game} />
</div>
))
}
// For multi-row, we can use a grid with horizontal scroll on the container
// or wrap items. The requirement "3*6 horizontally scrollable" suggests
// a grid that moves as a block or multi-row horizontal layout.
return (
<div className={cn(
"grid grid-flow-col gap-4 pb-2",
rows === 2 ? "grid-rows-2" : rows === 3 ? "grid-rows-3" : ""
)}>
{games.map((game, idx) => (
<div key={idx} style={{ scrollSnapAlign: "start" }}>
<GameCard {...game} />
</div>
))}
</div>
)
}
return (
<div className="flex flex-col gap-3 py-4">
<div className="flex items-center justify-between px-4">
<h2 className="text-sm font-bold text-white/90 border-l-4 border-brand-primary pl-3 uppercase tracking-tight">
{title}
</h2>
<div className="flex items-center gap-3">
{showSeeMore && (
<button className="text-[10px] text-white/40 hover:text-white uppercase font-bold transition-colors">
See More
</button>
)}
<div className="flex items-center gap-1">
<button
onClick={() => scroll("left")}
className="size-6 flex items-center justify-center bg-white/5 hover:bg-white/10 text-white/60 hover:text-white transition-colors"
>
<ChevronLeft className="size-4" />
</button>
<button
onClick={() => scroll("right")}
className="size-6 flex items-center justify-center bg-white/5 hover:bg-white/10 text-white/60 hover:text-white transition-colors"
>
<ChevronRight className="size-4" />
</button>
</div>
</div>
</div>
<div
ref={scrollRef}
className="flex overflow-x-auto scrollbar-none px-4 pb-2 snap-x snap-mandatory"
>
{rows === 1 ? (
<div className="flex gap-4">
{renderGames()}
</div>
) : (
renderGames()
)}
</div>
</div>
)
}

View File

@ -0,0 +1,86 @@
"use client"
import { cn } from "@/lib/utils"
import { Search, Heart, Clock, Star } from "lucide-react"
export type GameCategory = string
interface GamingSidebarProps {
title: string
subtitle?: string
activeCategory: GameCategory
onCategoryChange: (category: GameCategory) => void
categories: {
id: string
name: string
icon: any
subtitle?: string
hasChevron?: boolean
}[]
}
export function GamingSidebar({ title, subtitle, activeCategory, onCategoryChange, categories }: GamingSidebarProps) {
return (
<aside className="hidden h-full w-[280px] shrink-0 bg-brand-surface-light lg:block overflow-y-auto border-r border-border/40 scrollbar-hide">
{/* Sidebar Header */}
<div className="bg-brand-surface px-3 py-2 text-[12px] font-bold text-white uppercase flex items-center justify-between border-b border-white/10 h-12">
<div className="flex items-center gap-2">
<Star className="size-4 text-brand-primary" />
<div className="flex flex-col">
<span className="leading-tight">{title}</span>
{subtitle && <span className="text-[10px] text-white/40 font-normal lowercase">{subtitle}</span>}
</div>
</div>
<svg viewBox="0 0 24 24" className="size-3.5 fill-white/40"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
</div>
<div className="flex flex-col gap-[1px]">
{categories.map((cat) => {
const Icon = cat.icon
const isActive = activeCategory === cat.id
return (
<button
key={cat.id}
onClick={() => onCategoryChange(cat.id)}
className={cn(
"w-full flex flex-col px-3 py-2 text-left transition-colors border-b border-border/5",
isActive
? "bg-brand-surface border-l-4 border-l-brand-primary"
: "bg-brand-surface-light hover:bg-brand-surface"
)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
{typeof Icon === 'string' ? (
(Icon.startsWith('http') || Icon.startsWith('/')) ? (
<img src={Icon} alt={cat.name} className="w-5 h-5 object-contain opacity-80" />
) : (
<span className="size-4 flex items-center justify-center text-[14px]">{Icon}</span>
)
) : (
<Icon className={cn("size-4", isActive ? "text-brand-primary" : "text-white/60")} />
)}
<div className="flex flex-col">
<span className={cn(
"text-[12px] font-bold tracking-tight",
isActive ? "text-white" : "text-white/80"
)}>
{cat.name}
</span>
{cat.subtitle && (
<span className="text-[9px] text-white/40 font-medium">{cat.subtitle}</span>
)}
</div>
</div>
{cat.hasChevron && (
<svg viewBox="0 0 24 24" className="size-3.5 fill-white/40"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
)}
</div>
</button>
)
})}
</div>
</aside>
)
}

View File

@ -0,0 +1,88 @@
"use client"
import { cn } from "@/lib/utils"
import { Search, Heart, Clock, Star, Zap, LayoutGrid, Gamepad2, Award, Coins, Flame, Trophy } from "lucide-react"
export type GameCategory =
| "all"
| "search"
| "favourite"
| "recently-played"
| "most-popular"
| "harif-special"
| "for-you"
| "slots"
| "crash-games"
| "higher-lower"
| "smartsoft"
| "keno-spin"
| "pragmatic-play"
| "evoplay-bonus"
interface VirtualSidebarProps {
activeCategory: GameCategory
onCategoryChange: (category: GameCategory) => void
}
const categories = [
{ id: "all", name: "Virtual", icon: Star, subtitle: "Check out our games!", hasChevron: true },
{ id: "search", name: "Search", icon: Search },
{ id: "favourite", name: "Favourite", icon: Heart },
{ id: "recently-played", name: "Recently Played", icon: Clock },
{ id: "most-popular", name: "Most Popular", icon: Star },
{ id: "harif-special", name: "Harif Special", icon: Zap },
{ id: "for-you", name: "For You", icon: Star },
{ id: "slots", name: "Slots", icon: Star },
{ id: "crash-games", name: "Crash Games", icon: Star },
{ id: "higher-lower", name: "Higher Lower", icon: Star },
{ id: "smartsoft", name: "Smartsoft", icon: Star },
{ id: "keno-spin", name: "Keno & Spin", icon: Star },
{ id: "pragmatic-play", name: "Pragmatic Play", icon: Star },
{ id: "evoplay-bonus", name: "EvoPlay BONUS", icon: Star },
]
export function VirtualSidebar({ activeCategory, onCategoryChange }: VirtualSidebarProps) {
return (
<aside className="hidden h-full w-[240px] shrink-0 bg-brand-surface-light lg:block overflow-y-auto border-r border-border/40 scrollbar-hide">
<div className="flex flex-col gap-[1px]">
{categories.map((cat) => {
const Icon = cat.icon
const isActive = activeCategory === cat.id
return (
<button
key={cat.id}
onClick={() => onCategoryChange(cat.id as GameCategory)}
className={cn(
"w-full flex flex-col px-3 py-2 text-left transition-colors border-b border-border/5",
isActive
? "bg-brand-surface border-l-4 border-l-brand-primary"
: "bg-brand-surface-light hover:bg-brand-surface"
)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
<Icon className={cn("size-4", isActive ? "text-brand-primary" : "text-white/60")} />
<div className="flex flex-col">
<span className={cn(
"text-[12px] font-bold tracking-tight",
isActive ? "text-white" : "text-white/80"
)}>
{cat.name}
</span>
{cat.subtitle && (
<span className="text-[9px] text-white/40 font-medium">{cat.subtitle}</span>
)}
</div>
</div>
{cat.hasChevron && (
<svg viewBox="0 0 24 24" className="size-3.5 fill-white/40"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
)}
</div>
</button>
)
})}
</div>
</aside>
)
}