betting and ui components
This commit is contained in:
parent
5587dff57c
commit
9029fb4f6a
|
|
@ -2,183 +2,203 @@
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useBetslipStore } from "@/lib/store/betslip-store"
|
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"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const quickStakes = [5, 10, 20, 50, 100, 200]
|
const quickStakes = [10, 50, 100, 1000, 2000, 5000]
|
||||||
|
|
||||||
export function Betslip() {
|
export function Betslip() {
|
||||||
const { bets, removeBet, clearBets, updateStake, getPotentialWin, getTotalOdds } = useBetslipStore()
|
const { bets, removeBet, clearBets, updateStake, getPotentialWin, getTotalOdds } = useBetslipStore()
|
||||||
const [activeTab, setActiveTab] = useState<"single" | "accumulator">("single")
|
|
||||||
const [globalStake, setGlobalStake] = useState("10")
|
const [globalStake, setGlobalStake] = useState("10")
|
||||||
|
|
||||||
const potentialWin = getPotentialWin()
|
|
||||||
const totalOdds = getTotalOdds()
|
const totalOdds = getTotalOdds()
|
||||||
|
const isMulti = bets.length > 1
|
||||||
|
const potentialWin = isMulti
|
||||||
|
? Number(globalStake) * totalOdds
|
||||||
|
: getPotentialWin()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card rounded border border-border overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
{/* Header */}
|
<div className="space-y-3">
|
||||||
<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">
|
|
||||||
{bets.length === 0 ? (
|
{bets.length === 0 ? (
|
||||||
<div className="py-6 text-center">
|
<div className="py-10 px-4 text-center">
|
||||||
<div className="text-2xl mb-2">🎯</div>
|
<p className="text-[11px] text-muted-foreground font-medium leading-relaxed">
|
||||||
<p className="text-[11px] text-muted-foreground leading-relaxed">
|
No bet has been selected. To select a bet, please click on the respective odds
|
||||||
No bets selected. Click on odds to add selections.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Bet items */}
|
{/* Bet cards */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{bets.map((bet) => (
|
{bets.map((bet) => (
|
||||||
<div key={bet.id} className="bg-secondary/50 rounded border border-border/50 p-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 mb-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[11px] font-semibold text-foreground truncate">{bet.event}</div>
|
<div className="text-[11px] font-bold text-white uppercase leading-tight truncate">
|
||||||
<div className="text-[10px] text-muted-foreground truncate">{bet.league}</div>
|
{bet.event}
|
||||||
<div className="text-[10px] text-primary font-medium mt-0.5">
|
|
||||||
{bet.market}: <span className="text-foreground">{bet.selection}</span>
|
|
||||||
</div>
|
</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>
|
||||||
<div className="flex items-center gap-1.5 shrink-0">
|
|
||||||
<span className="text-sm font-bold text-primary">{bet.odds.toFixed(2)}</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => removeBet(bet.id)}
|
onClick={() => removeBet(bet.id)}
|
||||||
className="text-muted-foreground hover:text-destructive transition-colors"
|
className="text-white/50 hover:text-white transition-colors shrink-0 p-0.5"
|
||||||
|
aria-label="Remove bet"
|
||||||
>
|
>
|
||||||
<X className="size-3.5" />
|
<X className="size-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stake input for single */}
|
{/* Single bet: stake on card */}
|
||||||
{(activeTab === "single" || bets.length === 1) && (
|
{!isMulti && (
|
||||||
<div>
|
<div className="mt-3 pt-3 border-t border-border/20">
|
||||||
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
|
<div className="flex items-center gap-1 mb-2">
|
||||||
<div className="flex gap-1">
|
<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
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={bet.stake ?? 10}
|
value={bet.stake ?? 10}
|
||||||
onChange={(e) => updateStake(bet.id, Number(e.target.value))}
|
onChange={(e) => updateStake(bet.id, Number(e.target.value) || 10)}
|
||||||
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"
|
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"
|
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>
|
||||||
<div className="flex gap-1 mt-1 flex-wrap">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{quickStakes.map((s) => (
|
{quickStakes.map((s) => (
|
||||||
<button
|
<button
|
||||||
key={s}
|
key={s}
|
||||||
onClick={() => updateStake(bet.id, s)}
|
onClick={() => updateStake(bet.id, s)}
|
||||||
className={cn(
|
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
|
(bet.stake ?? 10) === s
|
||||||
? "bg-primary text-primary-foreground border-primary"
|
? "bg-brand-primary text-black"
|
||||||
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
|
: "bg-brand-primary/20 text-brand-primary hover:bg-brand-primary/30"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{s}
|
{s}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Accumulator stake section */}
|
{/* Single bet: potential winning below card */}
|
||||||
{activeTab === "accumulator" && bets.length > 1 && (
|
{!isMulti && (
|
||||||
<div className="bg-secondary/50 rounded border border-border/50 p-2 space-y-2">
|
<div className="flex justify-between items-center text-[11px] font-bold text-white">
|
||||||
<div className="flex justify-between text-[11px]">
|
<span className="text-white/70">Potential winning</span>
|
||||||
<span className="text-muted-foreground">Total Odds:</span>
|
<span>{potentialWin.toFixed(2)} ETB</span>
|
||||||
<span className="font-bold text-primary">{totalOdds.toFixed(2)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</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 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
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={globalStake}
|
value={globalStake}
|
||||||
onChange={(e) => setGlobalStake(e.target.value)}
|
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"
|
min="1"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-1 mt-1 flex-wrap">
|
<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) => (
|
{quickStakes.map((s) => (
|
||||||
<button
|
<button
|
||||||
key={s}
|
key={s}
|
||||||
onClick={() => setGlobalStake(String(s))}
|
onClick={() => setGlobalStake(String(s))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
|
"size-7 rounded-full text-[10px] font-bold transition-colors",
|
||||||
globalStake === String(s)
|
Number(globalStake) === s
|
||||||
? "bg-primary text-primary-foreground border-primary"
|
? "bg-brand-primary text-black"
|
||||||
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
|
: "bg-brand-primary/20 text-brand-primary hover:bg-brand-primary/30"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{s}
|
{s}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1.5 flex justify-between text-[10px]">
|
<div className="flex justify-between text-[11px] font-bold text-white pt-0.5">
|
||||||
<span className="text-muted-foreground">Potential win:</span>
|
<span className="text-white/70">Potential winning</span>
|
||||||
<span className="text-primary font-semibold">
|
<span className="text-brand-primary">{(Number(globalStake) * totalOdds).toFixed(2)} ETB</span>
|
||||||
{(Number(globalStake) * totalOdds).toFixed(2)} ETB
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Place bet button */}
|
<div className="flex items-center gap-2 py-2 px-1 text-[10px] text-brand-primary/90">
|
||||||
<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">
|
<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>
|
||||||
Place Bet
|
<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>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ export function CheckYourBet() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
|
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
|
||||||
<div className="px-1 text-center space-y-3">
|
<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>
|
<p className="text-[11px] text-white">Your bet ID</p>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={betId}
|
value={betId}
|
||||||
onChange={(e) => setBetId(e.target.value)}
|
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()}
|
onKeyDown={(e) => e.key === "Enter" && handleCheck()}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCheck}
|
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>
|
<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>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
|
import Link from "next/link"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
import { useBetslipStore } from "@/lib/store/betslip-store"
|
import { useBetslipStore } from "@/lib/store/betslip-store"
|
||||||
import { mockEvents, popularLeagues, type Event } from "@/lib/mock-data"
|
import { mockEvents, popularLeagues, type Event } from "@/lib/mock-data"
|
||||||
|
|
@ -16,11 +17,11 @@ function OddsButton({ odds, onClick, isSelected }: {
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn(
|
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",
|
"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-[#ff9800] text-black font-bold border-none"
|
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>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -29,12 +30,12 @@ function EventRow({ event }: { event: Event }) {
|
||||||
const { bets, addBet } = useBetslipStore()
|
const { bets, addBet } = useBetslipStore()
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* 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">
|
<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" />
|
<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" />
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Time & Team Column */}
|
{/* Time & Team Column */}
|
||||||
|
|
@ -58,10 +59,10 @@ function EventRow({ event }: { event: Event }) {
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onClick={() => addBet({
|
onClick={() => addBet({
|
||||||
id: betId,
|
id: betId,
|
||||||
event: `${event.homeTeam} vs ${event.awayTeam}`,
|
event: `${event.homeTeam} - ${event.awayTeam}`,
|
||||||
league: event.league,
|
league: `${event.sport} - ${event.country} - ${event.league}`,
|
||||||
market: "1X2",
|
market: "1X2",
|
||||||
selection: `${event.homeTeam} (${market.label})`,
|
selection: market.label,
|
||||||
odds: market.odds,
|
odds: market.odds,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
@ -69,10 +70,14 @@ function EventRow({ event }: { event: Event }) {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* More Markets Button */}
|
{/* More Markets Button -> match detail page */}
|
||||||
<button className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-border/10 group">
|
<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" />
|
<Plus className="size-3 text-white group-hover:scale-110 transition-transform" />
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -112,26 +117,26 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
const renderTableHeaders = () => (
|
const renderTableHeaders = () => (
|
||||||
<>
|
<>
|
||||||
{/* Table Header Categories */}
|
{/* 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="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-[#222] 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">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-white/5 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-white/5 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-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-[#222] transition-colors cursor-pointer">Correct Score</div>
|
<div className="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer">Correct Score</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Sub Headers */}
|
{/* 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="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-[#222] 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">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 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-[#ff9800] text-black border-r border-border/10">Combo</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-[#222] 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 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="h-full flex items-center justify-center hover:bg-white/5 transition-colors cursor-pointer">Home</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const renderColumnHeaders = () => (
|
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] 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="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">
|
<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) => (
|
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 */}
|
{/* 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">
|
<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" />
|
<BarChart2 className="size-3 cursor-pointer hover:text-primary" />
|
||||||
</div>
|
</div>
|
||||||
{/* ID */}
|
{/* 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}
|
{event.id}
|
||||||
</div>
|
</div>
|
||||||
{/* Time */}
|
{/* Time */}
|
||||||
|
|
@ -156,10 +161,13 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
<span>{event.time}</span>
|
<span>{event.time}</span>
|
||||||
<span className="text-[7px] text-white/30 uppercase mt-0.5">PM</span>
|
<span className="text-[7px] text-white/30 uppercase mt-0.5">PM</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Event Name */}
|
{/* Event Name -> same route as + icon (match detail) */}
|
||||||
<div className="flex-1 px-4 text-[10.5px] font-black text-white truncate max-w-[200px]">
|
<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}
|
{event.homeTeam} - {event.awayTeam}
|
||||||
</div>
|
</Link>
|
||||||
{/* Odds Grid */}
|
{/* Odds Grid */}
|
||||||
<div className="flex-1 grid grid-cols-10 h-full">
|
<div className="flex-1 grid grid-cols-10 h-full">
|
||||||
{event.markets.slice(0, 10).map((market) => {
|
{event.markets.slice(0, 10).map((market) => {
|
||||||
|
|
@ -170,15 +178,15 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
key={market.id}
|
key={market.id}
|
||||||
onClick={() => addBet({
|
onClick={() => addBet({
|
||||||
id: betId,
|
id: betId,
|
||||||
event: `${event.homeTeam} vs ${event.awayTeam}`,
|
event: `${event.homeTeam} - ${event.awayTeam}`,
|
||||||
league: event.league,
|
league: `${event.sport} - ${event.country} - ${event.league}`,
|
||||||
market: "1X2",
|
market: "1X2",
|
||||||
selection: `${event.homeTeam} (${market.label})`,
|
selection: market.label,
|
||||||
odds: market.odds,
|
odds: market.odds,
|
||||||
})}
|
})}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center text-[10.5px] font-black tabular-nums transition-all border-r border-white/5",
|
"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)}
|
{market.odds.toFixed(2)}
|
||||||
|
|
@ -186,10 +194,14 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{/* More Button */}
|
{/* More Button -> match detail page */}
|
||||||
<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">
|
<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" />
|
<Plus className="size-3" />
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -202,9 +214,9 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
}, {} as Record<string, Event[]>)
|
}, {} as Record<string, Event[]>)
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* 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">
|
<div className="flex items-center gap-2 text-[11px] font-bold text-white/60">
|
||||||
<Plus className="size-3 cursor-pointer hover:text-white" />
|
<Plus className="size-3 cursor-pointer hover:text-white" />
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
|
|
@ -220,7 +232,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Large Market Tab Grid */}
|
{/* 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: "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" }
|
{ 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}
|
key={i}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-8 border-r border-b border-border/10 flex items-center justify-center text-[10px] font-black uppercase transition-all",
|
"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}
|
{m.label}
|
||||||
|
|
@ -244,7 +256,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
<div className="overflow-y-auto max-h-[700px]">
|
<div className="overflow-y-auto max-h-[700px]">
|
||||||
{Object.entries(groupedEvents).map(([date, dateEvents]) => (
|
{Object.entries(groupedEvents).map(([date, dateEvents]) => (
|
||||||
<div key={date} className="flex flex-col">
|
<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}
|
{date}
|
||||||
</div>
|
</div>
|
||||||
{dateEvents.map(event => renderEventItem(event))}
|
{dateEvents.map(event => renderEventItem(event))}
|
||||||
|
|
@ -263,12 +275,12 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
}, {} as Record<string, Event[]>)
|
}, {} as Record<string, Event[]>)
|
||||||
|
|
||||||
return (
|
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">
|
<div className="flex flex-col">
|
||||||
{Object.entries(homeEventsByLeague).map(([leagueName, leagueEvents]) => (
|
{Object.entries(homeEventsByLeague).map(([leagueName, leagueEvents]) => (
|
||||||
<div key={leagueName} className="flex flex-col border-b border-white/5 last:border-none">
|
<div key={leagueName} className="flex flex-col border-b border-white/5 last:border-none">
|
||||||
{/* League Box Header */}
|
{/* 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">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[14px]">
|
<span className="text-[14px]">
|
||||||
{popularLeagues.find(l => l.name === leagueName)?.icon ||
|
{popularLeagues.find(l => l.name === leagueName)?.icon ||
|
||||||
|
|
@ -285,7 +297,7 @@ export function EventsList({ filter = "All", sport = "all", search = "" }: {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Column Headers for each league box */}
|
{/* 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">
|
<div className="w-[180px] flex gap-4">
|
||||||
<span className="w-5 text-center">Stats</span>
|
<span className="w-5 text-center">Stats</span>
|
||||||
<span className="w-6">ID</span>
|
<span className="w-6">ID</span>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export function HeroBanner() {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
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) => (
|
{images.map((src, index) => (
|
||||||
<img
|
<img
|
||||||
key={src}
|
key={src}
|
||||||
|
|
@ -52,7 +52,7 @@ export function HeroBanner() {
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setCurrentIndex(index)}
|
onClick={() => setCurrentIndex(index)}
|
||||||
className={`w-1.5 h-1.5 rounded-full transition-all ${
|
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"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
import { InPlayHeader } from "@/components/betting/in-play-header"
|
import { InPlayHeader } from "@/components/betting/in-play-header"
|
||||||
import { QuickFilterBar } from "@/components/betting/quick-filter-bar"
|
import { QuickFilterBar } from "@/components/betting/quick-filter-bar"
|
||||||
import { SearchEvent } from "@/components/betting/search-event"
|
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"
|
import { EventsList } from "@/components/betting/events-list"
|
||||||
|
|
||||||
export function InPlayPage() {
|
export function InPlayPage() {
|
||||||
|
const [activeFilter, setActiveFilter] = useState("All")
|
||||||
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<InPlayHeader />
|
<InPlayHeader />
|
||||||
<QuickFilterBar />
|
<QuickFilterBar active={activeFilter} onChange={setActiveFilter} />
|
||||||
<SearchEvent />
|
<SearchEvent value={searchQuery} onChange={setSearchQuery} />
|
||||||
<BetServices />
|
<BetServices />
|
||||||
<EventsList />
|
<EventsList />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
|
||||||
const period = "H2"
|
const period = "H2"
|
||||||
|
|
||||||
return (
|
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) */}
|
{/* 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 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">
|
<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>
|
<span className="text-white/40">{period}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center min-w-0 flex-1 gap-2">
|
<div className="flex items-center min-w-0 flex-1 gap-2">
|
||||||
<span className="text-[11.5px] font-black text-white truncate italic uppercase">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 shrink-0 px-1 opacity-20 group-hover:opacity-100 transition-opacity">
|
<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 */}
|
{/* Odds Grid or Placeholder */}
|
||||||
<div className="flex-1 h-full flex items-center">
|
<div className="flex-1 h-full flex items-center">
|
||||||
{isNoOdds ? (
|
{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
|
Sorry, no odds for this match
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -48,16 +48,16 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
|
||||||
key={m.id}
|
key={m.id}
|
||||||
onClick={() => addBet({
|
onClick={() => addBet({
|
||||||
id: `${event.id}-${m.id}`,
|
id: `${event.id}-${m.id}`,
|
||||||
event: `${event.homeTeam} vs ${event.awayTeam}`,
|
event: `${event.homeTeam} - ${event.awayTeam}`,
|
||||||
league: event.league,
|
league: `${event.sport} - ${event.country} - ${event.league}`,
|
||||||
market: "1X2",
|
market: "1X2",
|
||||||
selection: `${m.label}`,
|
selection: m.label,
|
||||||
odds: m.odds,
|
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-[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>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
@ -66,7 +66,7 @@ function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean })
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Indicator */}
|
{/* Right Indicator */}
|
||||||
<div className="w-[4px] bg-[#ff9800] h-full" />
|
<div className="w-[4px] bg-brand-primary h-full" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -124,9 +124,9 @@ export function LiveEventsList() {
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* 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">
|
<div className="flex items-center gap-0 h-full">
|
||||||
{/* Favourites & Prematch */}
|
{/* Favourites & Prematch */}
|
||||||
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
|
<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}
|
key={sport.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col items-center justify-center px-3 h-full border-r border-white/5 min-w-[75px] relative transition-colors",
|
"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="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="text-[16px]">{sport.icon}</span>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"text-[9px] font-bold uppercase mt-1 tracking-tighter whitespace-nowrap",
|
"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}
|
{sport.label}
|
||||||
</span>
|
</span>
|
||||||
{sport.active && (
|
{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>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
@ -164,7 +164,7 @@ export function LiveEventsList() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Category Header (Soccer) */}
|
{/* 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>
|
<span className="text-[16px]">⚽</span>
|
||||||
<h2 className="text-[14px] font-black text-white uppercase tracking-tight">Soccer</h2>
|
<h2 className="text-[14px] font-black text-white uppercase tracking-tight">Soccer</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -174,7 +174,7 @@ export function LiveEventsList() {
|
||||||
{liveMatches.map((group, gIdx) => (
|
{liveMatches.map((group, gIdx) => (
|
||||||
<div key={gIdx} className="flex flex-col">
|
<div key={gIdx} className="flex flex-col">
|
||||||
{/* League Header */}
|
{/* 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" />
|
<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">
|
<span className="text-[9.5px] font-black text-white/60 uppercase tracking-widest leading-none">
|
||||||
{group.league}
|
{group.league}
|
||||||
|
|
|
||||||
269
components/betting/match-detail-view.tsx
Normal file
269
components/betting/match-detail-view.tsx
Normal 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"><</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -8,16 +8,16 @@ export function ReloadTicket() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
|
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
|
||||||
<div className="px-1 text-center space-y-3">
|
<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>
|
<p className="text-[11px] text-white">Insert the code to load</p>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={code}
|
value={code}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const sports = [
|
||||||
export function SportsNav() {
|
export function SportsNav() {
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="football" className="w-full">
|
<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) => (
|
{sports.map((sport) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={sport.id}
|
key={sport.id}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import { ChevronRight } from "lucide-react"
|
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 = [
|
const topMatches = [
|
||||||
{
|
{
|
||||||
|
|
@ -45,23 +48,33 @@ const topMatches = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export function TopMatches() {
|
export function TopMatches() {
|
||||||
|
const { bets, addBet } = useBetslipStore()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide -mx-1 px-1">
|
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide -mx-1 px-1">
|
||||||
{topMatches.map((match) => (
|
{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
|
<div
|
||||||
key={match.id}
|
key={match.id}
|
||||||
className="min-w-[280px] bg-[#222] border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
|
className="min-w-[280px] bg-brand-surface border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
|
||||||
>
|
>
|
||||||
{/* Top Label Ribbon */}
|
{/* 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-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">
|
<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
|
TOP
|
||||||
</div>
|
</div>
|
||||||
</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="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">
|
<div className="flex items-center gap-1">
|
||||||
<span className="font-bold text-[#ff9800]">{match.league}</span>
|
<span className="font-bold text-brand-primary">{match.league}</span>
|
||||||
<span className="font-black text-white ml-1 italic">{match.time}</span>
|
<span className="font-black text-white ml-1 italic">{match.time}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="size-3 text-white/20 mr-4" />
|
<ChevronRight className="size-3 text-white/20 mr-4" />
|
||||||
|
|
@ -73,7 +86,7 @@ export function TopMatches() {
|
||||||
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10">⚽</div>
|
<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>
|
<span className="text-[11px] font-black text-white truncate uppercase italic">{match.homeTeam}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[#ff9800] text-[9.5px] font-black italic shrink-0 px-2 opacity-60">VS</span>
|
<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">
|
<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>
|
<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 className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10">⚽</div>
|
||||||
|
|
@ -82,21 +95,43 @@ export function TopMatches() {
|
||||||
</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">
|
<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">
|
{outcomes.map(({ key, label, odds }) => {
|
||||||
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">1</span>
|
const betId = `${match.id}-${key}`
|
||||||
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.home.toFixed(2)}</span>
|
const isSelected = bets.some((b) => b.id === betId)
|
||||||
</button>
|
return (
|
||||||
<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">
|
<button
|
||||||
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">X</span>
|
key={key}
|
||||||
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.draw.toFixed(2)}</span>
|
type="button"
|
||||||
</button>
|
onClick={() =>
|
||||||
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn">
|
addBet({
|
||||||
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">2</span>
|
id: betId,
|
||||||
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.away.toFixed(2)}</span>
|
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>
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
components/games/game-card.tsx
Normal file
40
components/games/game-card.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
95
components/games/game-row.tsx
Normal file
95
components/games/game-row.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
86
components/games/gaming-sidebar.tsx
Normal file
86
components/games/gaming-sidebar.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
88
components/games/virtual-sidebar.tsx
Normal file
88
components/games/virtual-sidebar.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user