Compare commits
6 Commits
2c96be08bf
...
bf1efb3341
| Author | SHA1 | Date | |
|---|---|---|---|
| bf1efb3341 | |||
| 263cd4b65e | |||
| 463cabbdd0 | |||
| bad08b229e | |||
| 9029fb4f6a | |||
| 5587dff57c |
|
|
@ -1,2 +0,0 @@
|
||||||
Internal shared components for app routes.
|
|
||||||
|
|
||||||
21
app/event/[id]/page.tsx
Normal file
21
app/event/[id]/page.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import Link from "next/link"
|
||||||
|
import { getEventById } from "@/lib/mock-data"
|
||||||
|
import { MatchDetailView } from "@/components/betting/match-detail-view"
|
||||||
|
|
||||||
|
export default async function EventPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
|
const { id } = await params
|
||||||
|
const event = getEventById(id)
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-[40vh] gap-4">
|
||||||
|
<p className="text-white/80">Match not found.</p>
|
||||||
|
<Link href="/" className="text-brand-primary font-bold hover:underline">
|
||||||
|
Back to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MatchDetailView event={event} />
|
||||||
|
}
|
||||||
|
|
@ -57,41 +57,39 @@
|
||||||
:root {
|
:root {
|
||||||
--radius: 0rem;
|
--radius: 0rem;
|
||||||
|
|
||||||
/* Brand Colors (Black, Yellow, White theme) */
|
/* Brand Colors (White and Green theme) - dark grey, not near-black */
|
||||||
--brand-primary: #ff9800;
|
--brand-primary: #22c55e;
|
||||||
--brand-primary-hover: #e68900;
|
--brand-primary-hover: #16a34a;
|
||||||
--brand-secondary: #ff9800;
|
--brand-secondary: #22c55e;
|
||||||
/* Usually same as primary for now */
|
--brand-bg: #252525;
|
||||||
--brand-bg: #121212;
|
--brand-surface: #2d2d2d;
|
||||||
--brand-surface: #1a1a1a;
|
--brand-surface-light: #353535;
|
||||||
--brand-surface-light: #2a2a2a;
|
--brand-accent: #15803d;
|
||||||
--brand-accent: #852222;
|
--brand-live: #22c55e;
|
||||||
/* Maroon */
|
|
||||||
--brand-live: #ff3b3b;
|
|
||||||
|
|
||||||
/* Shadcn default mappings */
|
/* Shadcn default mappings */
|
||||||
--background: var(--brand-bg);
|
--background: var(--brand-bg);
|
||||||
--foreground: #ffffff;
|
--foreground: #ffffff;
|
||||||
--card: #1e1e1e;
|
--card: #2a2a2a;
|
||||||
--card-foreground: #ffffff;
|
--card-foreground: #ffffff;
|
||||||
--popover: #1e1e1e;
|
--popover: #2a2a2a;
|
||||||
--popover-foreground: #ffffff;
|
--popover-foreground: #ffffff;
|
||||||
--primary: var(--brand-primary);
|
--primary: var(--brand-primary);
|
||||||
--primary-foreground: #ffffff;
|
--primary-foreground: #ffffff;
|
||||||
--secondary: #222222;
|
--secondary: #2d2d2d;
|
||||||
--secondary-foreground: #a0a0a0;
|
--secondary-foreground: #a0a0a0;
|
||||||
--muted: #1a1a1a;
|
--muted: #252525;
|
||||||
--muted-foreground: #808080;
|
--muted-foreground: #808080;
|
||||||
--accent: var(--brand-primary);
|
--accent: var(--brand-primary);
|
||||||
--accent-foreground: #121212;
|
--accent-foreground: #121212;
|
||||||
--destructive: #ef4444;
|
--destructive: #ef4444;
|
||||||
--border: #2a2a2a;
|
--border: #353535;
|
||||||
--input: #222222;
|
--input: #2d2d2d;
|
||||||
--ring: var(--brand-primary);
|
--ring: var(--brand-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.13 0.008 250);
|
--background: oklch(0.17 0.01 250);
|
||||||
--foreground: oklch(0.93 0.005 250);
|
--foreground: oklch(0.93 0.005 250);
|
||||||
--card: oklch(0.17 0.01 250);
|
--card: oklch(0.17 0.01 250);
|
||||||
--card-foreground: oklch(0.93 0.005 250);
|
--card-foreground: oklch(0.93 0.005 250);
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,13 @@ export default function LoginPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* HARIF box */}
|
{/* HARIF box */}
|
||||||
<div className="bg-[#852222] px-3 py-1 -skew-x-12 flex items-center h-[38px]">
|
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[38px]">
|
||||||
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
|
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
|
||||||
HARIF
|
HARIF
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* SPORT text */}
|
{/* SPORT text */}
|
||||||
<span className="text-2xl font-black text-[#ff9800] italic tracking-tighter ml-1 leading-none">
|
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">
|
||||||
SPORT
|
SPORT
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,7 +76,7 @@ export default function LoginPage() {
|
||||||
type="tel"
|
type="tel"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
|
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,7 +91,7 @@ export default function LoginPage() {
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
|
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ export default function LoginPage() {
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Link
|
<Link
|
||||||
href="/reset-password"
|
href="/reset-password"
|
||||||
className="text-[11px] text-white/50 hover:text-[#ff9800] transition-colors"
|
className="text-[11px] text-white/50 hover:text-brand-primary transition-colors"
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -120,7 +120,7 @@ export default function LoginPage() {
|
||||||
{/* Register link */}
|
{/* Register link */}
|
||||||
<p className="text-center text-[11px] text-white/50">
|
<p className="text-center text-[11px] text-white/50">
|
||||||
Don't have an account?{" "}
|
Don't have an account?{" "}
|
||||||
<Link href="/register" className="text-[#ff9800] hover:underline font-semibold">
|
<Link href="/register" className="text-brand-primary hover:underline font-semibold">
|
||||||
Register
|
Register
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
|
import { Suspense } from "react"
|
||||||
import { SportHome } from "@/components/betting/sport-home"
|
import { SportHome } from "@/components/betting/sport-home"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return <SportHome />
|
return (
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<SportHome />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
385
app/print-odds/page.tsx
Normal file
385
app/print-odds/page.tsx
Normal file
|
|
@ -0,0 +1,385 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useMemo } from "react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
type NationId = string
|
||||||
|
type TournamentId = string
|
||||||
|
type MarketId = string
|
||||||
|
|
||||||
|
const SPORTS_WITH_NATIONS: { sport: string; sportId: string; nations: { id: NationId; name: string; flag: string }[] }[] = [
|
||||||
|
{
|
||||||
|
sport: "Football",
|
||||||
|
sportId: "football",
|
||||||
|
nations: [
|
||||||
|
{ id: "intl-clubs", name: "International Clubs", flag: "🌍" },
|
||||||
|
{ id: "england", name: "England", flag: "🏴" },
|
||||||
|
{ id: "spain", name: "Spain", flag: "🇪🇸" },
|
||||||
|
{ id: "germany", name: "Germany", flag: "🇩🇪" },
|
||||||
|
{ id: "italy", name: "Italy", flag: "🇮🇹" },
|
||||||
|
{ id: "international", name: "International", flag: "🌐" },
|
||||||
|
{ id: "france", name: "France", flag: "🇫🇷" },
|
||||||
|
{ id: "portugal", name: "Portugal", flag: "🇵🇹" },
|
||||||
|
{ id: "england-amateur", name: "England Amateur", flag: "🏴" },
|
||||||
|
{ id: "netherlands", name: "Netherlands", flag: "🇳🇱" },
|
||||||
|
{ id: "scotland", name: "Scotland", flag: "🏴" },
|
||||||
|
{ id: "belgium", name: "Belgium", flag: "🇧🇪" },
|
||||||
|
{ id: "turkiye", name: "Turkiye", flag: "🇹🇷" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const TOURNAMENTS_BY_NATION: Record<NationId, { id: TournamentId; name: string; flag: string }[]> = {
|
||||||
|
"intl-clubs": [
|
||||||
|
{ id: "uefa-cl", name: "UEFA Champions League", flag: "🏆" },
|
||||||
|
{ id: "uefa-el", name: "UEFA Europa League", flag: "🏆" },
|
||||||
|
{ id: "uefa-ecl", name: "UEFA Europa Conference League", flag: "🏆" },
|
||||||
|
{ id: "copa-libertadores", name: "Copa Libertadores", flag: "🏆" },
|
||||||
|
{ id: "copa-sudamericana", name: "Copa Sudamericana", flag: "🏆" },
|
||||||
|
],
|
||||||
|
england: [
|
||||||
|
{ id: "premier-league", name: "Premier League", flag: "🏴" },
|
||||||
|
{ id: "championship", name: "Championship", flag: "🏴" },
|
||||||
|
{ id: "league-one", name: "League One", flag: "🏴" },
|
||||||
|
{ id: "league-two", name: "League Two", flag: "🏴" },
|
||||||
|
{ id: "fa-cup", name: "FA Cup", flag: "🏴" },
|
||||||
|
{ id: "efl-cup", name: "EFL Cup", flag: "🏴" },
|
||||||
|
{ id: "national-league", name: "National League", flag: "🏴" },
|
||||||
|
],
|
||||||
|
spain: [
|
||||||
|
{ id: "laliga", name: "LaLiga", flag: "🇪🇸" },
|
||||||
|
{ id: "laliga2", name: "LaLiga 2", flag: "🇪🇸" },
|
||||||
|
{ id: "copa-del-rey", name: "Copa del Rey", flag: "🇪🇸" },
|
||||||
|
],
|
||||||
|
germany: [
|
||||||
|
{ id: "bundesliga", name: "Bundesliga", flag: "🇩🇪" },
|
||||||
|
{ id: "bundesliga2", name: "2. Bundesliga", flag: "🇩🇪" },
|
||||||
|
{ id: "dfb-pokal", name: "DFB-Pokal", flag: "🇩🇪" },
|
||||||
|
],
|
||||||
|
italy: [
|
||||||
|
{ id: "serie-a", name: "Serie A", flag: "🇮🇹" },
|
||||||
|
{ id: "serie-b", name: "Serie B", flag: "🇮🇹" },
|
||||||
|
{ id: "coppa-italia", name: "Coppa Italia", flag: "🇮🇹" },
|
||||||
|
],
|
||||||
|
international: [
|
||||||
|
{ id: "world-cup", name: "FIFA World Cup", flag: "🌐" },
|
||||||
|
{ id: "euro", name: "UEFA European Championship", flag: "🌐" },
|
||||||
|
],
|
||||||
|
france: [
|
||||||
|
{ id: "ligue1", name: "Ligue 1", flag: "🇫🇷" },
|
||||||
|
{ id: "ligue2", name: "Ligue 2", flag: "🇫🇷" },
|
||||||
|
],
|
||||||
|
portugal: [
|
||||||
|
{ id: "liga-portugal", name: "Liga Portugal", flag: "🇵🇹" },
|
||||||
|
],
|
||||||
|
"england-amateur": [],
|
||||||
|
netherlands: [
|
||||||
|
{ id: "eredivisie", name: "Eredivisie", flag: "🇳🇱" },
|
||||||
|
{ id: "eerste-divisie", name: "Eerste Divisie", flag: "🇳🇱" },
|
||||||
|
],
|
||||||
|
scotland: [{ id: "scotland-prem", name: "Scottish Premiership", flag: "🏴" }],
|
||||||
|
belgium: [{ id: "pro-league", name: "Pro League", flag: "🇧🇪" }],
|
||||||
|
turkiye: [{ id: "super-lig", name: "Süper Lig", flag: "🇹🇷" }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALL_MARKETS: { id: MarketId; code: string; name: string }[] = [
|
||||||
|
{ id: "164", code: "164", name: "1x2" },
|
||||||
|
{ id: "166", code: "166", name: "Double Chance" },
|
||||||
|
{ id: "168", code: "168", name: "Draw No Bet" },
|
||||||
|
{ id: "170", code: "170", name: "Handicap 1:0" },
|
||||||
|
{ id: "172", code: "172", name: "Asian Handicap 0.5" },
|
||||||
|
{ id: "174", code: "174", name: "Total 0.5" },
|
||||||
|
{ id: "176", code: "176", name: "Total 1.5" },
|
||||||
|
{ id: "178", code: "178", name: "Total 2.5" },
|
||||||
|
{ id: "180", code: "180", name: "Total 3.5" },
|
||||||
|
{ id: "181", code: "181", name: "Total 4.5" },
|
||||||
|
{ id: "184", code: "184", name: "Home Team Over/Under" },
|
||||||
|
{ id: "186", code: "186", name: "Home Team Over/Under" },
|
||||||
|
{ id: "188", code: "188", name: "Away Team Over/Under" },
|
||||||
|
{ id: "189", code: "189", name: "Away Team Over/Under" },
|
||||||
|
{ id: "191", code: "191", name: "Home Team Goals" },
|
||||||
|
{ id: "193", code: "193", name: "Away Team Goals" },
|
||||||
|
{ id: "194", code: "194", name: "1st Half - 1X2" },
|
||||||
|
{ id: "196", code: "196", name: "1st Half - Draw No Bet" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const MAIN_MARKET_IDS: MarketId[] = ["164", "166", "168", "178", "194"]
|
||||||
|
|
||||||
|
export default function PrintOddsPage() {
|
||||||
|
const [sportChecked, setSportChecked] = useState(true)
|
||||||
|
const [selectedNations, setSelectedNations] = useState<Set<NationId>>(new Set())
|
||||||
|
const [selectedTournaments, setSelectedTournaments] = useState<Set<TournamentId>>(new Set())
|
||||||
|
const [selectedMarkets, setSelectedMarkets] = useState<Set<MarketId>>(new Set())
|
||||||
|
const [sorting, setSorting] = useState("Events")
|
||||||
|
const [printSorting, setPrintSorting] = useState("Horizontal")
|
||||||
|
const [dateHelper, setDateHelper] = useState("Today")
|
||||||
|
const [startDate, setStartDate] = useState("02/28/2026")
|
||||||
|
const [endDate, setEndDate] = useState("02/28/2026")
|
||||||
|
const [logo, setLogo] = useState("Without logo")
|
||||||
|
|
||||||
|
const availableTournaments = useMemo(() => {
|
||||||
|
if (!sportChecked || selectedNations.size === 0) return []
|
||||||
|
const list: { id: TournamentId; name: string; flag: string }[] = []
|
||||||
|
selectedNations.forEach((nationId) => {
|
||||||
|
const tournaments = TOURNAMENTS_BY_NATION[nationId as NationId]
|
||||||
|
if (tournaments) list.push(...tournaments)
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}, [sportChecked, selectedNations])
|
||||||
|
|
||||||
|
const toggleNation = (id: NationId) => {
|
||||||
|
setSelectedNations((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(id)) next.delete(id)
|
||||||
|
else next.add(id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleTournament = (id: TournamentId) => {
|
||||||
|
setSelectedTournaments((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(id)) next.delete(id)
|
||||||
|
else next.add(id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleMarket = (id: MarketId) => {
|
||||||
|
setSelectedMarkets((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(id)) next.delete(id)
|
||||||
|
else next.add(id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickMainMarkets = () => {
|
||||||
|
setSelectedMarkets((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
MAIN_MARKET_IDS.forEach((id) => next.add(id))
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const makePdf = () => {
|
||||||
|
const printWindow = window.open("", "_blank")
|
||||||
|
if (!printWindow) {
|
||||||
|
window.print()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Print Odds</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; padding: 20px; color: #111; }
|
||||||
|
h1 { font-size: 18px; margin-bottom: 16px; }
|
||||||
|
table { border-collapse: collapse; width: 100%; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||||
|
th { background: #f5f5f5; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Print Odds – ${dateHelper}</h1>
|
||||||
|
<p><strong>Date range:</strong> ${startDate} – ${endDate}</p>
|
||||||
|
<p><strong>Nations:</strong> ${Array.from(selectedNations).join(", ")}</p>
|
||||||
|
<p><strong>Tournaments:</strong> ${Array.from(selectedTournaments).join(", ")}</p>
|
||||||
|
<p><strong>Markets:</strong> ${Array.from(selectedMarkets).map((id) => ALL_MARKETS.find((m) => m.id === id)?.name ?? id).join(", ")}</p>
|
||||||
|
<p><em>Odds sheet would be generated based on selected options.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`)
|
||||||
|
printWindow.document.close()
|
||||||
|
printWindow.focus()
|
||||||
|
printWindow.print()
|
||||||
|
printWindow.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const sport = SPORTS_WITH_NATIONS[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-brand-bg text-white flex flex-col">
|
||||||
|
<div className="bg-brand-surface border-b border-white/10 px-4 py-2 flex flex-wrap items-center gap-4">
|
||||||
|
<select
|
||||||
|
value={sorting}
|
||||||
|
onChange={(e) => setSorting(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] px-2 py-1.5 rounded-none"
|
||||||
|
>
|
||||||
|
<option value="Events">Events</option>
|
||||||
|
<option value="Leagues">Leagues</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
value={printSorting}
|
||||||
|
onChange={(e) => setPrintSorting(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] px-2 py-1.5 rounded-none"
|
||||||
|
>
|
||||||
|
<option value="Horizontal">Horizontal</option>
|
||||||
|
<option value="Vertical">Vertical</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
value={dateHelper}
|
||||||
|
onChange={(e) => setDateHelper(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] px-2 py-1.5 rounded-none"
|
||||||
|
>
|
||||||
|
<option value="Today">Today</option>
|
||||||
|
<option value="Tomorrow">Tomorrow</option>
|
||||||
|
<option value="Week">Week</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] w-28 px-2 py-1.5 rounded-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] w-28 px-2 py-1.5 rounded-none"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={logo}
|
||||||
|
onChange={(e) => setLogo(e.target.value)}
|
||||||
|
className="bg-brand-surface-light border border-white/20 text-white text-[11px] px-2 py-1.5 rounded-none"
|
||||||
|
>
|
||||||
|
<option value="Without logo">Without logo</option>
|
||||||
|
<option value="With logo">With logo</option>
|
||||||
|
</select>
|
||||||
|
<div className="flex-1" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={pickMainMarkets}
|
||||||
|
className="bg-brand-primary text-black px-4 py-2 text-[11px] font-bold uppercase rounded-none hover:bg-brand-primary-hover"
|
||||||
|
>
|
||||||
|
Pick Main Markets +
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={makePdf}
|
||||||
|
className="bg-brand-primary text-black px-4 py-2 text-[11px] font-bold uppercase rounded-none hover:bg-brand-primary-hover flex items-center gap-1"
|
||||||
|
>
|
||||||
|
Make PDF
|
||||||
|
<svg viewBox="0 0 24 24" className="size-4 fill-current"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="print-odds-content" className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-0 p-4 min-h-0">
|
||||||
|
{/* 1 Choose Nation */}
|
||||||
|
<div className="border border-white/10 rounded-l bg-brand-surface-light overflow-hidden flex flex-col">
|
||||||
|
<div className="px-3 py-2 bg-brand-surface border-b border-white/10 text-[11px] font-black uppercase">
|
||||||
|
1 Choose Nation
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
||||||
|
<label className="flex items-center gap-2 py-1.5 px-2 hover:bg-white/5 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={sportChecked}
|
||||||
|
onChange={(e) => setSportChecked(e.target.checked)}
|
||||||
|
className="rounded border-white/40 text-brand-primary"
|
||||||
|
/>
|
||||||
|
<span className="text-[11px] font-bold">{sport.sport}</span>
|
||||||
|
</label>
|
||||||
|
{sport.nations.map((nation) => (
|
||||||
|
<label
|
||||||
|
key={nation.id}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 py-1.5 px-2 hover:bg-white/5 cursor-pointer rounded",
|
||||||
|
selectedNations.has(nation.id) && "bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedNations.has(nation.id)}
|
||||||
|
onChange={() => toggleNation(nation.id)}
|
||||||
|
className="rounded border-white/40 text-brand-primary"
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] mr-1">{nation.flag}</span>
|
||||||
|
<span className="text-[11px]">{nation.name}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2 Choose Tournaments */}
|
||||||
|
<div className="border border-white/10 border-x-0 md:border-x bg-brand-surface-light overflow-hidden flex flex-col">
|
||||||
|
<div className="px-3 py-2 bg-brand-surface border-b border-white/10 text-[11px] font-black uppercase">
|
||||||
|
2 Choose Tournaments
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
||||||
|
{availableTournaments.length === 0 ? (
|
||||||
|
<p className="text-white/50 text-[11px] py-4 px-2">Select nations first</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<label className="flex items-center gap-2 py-1.5 px-2 hover:bg-white/5 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedTournaments.size === availableTournaments.length}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedTournaments(new Set(availableTournaments.map((t) => t.id)))
|
||||||
|
} else {
|
||||||
|
setSelectedTournaments(new Set())
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="rounded border-white/40 text-brand-primary"
|
||||||
|
/>
|
||||||
|
<span className="text-[11px] font-bold">All</span>
|
||||||
|
</label>
|
||||||
|
{availableTournaments.map((t) => (
|
||||||
|
<label
|
||||||
|
key={t.id}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 py-1.5 px-2 hover:bg-white/5 cursor-pointer rounded",
|
||||||
|
selectedTournaments.has(t.id) && "bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedTournaments.has(t.id)}
|
||||||
|
onChange={() => toggleTournament(t.id)}
|
||||||
|
className="rounded border-white/40 text-brand-primary"
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] mr-1">{t.flag}</span>
|
||||||
|
<span className="text-[11px]">{t.name}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3 Choose Markets - shown only after nations and tournaments are chosen */}
|
||||||
|
<div className="border border-white/10 rounded-r bg-brand-surface-light overflow-hidden flex flex-col">
|
||||||
|
<div className="px-3 py-2 bg-brand-surface border-b border-white/10 text-[11px] font-black uppercase">
|
||||||
|
3 Choose Markets
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
||||||
|
{selectedNations.size === 0 || selectedTournaments.size === 0 ? (
|
||||||
|
<p className="text-white/50 text-[11px] py-4 px-2">Select nations and tournaments first</p>
|
||||||
|
) : (
|
||||||
|
ALL_MARKETS.map((m) => (
|
||||||
|
<label
|
||||||
|
key={m.id}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 py-1.5 px-2 hover:bg-white/5 cursor-pointer rounded",
|
||||||
|
selectedMarkets.has(m.id) && "bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedMarkets.has(m.id)}
|
||||||
|
onChange={() => toggleMarket(m.id)}
|
||||||
|
className="rounded border-white/40 text-brand-primary"
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] text-white/60 w-6">{m.code}</span>
|
||||||
|
<span className="text-[11px]">{m.name}</span>
|
||||||
|
</label>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -44,13 +44,13 @@ export default function RegisterPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* HARIF box */}
|
{/* HARIF box */}
|
||||||
<div className="bg-[#852222] px-3 py-1 -skew-x-12 flex items-center h-[38px]">
|
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[38px]">
|
||||||
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
|
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
|
||||||
HARIF
|
HARIF
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* SPORT text */}
|
{/* SPORT text */}
|
||||||
<span className="text-2xl font-black text-[#ff9800] italic tracking-tighter ml-1 leading-none">
|
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">
|
||||||
SPORT
|
SPORT
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,7 +78,7 @@ export default function RegisterPage() {
|
||||||
type="tel"
|
type="tel"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
|
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -93,7 +93,7 @@ export default function RegisterPage() {
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
|
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ export default function RegisterPage() {
|
||||||
value={repeatPassword}
|
value={repeatPassword}
|
||||||
onChange={(e) => setRepeatPassword(e.target.value)}
|
onChange={(e) => setRepeatPassword(e.target.value)}
|
||||||
placeholder="Repeat Password"
|
placeholder="Repeat Password"
|
||||||
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
|
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ export default function RegisterPage() {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ageConfirmed}
|
checked={ageConfirmed}
|
||||||
onChange={(e) => setAgeConfirmed(e.target.checked)}
|
onChange={(e) => setAgeConfirmed(e.target.checked)}
|
||||||
className="w-4 h-4 accent-[#ff9800]"
|
className="w-4 h-4 accent-brand-primary"
|
||||||
/>
|
/>
|
||||||
<span className="text-[12px] text-white/80">
|
<span className="text-[12px] text-white/80">
|
||||||
I confirm I'm over 21 years old
|
I confirm I'm over 21 years old
|
||||||
|
|
@ -139,7 +139,7 @@ export default function RegisterPage() {
|
||||||
{/* Login link */}
|
{/* Login link */}
|
||||||
<p className="text-center text-[11px] text-white/50">
|
<p className="text-center text-[11px] text-white/50">
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<Link href="/login" className="text-[#ff9800] hover:underline font-semibold">
|
<Link href="/login" className="text-brand-primary hover:underline font-semibold">
|
||||||
Login
|
Login
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
106
app/special-games/page.tsx
Normal file
106
app/special-games/page.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import Image from "next/image"
|
||||||
|
import { GamingSidebar } from "@/components/games/gaming-sidebar"
|
||||||
|
import { GameRow } from "@/components/games/game-row"
|
||||||
|
import { GameCard } from "@/components/games/game-card"
|
||||||
|
import { Search, Heart, Clock, Star, Zap } from "lucide-react"
|
||||||
|
|
||||||
|
// Dummy data
|
||||||
|
const DUMMY_GAMES = [
|
||||||
|
{ id: "s1", title: "Safari Hot 20", image: "https://images.unsplash.com/photo-1546182990-dffeafbe841d?w=400&h=300&fit=crop", provider: "Pragmatic Play" },
|
||||||
|
{ id: "s2", title: "Phoenix Hot 20", image: "https://images.unsplash.com/photo-1535223289827-42f1e9919769?w=400&h=300&fit=crop", provider: "Gamzix" },
|
||||||
|
{ id: "s3", title: "Habesha Fortune 5", image: "https://images.unsplash.com/photo-1511512578047-dfb367046420?w=400&h=300&fit=crop", provider: "Smartsoft" },
|
||||||
|
{ id: "s4", title: "Chicken Road 2.0", image: "https://images.unsplash.com/photo-1518717758536-85ae29035b6d?w=400&h=300&fit=crop", provider: "Smartsoft" },
|
||||||
|
{ id: "s5", title: "Hot to Burn", image: "https://images.unsplash.com/photo-1553775282-20af807d920c?w=400&h=300&fit=crop", provider: "Pragmatic Play" },
|
||||||
|
{ id: "s6", title: "HydroPlane", image: "https://images.unsplash.com/photo-1567620905732-2d1ec7bb7445?w=400&h=300&fit=crop", provider: "Smartsoft" },
|
||||||
|
{ id: "s7", title: "Maneki Win Hard", image: "https://images.unsplash.com/photo-1569154941061-e231b4725ef1?w=400&h=300&fit=crop", provider: "Scribe" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const CATEGORIES = [
|
||||||
|
{ id: "search", name: "Search", icon: Search },
|
||||||
|
{ id: "popular", name: "Popular Games", icon: Star },
|
||||||
|
{ id: "favourites", name: "Favourites", icon: Heart },
|
||||||
|
{ id: "recently-played", name: "Recently Played", icon: Clock },
|
||||||
|
{ id: "evoplay", name: "Evoplay", icon: Star },
|
||||||
|
{ id: "betsoft", name: "Betsoft", icon: Star },
|
||||||
|
{ id: "inout", name: "InOUT Games", icon: Star },
|
||||||
|
{ id: "scribe", name: "Scribe Games", icon: Star },
|
||||||
|
{ id: "pragmatic", name: "Pragmatic", icon: Star },
|
||||||
|
{ id: "mancala", name: "Mancala", icon: Star },
|
||||||
|
{ id: "spinomenal", name: "Spinomenal", icon: Star },
|
||||||
|
{ id: "abracadabra", name: "Abracadabra", icon: Star },
|
||||||
|
{ id: "turbo", name: "Turbo Games", icon: Star },
|
||||||
|
]
|
||||||
|
|
||||||
|
const CATEGORIES_DATA = [
|
||||||
|
{ id: "popular", title: "Popular Games", games: [...DUMMY_GAMES, ...DUMMY_GAMES, ...DUMMY_GAMES].slice(0, 18) },
|
||||||
|
{ id: "evoplay", title: "Evoplay", games: [...DUMMY_GAMES].reverse() },
|
||||||
|
{ id: "betsoft", title: "Betsoft", games: DUMMY_GAMES.slice(0, 4) },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function SpecialGamesPage() {
|
||||||
|
const [activeCategory, setActiveCategory] = useState("all")
|
||||||
|
|
||||||
|
const activeCategoryData = CATEGORIES_DATA.find(c => c.id === activeCategory)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-[calc(100vh-140px)] overflow-hidden bg-brand-bg">
|
||||||
|
<GamingSidebar
|
||||||
|
title="Special Games"
|
||||||
|
subtitle="Check out our games!"
|
||||||
|
activeCategory={activeCategory}
|
||||||
|
onCategoryChange={setActiveCategory}
|
||||||
|
categories={CATEGORIES}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main className="flex-1 overflow-y-auto scrollbar-none pb-12">
|
||||||
|
<div className="relative w-full aspect-[4/1] md:aspect-[5/1] overflow-hidden">
|
||||||
|
<Image
|
||||||
|
src="/aviator-bannsser.jpg"
|
||||||
|
alt="Special Games Banner"
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-0">
|
||||||
|
{activeCategory === "all" ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{CATEGORIES_DATA.map((category) => (
|
||||||
|
<GameRow
|
||||||
|
key={category.id}
|
||||||
|
title={category.title}
|
||||||
|
games={category.games}
|
||||||
|
rows={category.id === "popular" ? 3 : 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-lg font-bold text-white uppercase border-l-4 border-brand-primary pl-4">
|
||||||
|
{activeCategoryData?.title || activeCategory.replace("-", " ")}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveCategory("all")}
|
||||||
|
className="text-xs text-white/40 hover:text-white uppercase font-bold"
|
||||||
|
>
|
||||||
|
Back to Special Games
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6">
|
||||||
|
{(activeCategoryData?.games || DUMMY_GAMES).map((game, idx) => (
|
||||||
|
<GameCard key={idx} {...game} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
204
app/virtual/page.tsx
Normal file
204
app/virtual/page.tsx
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import Image from "next/image"
|
||||||
|
import { GamingSidebar } from "@/components/games/gaming-sidebar"
|
||||||
|
import { GameRow } from "@/components/games/game-row"
|
||||||
|
import { GameCard } from "@/components/games/game-card"
|
||||||
|
import { Search, Heart, Clock, Star, Zap, Gamepad2, AlertCircle, LayoutGrid } from "lucide-react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import api from "@/lib/api"
|
||||||
|
|
||||||
|
interface Provider {
|
||||||
|
provider_id: string
|
||||||
|
provider_name: string
|
||||||
|
logo_dark: string
|
||||||
|
logo_light: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiGame {
|
||||||
|
gameId: string
|
||||||
|
providerId: string
|
||||||
|
provider: string
|
||||||
|
name: string
|
||||||
|
category: string
|
||||||
|
deviceType: string
|
||||||
|
hasDemo: boolean
|
||||||
|
hasFreeBets: boolean
|
||||||
|
demoUrl?: string
|
||||||
|
image?: string // In case it gets added
|
||||||
|
thumbnail?: string
|
||||||
|
provider_id?: string // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_IMAGE = "https://st.pokgaming.com/gameThumbnails/246.jpg"
|
||||||
|
|
||||||
|
export default function VirtualPage() {
|
||||||
|
const [activeCategory, setActiveCategory] = useState("all")
|
||||||
|
const [providers, setProviders] = useState<Provider[]>([])
|
||||||
|
const [games, setGames] = useState<ApiGame[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchVirtualData = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const [providersRes, gamesRes] = await Promise.all([
|
||||||
|
api.get("/virtual-game/orchestrator/providers"),
|
||||||
|
api.get("/virtual-game/orchestrator/games", { params: { limit: 2000 } })
|
||||||
|
])
|
||||||
|
|
||||||
|
const pData = providersRes.data
|
||||||
|
const gData = gamesRes.data
|
||||||
|
|
||||||
|
const providersList = pData.providers || pData.data || pData || []
|
||||||
|
const gamesList = gData.data || gData.games || gData || []
|
||||||
|
|
||||||
|
setProviders(Array.isArray(providersList) ? providersList : [])
|
||||||
|
setGames(Array.isArray(gamesList) ? gamesList : [])
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Failed to fetch virtual games data:", err)
|
||||||
|
setError("Failed to load games data.")
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchVirtualData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Create Sidebar Categories dynamically from providers
|
||||||
|
const sidebarCategories = [
|
||||||
|
{ id: "all", name: "All Games", icon: LayoutGrid },
|
||||||
|
...providers.map(p => ({
|
||||||
|
id: p.provider_id,
|
||||||
|
name: p.provider_name,
|
||||||
|
icon: p.logo_dark || p.logo_light || Gamepad2
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
|
||||||
|
// Filter games based on active category
|
||||||
|
// If "all", group by provider
|
||||||
|
let displayedGames: any[] = []
|
||||||
|
let groupedGames: { title: string, games: any[] }[] = []
|
||||||
|
|
||||||
|
const mapApiGameToCard = (game: ApiGame) => ({
|
||||||
|
id: game.gameId,
|
||||||
|
title: game.name,
|
||||||
|
image: game.thumbnail || game.image || DEFAULT_IMAGE,
|
||||||
|
provider: game.provider
|
||||||
|
})
|
||||||
|
|
||||||
|
if (activeCategory === "all") {
|
||||||
|
// Group up to 12 games per provider for the rows
|
||||||
|
providers.forEach(p => {
|
||||||
|
const providerIdStr = String(p.provider_id || "").trim().toLowerCase()
|
||||||
|
const providerGames = games
|
||||||
|
.filter(g => {
|
||||||
|
const gameProvId = String(g.providerId || g.provider_id || "").trim().toLowerCase()
|
||||||
|
return gameProvId === providerIdStr
|
||||||
|
})
|
||||||
|
.slice(0, 12)
|
||||||
|
.map(mapApiGameToCard)
|
||||||
|
|
||||||
|
if (providerGames.length > 0) {
|
||||||
|
groupedGames.push({
|
||||||
|
title: p.provider_name,
|
||||||
|
games: providerGames
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
|
||||||
|
displayedGames = games
|
||||||
|
.filter(g => {
|
||||||
|
const gameProvId = String(g.providerId || g.provider_id || "").trim().toLowerCase()
|
||||||
|
const matches = gameProvId === String(activeCategory).trim().toLowerCase()
|
||||||
|
if (g.providerId?.toLowerCase().includes('pop') || g.provider_id?.toLowerCase().includes('pop')) {
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
})
|
||||||
|
.map(mapApiGameToCard)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCategoryData = providers.find(
|
||||||
|
p => String(p.provider_id || "").trim().toLowerCase() === String(activeCategory).trim().toLowerCase()
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-[calc(100vh-140px)] overflow-hidden bg-brand-bg">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<GamingSidebar
|
||||||
|
title="Virtual"
|
||||||
|
subtitle="Check out our games!"
|
||||||
|
activeCategory={activeCategory}
|
||||||
|
onCategoryChange={setActiveCategory}
|
||||||
|
categories={sidebarCategories}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 overflow-y-auto scrollbar-none pb-12">
|
||||||
|
{/* Banner */}
|
||||||
|
<div className="relative w-full aspect-[4/1] md:aspect-[5/1] overflow-hidden">
|
||||||
|
<Image
|
||||||
|
src="/aviator-bannsser.jpg"
|
||||||
|
alt="Virtual Games Banner"
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-0">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="p-8 text-center text-white/60 text-sm font-bold flex items-center justify-center gap-2">
|
||||||
|
<div className="size-4 rounded-full border-2 border-brand-primary border-t-transparent animate-spin" />
|
||||||
|
Loading games...
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="p-8 text-center text-red-400 text-sm font-bold flex items-center justify-center gap-2">
|
||||||
|
<AlertCircle className="size-4" />
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
) : activeCategory === "all" ? (
|
||||||
|
// Show all categories
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{groupedGames.map((category, index) => (
|
||||||
|
<GameRow
|
||||||
|
key={category.title}
|
||||||
|
title={category.title}
|
||||||
|
games={category.games}
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Show only selected category
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-lg font-bold text-white uppercase border-l-4 border-brand-primary pl-4">
|
||||||
|
{activeCategoryData?.provider_name || 'Games'}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveCategory("all")}
|
||||||
|
className="text-xs text-white/40 hover:text-white uppercase font-bold"
|
||||||
|
>
|
||||||
|
Back to Virtual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6">
|
||||||
|
{displayedGames.map((game, idx) => (
|
||||||
|
<GameCard key={game.id || idx} {...game} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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">
|
<button
|
||||||
<span className="text-sm font-bold text-primary">{bet.odds.toFixed(2)}</span>
|
onClick={() => removeBet(bet.id)}
|
||||||
<button
|
className="text-white/50 hover:text-white transition-colors shrink-0 p-0.5"
|
||||||
onClick={() => removeBet(bet.id)}
|
aria-label="Remove bet"
|
||||||
className="text-muted-foreground hover:text-destructive transition-colors"
|
>
|
||||||
>
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 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>
|
<div className="flex items-center gap-1">
|
||||||
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
|
<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
|
||||||
{quickStakes.map((s) => (
|
type="button"
|
||||||
<button
|
onClick={() => setGlobalStake(String(Number(globalStake) + 1))}
|
||||||
key={s}
|
className="w-7 h-7 flex items-center justify-center rounded bg-white/10 text-white text-sm font-bold hover:bg-white/20"
|
||||||
onClick={() => setGlobalStake(String(s))}
|
>
|
||||||
className={cn(
|
+
|
||||||
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
|
</button>
|
||||||
globalStake === String(s)
|
</div>
|
||||||
? "bg-primary text-primary-foreground border-primary"
|
<div className="flex flex-wrap gap-1.5">
|
||||||
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
|
{quickStakes.map((s) => (
|
||||||
)}
|
<button
|
||||||
>
|
key={s}
|
||||||
{s}
|
onClick={() => setGlobalStake(String(s))}
|
||||||
</button>
|
className={cn(
|
||||||
))}
|
"size-7 rounded-full text-[10px] font-bold transition-colors",
|
||||||
</div>
|
Number(globalStake) === s
|
||||||
<div className="mt-1.5 flex justify-between text-[10px]">
|
? "bg-brand-primary text-black"
|
||||||
<span className="text-muted-foreground">Potential win:</span>
|
: "bg-brand-primary/20 text-brand-primary hover:bg-brand-primary/30"
|
||||||
<span className="text-primary font-semibold">
|
)}
|
||||||
{(Number(globalStake) * totalOdds).toFixed(2)} ETB
|
>
|
||||||
</span>
|
{s}
|
||||||
</div>
|
</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>
|
||||||
</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>
|
||||||
</button>
|
</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>
|
</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,58 +48,90 @@ 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) => {
|
||||||
<div
|
const eventName = `${match.homeTeam} - ${match.awayTeam}`
|
||||||
key={match.id}
|
const leagueForBet = `Football - ${match.league}`
|
||||||
className="min-w-[280px] bg-[#222] border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
|
const outcomes = [
|
||||||
>
|
{ key: "1", label: "1", odds: match.odds.home },
|
||||||
{/* Top Label Ribbon */}
|
{ key: "x", label: "X", odds: match.odds.draw },
|
||||||
<div className="absolute top-0 right-0 w-12 h-12 overflow-hidden z-10 pointer-events-none">
|
{ key: "2", label: "2", odds: match.odds.away },
|
||||||
<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">
|
] as const
|
||||||
TOP
|
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>
|
</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>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -66,39 +66,41 @@ export function AuthModal({ open, defaultMode, onClose }: AuthModalProps) {
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="relative w-[420px] bg-brand-surface-light shadow-2xl animate-fade-in"
|
className="relative w-[280px] bg-brand-surface-light shadow-2xl animate-fade-in"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Close button */}
|
{/* Close button */}
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="absolute top-2 right-3 text-white/60 hover:text-white text-xl leading-none transition-colors z-10"
|
className="absolute top-1 right-2 text-white/60 hover:text-white text-lg leading-none transition-colors z-10"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="flex items-center justify-center py-5 border-b border-white/10 bg-brand-surface">
|
<div className="flex items-center justify-center py-4 border-b border-white/10 bg-brand-surface">
|
||||||
<Logo />
|
<div className="scale-75 origin-center">
|
||||||
|
<Logo />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-3">
|
||||||
<h2 className="text-sm font-black text-white uppercase tracking-widest">
|
<h2 className="text-[12px] font-black text-white uppercase tracking-widest">
|
||||||
{mode === "login" ? "LOGIN" : "REGISTER"}
|
{mode === "login" ? "LOGIN" : "REGISTER"}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Form */}
|
{/* Form */}
|
||||||
<div className="px-8 pb-6 space-y-4">
|
<div className="px-4 pb-6 space-y-4">
|
||||||
{/* Phone Number */}
|
{/* Phone Number */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[11px] font-semibold text-white/80 mb-1">
|
<label className="block text-[11px] font-semibold text-white/80 mb-1">
|
||||||
Phone Number
|
Phone Number
|
||||||
</label>
|
</label>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="flex items-center justify-center bg-white text-[#333] text-[12px] font-bold px-3 border border-gray-300 whitespace-nowrap select-none">
|
<span className="flex items-center justify-center bg-white text-[#333] text-[11px] font-bold px-1.5 border border-gray-300 whitespace-nowrap select-none">
|
||||||
ET +251
|
ET +251
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,18 @@ import { RightPanel } from "@/components/layout/right-panel"
|
||||||
import { SiteFooter } from "@/components/layout/site-footer"
|
import { SiteFooter } from "@/components/layout/site-footer"
|
||||||
import { AuthModal } from "@/components/layout/auth-modal"
|
import { AuthModal } from "@/components/layout/auth-modal"
|
||||||
import { MobileBottomNav } from "@/components/layout/mobile-bottom-nav"
|
import { MobileBottomNav } from "@/components/layout/mobile-bottom-nav"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
type AuthMode = "login" | "register"
|
type AuthMode = "login" | "register"
|
||||||
|
|
||||||
export default function LayoutClientWrapper({ children }: { children: React.ReactNode }) {
|
export default function LayoutClientWrapper({ children }: { children: React.ReactNode }) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isLivePage = pathname === "/live"
|
const isLivePage = pathname === "/live"
|
||||||
|
const isVirtualPage = pathname === "/virtual"
|
||||||
|
const isSpecialGamesPage = pathname === "/special-games"
|
||||||
|
|
||||||
|
const hideLeftSidebar = isLivePage || isVirtualPage || isSpecialGamesPage
|
||||||
|
const hideRightSidebar = isVirtualPage || isSpecialGamesPage // Show RightPanel on Live page
|
||||||
|
|
||||||
const [authOpen, setAuthOpen] = useState(false)
|
const [authOpen, setAuthOpen] = useState(false)
|
||||||
const [authMode, setAuthMode] = useState<AuthMode>("login")
|
const [authMode, setAuthMode] = useState<AuthMode>("login")
|
||||||
|
|
@ -24,23 +30,25 @@ export default function LayoutClientWrapper({ children }: { children: React.Reac
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col pb-14 md:pb-0">
|
<div className="flex min-h-screen flex-col pb-14 md:pb-0 overflow-x-hidden">
|
||||||
<SiteHeader
|
<SiteHeader
|
||||||
onLoginClick={() => openAuth("login")}
|
onLoginClick={() => openAuth("login")}
|
||||||
onRegisterClick={() => openAuth("register")}
|
onRegisterClick={() => openAuth("register")}
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-1 gap-0">
|
<div className="flex w-full flex-1 gap-0 min-w-0">
|
||||||
{/* Sidebar: hidden on mobile */}
|
{/* Sidebar: hidden on mobile and hidden on specific pages */}
|
||||||
{!isLivePage && (
|
{!hideLeftSidebar && (
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block shrink-0">
|
||||||
<SportsSidebar />
|
<SportsSidebar />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<main className="flex-1 min-w-0 px-2 py-3">{children}</main>
|
<main className={cn("flex-1 min-w-0 overflow-x-hidden", !hideLeftSidebar && "px-2 py-3")}>{children}</main>
|
||||||
{/* Right panel: hidden on mobile */}
|
{/* Right panel: completely off-flow on mobile/tablet; only from lg to match RightPanel */}
|
||||||
<div className="hidden md:block">
|
{!hideRightSidebar && (
|
||||||
<RightPanel />
|
<div className="hidden lg:block shrink-0 overflow-hidden">
|
||||||
</div>
|
<RightPanel />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { Betslip } from "@/components/betting/betslip"
|
import { Betslip } from "@/components/betting/betslip"
|
||||||
import { ReloadTicket } from "@/components/betting/reload-ticket"
|
import { ReloadTicket } from "@/components/betting/reload-ticket"
|
||||||
import { CheckYourBet } from "@/components/betting/check-your-bet"
|
import { CheckYourBet } from "@/components/betting/check-your-bet"
|
||||||
|
import { useBetslipStore } from "@/lib/store/betslip-store"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
@ -10,14 +11,15 @@ import { Input } from "@/components/ui/input"
|
||||||
|
|
||||||
export function RightPanel() {
|
export function RightPanel() {
|
||||||
const [activeTab, setActiveTab] = useState<"betslip" | "myBets">("betslip")
|
const [activeTab, setActiveTab] = useState<"betslip" | "myBets">("betslip")
|
||||||
|
const bets = useBetslipStore((s) => s.bets)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="hidden lg:flex w-[280px] shrink-0 flex-col gap-0 border-l border-border/30 bg-[#222]">
|
<aside className="hidden lg:flex w-[280px] shrink-0 flex-col gap-0 border-l border-border/30 bg-brand-surface">
|
||||||
{/* Search Header */}
|
{/* Search Header */}
|
||||||
<div className="p-3 bg-[#1a1a1a] flex items-center justify-between border-b border-border/20">
|
<div className="p-3 bg-brand-bg flex items-center justify-between border-b border-border/20">
|
||||||
<div className="flex items-center gap-2 text-[12px] font-bold text-white uppercase">
|
<div className="flex items-center gap-2 text-[12px] font-bold text-white uppercase">
|
||||||
Fast Bet <span className="text-[#ff9800]">QBET</span>
|
Fast Bet <span className="text-brand-primary">QBET</span>
|
||||||
<svg viewBox="0 0 24 24" className="size-4 fill-[#ff9800] ml-1"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
<svg viewBox="0 0 24 24" className="size-4 fill-brand-primary ml-1"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -26,32 +28,28 @@ export function RightPanel() {
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Event Code"
|
placeholder="Event Code"
|
||||||
className="bg-[#333] border border-border/40 px-2 py-1.5 h-auto text-[11px] outline-none text-white focus:border-[#ff9800] rounded-none shadow-none"
|
className="bg-brand-surface-light border border-border/40 px-2 py-1.5 h-auto text-[11px] outline-none text-white focus:border-brand-primary rounded-none shadow-none"
|
||||||
/>
|
/>
|
||||||
<div className="bg-[#333] border border-border/40 px-2 py-1.5 text-[11px] text-muted-foreground flex items-center justify-center">...</div>
|
<div className="bg-brand-surface-light border border-border/40 px-2 py-1.5 text-[11px] text-muted-foreground flex items-center justify-center">...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab switcher */}
|
{/* Tab switcher */}
|
||||||
<div className="flex items-center justify-between text-[11px] font-bold py-2.5 px-1 border-b border-border/10">
|
<div className="flex items-center justify-between text-[11px] font-bold py-2.5 px-1 border-b border-border/10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[#ff9800] uppercase cursor-pointer">Betslip</span>
|
<span className="text-brand-primary uppercase cursor-pointer">Betslip</span>
|
||||||
<span className="bg-[#ff9800] text-black px-1.5 rounded-full text-[10px] font-bold">0</span>
|
<span className="bg-brand-primary text-black px-1.5 rounded-full text-[10px] font-bold min-w-[18px] text-center">{bets.length}</span>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="text-white uppercase flex items-center gap-1 ml-2 opacity-80 hover:opacity-100 p-0 h-auto font-bold text-[11px]"
|
className="text-white uppercase flex items-center gap-1 ml-2 opacity-80 hover:opacity-100 p-0 h-auto font-bold text-[11px]"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" className="size-3 fill-[#ff9800]"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
|
<svg viewBox="0 0 24 24" className="size-3 fill-brand-primary"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
|
||||||
Settings
|
Settings
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white uppercase cursor-pointer opacity-80 hover:opacity-100">Decimal</span>
|
<span className="text-white uppercase cursor-pointer opacity-80 hover:opacity-100">Decimal</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-10 px-4 text-center">
|
<Betslip />
|
||||||
<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>
|
|
||||||
|
|
||||||
<ReloadTicket />
|
<ReloadTicket />
|
||||||
<CheckYourBet />
|
<CheckYourBet />
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Link from "next/link"
|
||||||
|
|
||||||
export function SiteFooter() {
|
export function SiteFooter() {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-[#2a2a2a] text-white pt-12">
|
<footer className="bg-brand-surface text-white pt-12">
|
||||||
<div className="container mx-auto px-6 grid grid-cols-1 md:grid-cols-4 gap-12 text-center md:text-left">
|
<div className="container mx-auto px-6 grid grid-cols-1 md:grid-cols-4 gap-12 text-center md:text-left">
|
||||||
{/* ABOUT */}
|
{/* ABOUT */}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -51,12 +51,12 @@ export function SiteFooter() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Logo Section */}
|
{/* Logo Section */}
|
||||||
<div className="flex flex-col items-center justify-center py-16 border-t border-white/5 mt-12 bg-[#222]">
|
<div className="flex flex-col items-center justify-center py-16 border-t border-white/5 mt-12 bg-brand-surface-light">
|
||||||
<div className="flex items-center bg-[#1a1a1a] px-5 py-2">
|
<div className="flex items-center bg-brand-surface px-5 py-2">
|
||||||
<div className="bg-[#852222] px-3 py-1 -skew-x-12">
|
<div className="bg-brand-accent px-3 py-1 -skew-x-12">
|
||||||
<span className="text-3xl font-black text-white italic tracking-tighter skew-x-12 inline-block">HARIF</span>
|
<span className="text-3xl font-black text-white italic tracking-tighter skew-x-12 inline-block">HARIF</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-3xl font-black text-[#ff9800] italic tracking-tighter ml-1">SPORT</span>
|
<span className="text-3xl font-black text-brand-primary italic tracking-tighter ml-1">SPORT</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer Links */}
|
{/* Footer Links */}
|
||||||
|
|
@ -70,7 +70,7 @@ export function SiteFooter() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cookie Text */}
|
{/* Cookie Text */}
|
||||||
<div className="bg-[#1a1a1a] py-10 px-6 text-center">
|
<div className="bg-brand-bg py-10 px-6 text-center">
|
||||||
<div className="container mx-auto max-w-5xl">
|
<div className="container mx-auto max-w-5xl">
|
||||||
<p className="text-[10px] text-white/40 leading-relaxed font-medium uppercase tracking-tight">
|
<p className="text-[10px] text-white/40 leading-relaxed font-medium uppercase tracking-tight">
|
||||||
By accessing, or continuing to use or browse this site, you consent to our use of certain cookies to improve your experience with us. We only use cookies that will enhance your experience and will not interfere with your privacy. Please look at our Cookie Policy for further informations on our use of the cookie and how you can disable it or manage it if you so choose.
|
By accessing, or continuing to use or browse this site, you consent to our use of certain cookies to improve your experience with us. We only use cookies that will enhance your experience and will not interfere with your privacy. Please look at our Cookie Policy for further informations on our use of the cookie and how you can disable it or manage it if you so choose.
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import { useState, useEffect } from "react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
const allNavItems = [
|
const allNavItems = [
|
||||||
{ href: "/", label: "SPORTS" },
|
{ href: "/", label: "ALL SPORTS" },
|
||||||
{ href: "/today", label: "TODAY" },
|
|
||||||
{ href: "/live", label: "LIVE" },
|
{ href: "/live", label: "LIVE" },
|
||||||
{ href: "/virtual", label: "VIRTUAL" },
|
{ href: "/virtual", label: "VIRTUAL" },
|
||||||
{ href: "/special-games", label: "SPECIAL G." },
|
{ href: "/special-games", label: "SPECIAL Games" },
|
||||||
{ href: "/multi-hot-5", label: "MULTI HOT 5" },
|
{ href: "/multi-hot-5", label: "MULTI HOT 5" },
|
||||||
{ href: "/poker", label: "POKER", isNew: true },
|
{ href: "/poker", label: "POKER", isNew: true },
|
||||||
{ href: "/race", label: "RACE", isNew: true },
|
{ href: "/race", label: "RACE", isNew: true },
|
||||||
|
|
@ -68,7 +68,14 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
<div className="hidden md:flex bg-brand-surface px-3 py-1 items-center justify-between text-[11px] text-white">
|
<div className="hidden md:flex bg-brand-surface px-3 py-1 items-center justify-between text-[11px] text-white">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button className="flex items-center gap-1.5 hover:text-primary transition-colors">
|
<button className="flex items-center gap-1.5 hover:text-primary transition-colors">
|
||||||
<img src="https://flagcdn.com/w20/gb.png" width="16" alt="English" className="rounded-sm" />
|
<Image
|
||||||
|
src="https://flagcdn.com/w20/gb.png"
|
||||||
|
alt="English"
|
||||||
|
width={20}
|
||||||
|
height={15}
|
||||||
|
className="rounded-sm"
|
||||||
|
unoptimized // Since it's an external image from a CDN
|
||||||
|
/>
|
||||||
<span className="font-bold flex items-center gap-1">en <span className="text-[8px]">▼</span></span>
|
<span className="font-bold flex items-center gap-1">en <span className="text-[8px]">▼</span></span>
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2 font-bold">
|
<div className="flex items-center gap-2 font-bold">
|
||||||
|
|
@ -78,14 +85,16 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-white font-bold tracking-tight">+251 (0) Number</span>
|
<span className="text-white font-bold tracking-tight">+251 (0)
|
||||||
|
<Input type="tel" placeholder="Phone Number" className="bg-brand-surface-light border-none px-2 py-0.5 w-32 text-[11px] h-7 rounded-none shadow-none text-white placeholder:text-gray-500" />
|
||||||
|
</span>
|
||||||
<Input type="password" placeholder="Password" className="bg-brand-surface-light border-none px-2 py-0.5 w-32 text-[11px] h-7 rounded-none shadow-none text-white placeholder:text-gray-500" />
|
<Input type="password" placeholder="Password" className="bg-brand-surface-light border-none px-2 py-0.5 w-32 text-[11px] h-7 rounded-none shadow-none text-white placeholder:text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button onClick={onLoginClick} className="bg-[#e67e22] text-white hover:bg-[#d35400] px-5 h-7 text-[11px] font-bold rounded-none uppercase">
|
<Button onClick={onLoginClick} className="bg-brand-primary text-white hover:bg-brand-primary-hover px-5 h-7 text-[11px] font-bold rounded-none uppercase">
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onRegisterClick} className="bg-[#d35400] text-white hover:bg-[#c0392b] px-5 h-7 text-[11px] font-bold rounded-none uppercase">
|
<Button onClick={onRegisterClick} className="bg-brand-primary text-white hover:bg-brand-primary-hover px-5 h-7 text-[11px] font-bold rounded-none uppercase">
|
||||||
Sign Up
|
Sign Up
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -119,13 +128,13 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={onLoginClick}
|
onClick={onLoginClick}
|
||||||
className="bg-[#e67e22] text-white px-3 h-8 text-[11px] font-bold uppercase"
|
className="bg-brand-primary text-white px-3 h-8 text-[11px] font-bold uppercase"
|
||||||
>
|
>
|
||||||
LOGIN
|
LOGIN
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onRegisterClick}
|
onClick={onRegisterClick}
|
||||||
className="bg-[#d35400] text-white px-3 h-8 text-[11px] font-bold uppercase"
|
className="bg-brand-primary text-white px-3 h-8 text-[11px] font-bold uppercase"
|
||||||
>
|
>
|
||||||
SIGN UP
|
SIGN UP
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -133,30 +142,38 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ===== DESKTOP: Main header bar ===== */}
|
{/* ===== DESKTOP: Main header bar ===== */}
|
||||||
<div className="hidden md:flex items-center px-0 bg-[#333] h-[60px]">
|
<div className="hidden md:flex items-center px-0 bg-brand-bg h-[60px]">
|
||||||
<Link href="/" className="flex items-center shrink-0">
|
<Link href="/" className="flex items-center shrink-0">
|
||||||
<div className="flex items-center bg-brand-surface h-[60px] px-4">
|
<div className="flex items-center bg-brand-surface h-[60px] px-4 w-[280px] shrink-0 border-r border-white/5">
|
||||||
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[34px]">
|
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[34px]">
|
||||||
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">HARIF</span>
|
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">HARIF</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">SPORT</span>
|
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">SPORT</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center flex-1 justify-end px-4 h-full gap-0">
|
<div className="flex items-center flex-1 justify-end px-4 h-full gap-0 bg-brand-surface">
|
||||||
<Link href="/" className="flex items-center justify-center bg-brand-primary h-[60px] w-[50px] shrink-0 hover:bg-brand-primary-hover transition-colors">
|
<Link
|
||||||
<svg viewBox="0 0 24 24" className="size-6 fill-black"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
href="/"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center h-[60px] w-[50px] shrink-0 transition-colors",
|
||||||
|
pathname === "/"
|
||||||
|
? "bg-brand-primary hover:bg-brand-primary-hover"
|
||||||
|
: "text-white hover:bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" className={cn("size-6", pathname === "/" ? "fill-black" : "fill-white")}><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="flex items-center text-[10.5px] font-bold h-full">
|
<nav className="flex items-center text-[10.5px] font-bold h-full">
|
||||||
{allNavItems.slice(0, 5).map((item) => {
|
{allNavItems.slice(0, 5).map((item) => {
|
||||||
const isActive = pathname === item.href
|
const isActive = pathname === item.href && item.href !== "/"
|
||||||
return (
|
return (
|
||||||
<Link key={item.href} href={item.href}
|
<Link key={item.href} href={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 flex items-center h-full transition-colors uppercase",
|
"px-4 flex items-center h-full transition-colors uppercase relative",
|
||||||
isActive ? (item.label === "LIVE" ? "bg-brand-primary text-black" : "text-primary bg-black/10") : "text-white hover:text-primary"
|
isActive ? "text-white bg-brand-primary" : "text-white hover:bg-white/5"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.label}
|
<span>{item.label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
@ -168,11 +185,11 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
<Link key={item.href} href={item.href}
|
<Link key={item.href} href={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 flex flex-col items-center justify-center h-full transition-colors relative uppercase",
|
"px-4 flex flex-col items-center justify-center h-full transition-colors relative uppercase",
|
||||||
isActive ? "text-primary bg-black/10" : "text-white hover:text-primary"
|
isActive ? "text-white bg-brand-primary" : "text-white hover:bg-white/5"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.isNew && (
|
{item.isNew && (
|
||||||
<span className="absolute top-3 text-[7px] text-primary font-black tracking-tighter leading-none">NEW</span>
|
<span className="absolute top-3 text-[7px] text-white/70 font-black tracking-tighter leading-none">NEW</span>
|
||||||
)}
|
)}
|
||||||
<span className={cn(item.isNew && "mt-1.5")}>{item.label}</span>
|
<span className={cn(item.isNew && "mt-1.5")}>{item.label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -184,8 +201,14 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
|
|
||||||
{/* ===== MOBILE: Horizontally scrollable nav tabs ===== */}
|
{/* ===== MOBILE: Horizontally scrollable nav tabs ===== */}
|
||||||
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-brand-surface-light border-t border-white/5">
|
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-brand-surface-light border-t border-white/5">
|
||||||
<Link href="/" className="flex-none px-4 h-9 flex items-center bg-brand-primary">
|
<Link
|
||||||
<svg viewBox="0 0 24 24" className="size-5 fill-black"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
href="/"
|
||||||
|
className={cn(
|
||||||
|
"flex-none px-4 h-9 flex items-center transition-colors",
|
||||||
|
pathname === "/" ? "bg-brand-primary" : "text-white"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" className={cn("size-5", pathname === "/" ? "fill-black" : "fill-white")}><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
||||||
</Link>
|
</Link>
|
||||||
{allNavItems.map((item) => {
|
{allNavItems.map((item) => {
|
||||||
const isActive = pathname === item.href
|
const isActive = pathname === item.href
|
||||||
|
|
@ -210,7 +233,7 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ===== MOBILE: Sport Category Icons Row ===== */}
|
{/* ===== MOBILE: Sport Category Icons Row ===== */}
|
||||||
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-[#333] border-t border-white/5 py-2 px-2 gap-4">
|
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-brand-surface-light border-t border-white/5 py-2 px-2 gap-4">
|
||||||
{[
|
{[
|
||||||
{ label: "Check Bet", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M20 4H4c-1.11 0-2 .89-2 2v12c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/></svg>, count: 99 },
|
{ label: "Check Bet", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M20 4H4c-1.11 0-2 .89-2 2v12c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/></svg>, count: 99 },
|
||||||
{ label: "Live", icon: (active: boolean) => <div className="size-2 bg-brand-live rounded-full animate-pulse" />, count: 1247 },
|
{ label: "Live", icon: (active: boolean) => <div className="size-2 bg-brand-live rounded-full animate-pulse" />, count: 1247 },
|
||||||
|
|
@ -229,28 +252,29 @@ export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ===== DESKTOP: Secondary sub-header ===== */}
|
{/* ===== DESKTOP: Secondary sub-header ===== */}
|
||||||
<div className="hidden md:flex bg-brand-surface-light border-t border-white/5 h-8 px-3 items-center gap-6 text-[11px]">
|
{pathname !== "/virtual" && pathname !== "/special-games" && (
|
||||||
{[
|
<div className="hidden md:flex bg-brand-surface-light border-t border-white/5 h-8 px-3 items-center gap-6 text-[11px]">
|
||||||
{ label: "Sport Home", href: "/" },
|
{[
|
||||||
{ label: "General View", href: "/live", forceActive: isLivePage },
|
{ label: "Sport Home", href: "/" },
|
||||||
{ label: "Event View", href: "/live/event" },
|
{ label: "Live View", href: "/live", forceActive: isLivePage },
|
||||||
].map((tab) => {
|
].map((tab) => {
|
||||||
const isActive = tab.forceActive || pathname === tab.href
|
const isActive = tab.forceActive || pathname === tab.href
|
||||||
return (
|
return (
|
||||||
<Link key={tab.label} href={tab.href}
|
<Link key={tab.label} href={tab.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative h-full flex items-center text-[10px] font-bold uppercase transition-colors tracking-tight px-1",
|
"relative h-full flex items-center text-[10px] font-bold uppercase transition-colors tracking-tight px-1",
|
||||||
isActive ? "text-brand-primary" : "text-white/60 hover:text-white"
|
isActive ? "text-brand-primary" : "text-white/60 hover:text-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
{isActive && tab.label !== "Sport Home" && (
|
{isActive && tab.label !== "Sport Home" && (
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-brand-primary" />
|
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-brand-primary" />
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* ===== MOBILE Drawer ===== */}
|
{/* ===== MOBILE Drawer ===== */}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,18 @@ import Link from "next/link"
|
||||||
import { popularLeagues } from "@/lib/mock-data"
|
import { popularLeagues } from "@/lib/mock-data"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { ChevronsLeft } from "lucide-react"
|
||||||
|
|
||||||
|
/** Soccer ball icon - outline style for white/green theme */
|
||||||
|
function SoccerBallIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" className={className} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 2v20M2 12h20" />
|
||||||
|
<path d="M4.93 4.93l14.14 14.14M4.93 19.07l14.14-14.14" strokeWidth="1" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const sportCategories = [
|
const sportCategories = [
|
||||||
{ id: "football", name: "Football", icon: "⚽", count: 1412 },
|
{ id: "football", name: "Football", icon: "⚽", count: 1412 },
|
||||||
|
|
@ -25,11 +37,13 @@ export function SportsSidebar() {
|
||||||
const [activeSport, setActiveSport] = useState("football")
|
const [activeSport, setActiveSport] = useState("football")
|
||||||
|
|
||||||
return (
|
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">
|
<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">
|
||||||
{/* Sports Menu Header */}
|
{/* Sports Menu Header */}
|
||||||
<div className="bg-brand-surface px-3 py-2 text-[11px] font-black text-brand-primary uppercase tracking-tighter flex items-center justify-between border-b border-border/30">
|
<div className="bg-brand-surface px-3 py-2 text-[11px] font-black text-white uppercase tracking-tighter flex items-center justify-between border-b border-border/30 h-10">
|
||||||
Sports Menu
|
<span>Sports Menu</span>
|
||||||
<svg viewBox="0 0 24 24" className="size-3.5 fill-current opacity-60"><path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/></svg>
|
<button type="button" className="p-1 rounded hover:bg-white/10 text-white/70 hover:text-white transition-colors" aria-label="Collapse sidebar">
|
||||||
|
<ChevronsLeft className="size-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Leagues Header */}
|
{/* Top Leagues Header */}
|
||||||
|
|
@ -45,27 +59,28 @@ export function SportsSidebar() {
|
||||||
href={`/?league=${league.id}`}
|
href={`/?league=${league.id}`}
|
||||||
className="w-full flex items-center justify-between px-3 py-2 text-left text-white/90 hover:bg-brand-surface transition-colors border-b border-border/10 group h-9"
|
className="w-full flex items-center justify-between px-3 py-2 text-left text-white/90 hover:bg-brand-surface transition-colors border-b border-border/10 group h-9"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<div className="size-4 shrink-0 overflow-hidden rounded-sm flex items-center justify-center bg-white/10 group-hover:bg-white/20 transition-colors">
|
<div className="size-5 shrink-0 overflow-hidden rounded-sm flex items-center justify-center bg-white/5 border border-white/10 group-hover:border-white/20 transition-colors">
|
||||||
{league.logo ? (
|
{league.logo ? (
|
||||||
<img src={league.logo} alt="" className="size-full object-contain" />
|
<img src={league.logo} alt="" className="size-full object-contain" />
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[10px] leading-none invert">⚽</span>
|
<span className="text-[11px]">⚽</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10.5px] font-bold leading-tight truncate max-w-[150px]">{league.name}</span>
|
<span className="text-white/50 text-[8px] font-bold select-none">•</span>
|
||||||
</div>
|
<span className="text-[10.5px] font-bold leading-tight truncate max-w-[140px]">{league.name}</span>
|
||||||
<div className="size-3.5 rounded-full border border-white/20 flex items-center justify-center group-hover:border-brand-primary transition-colors shrink-0">
|
|
||||||
<div className="size-1.5 bg-white/40 rounded-full group-hover:bg-brand-primary" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<SoccerBallIcon className="size-4 shrink-0 text-white/40 group-hover:text-brand-primary transition-colors" />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* In-Play Strip */}
|
{/* In-Play Strip */}
|
||||||
<Button className="w-full bg-[#004d40] text-[#00bfa5] hover:bg-[#003d33] border-none py-2.5 h-auto text-[11px] font-black uppercase rounded-none tracking-[2px]">
|
<Button asChild className="w-full bg-[#004d40] text-[#00bfa5] hover:bg-[#003d33] border-none py-2.5 h-auto text-[11px] font-black uppercase rounded-none tracking-[2px]">
|
||||||
<span className="size-2 rounded-full bg-[#ff9800] mr-2 live-dot shadow-[0_0_8px_#ff9800]"></span>
|
<Link href="/live">
|
||||||
|
<span className="size-2 rounded-full bg-brand-primary mr-2 live-dot shadow-[0_0_8px_var(--brand-primary)]"></span>
|
||||||
IN-PLAY
|
IN-PLAY
|
||||||
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Quick Filter Section */}
|
{/* Quick Filter Section */}
|
||||||
|
|
@ -75,7 +90,7 @@ export function SportsSidebar() {
|
||||||
{["All", "Today", "3h", "6h", "9h", "12h"].map((t) => (
|
{["All", "Today", "3h", "6h", "9h", "12h"].map((t) => (
|
||||||
<button key={t} className={cn(
|
<button key={t} className={cn(
|
||||||
"text-[10px] py-1.5 font-bold transition-colors",
|
"text-[10px] py-1.5 font-bold transition-colors",
|
||||||
t === "All" ? "bg-[#333] text-white" : "bg-[#2a2a2a] text-white/50 hover:text-white"
|
t === "All" ? "bg-brand-surface-light text-white" : "bg-brand-surface text-white/50 hover:text-white"
|
||||||
)}>{t}</button>
|
)}>{t}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -112,7 +127,7 @@ export function SportsSidebar() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[10px] font-bold text-white/40">{sport.count}</span>
|
<span className="text-[10px] font-bold text-white/40">{sport.count}</span>
|
||||||
<span className="text-[11px] text-white/20">⚽</span>
|
<SoccerBallIcon className="size-3.5 text-white/30 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
@ -123,17 +138,49 @@ export function SportsSidebar() {
|
||||||
Bet Services
|
Bet Services
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-0 border-b border-border/10">
|
<div className="grid grid-cols-3 gap-0 border-b border-border/10">
|
||||||
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
type="button"
|
||||||
|
className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group"
|
||||||
|
onClick={() => {
|
||||||
|
const w = 1200
|
||||||
|
const h = 800
|
||||||
|
const left = typeof window !== "undefined" ? Math.max(0, (window.screen.width - w) / 2) : 0
|
||||||
|
const top = typeof window !== "undefined" ? Math.max(0, (window.screen.height - h) / 2) : 0
|
||||||
|
window.open(
|
||||||
|
"https://s5.sir.sportradar.com/betinaction/en",
|
||||||
|
"LiveScore",
|
||||||
|
`noopener,noreferrer,width=${w},height=${h},left=${left},top=${top},scrollbars=yes,resizable=yes`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm3.3 14.71L11 12.41V7h2v4.59l3.71 3.71-1.42 1.41z"/></svg>
|
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm3.3 14.71L11 12.41V7h2v4.59l3.71 3.71-1.42 1.41z"/></svg>
|
||||||
<span className="text-[9px] text-white font-medium">Live Score</span>
|
<span className="text-[9px] text-white font-medium">Live Score</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
type="button"
|
||||||
|
className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group"
|
||||||
|
onClick={() => {
|
||||||
|
const w = 1200
|
||||||
|
const h = 800
|
||||||
|
const left = typeof window !== "undefined" ? Math.max(0, (window.screen.width - w) / 2) : 0
|
||||||
|
const top = typeof window !== "undefined" ? Math.max(0, (window.screen.height - h) / 2) : 0
|
||||||
|
window.open(
|
||||||
|
"https://statistics.betconstruct.com/#/en",
|
||||||
|
"Results",
|
||||||
|
`noopener,noreferrer,width=${w},height=${h},left=${left},top=${top},scrollbars=yes,resizable=yes`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z"/></svg>
|
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z"/></svg>
|
||||||
<span className="text-[9px] text-white font-medium">Results</span>
|
<span className="text-[9px] text-white font-medium">Results</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none hover:bg-brand-surface group">
|
<Button variant="ghost" asChild className="flex flex-col items-center justify-center py-4 h-auto rounded-none hover:bg-brand-surface group">
|
||||||
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"/></svg>
|
<Link href="/print-odds" className="flex flex-col items-center justify-center">
|
||||||
<span className="text-[9px] text-white font-medium">Print Odds</span>
|
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"/></svg>
|
||||||
|
<span className="text-[9px] text-white font-medium">Print Odds</span>
|
||||||
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ const buttonVariants = cva(
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
"hs-primary": "bg-[#ff9800] text-black font-bold hover:bg-[#ffa726] rounded-none",
|
"hs-primary": "bg-brand-primary text-black font-bold hover:bg-brand-primary-hover rounded-none",
|
||||||
"hs-secondary": "bg-[#333] text-white hover:bg-[#444] border border-border/20 rounded-none",
|
"hs-secondary": "bg-brand-surface-light text-white hover:bg-[#404040] border border-border/20 rounded-none",
|
||||||
"hs-maroon": "bg-[#852222] text-white font-bold hover:bg-[#962d2d] rounded-none",
|
"hs-maroon": "bg-brand-accent text-white font-bold hover:opacity-90 rounded-none",
|
||||||
"hs-inplay": "bg-[#004242] text-[#ff9800] font-bold hover:bg-[#005252] border-y border-border/10 rounded-none",
|
"hs-inplay": "bg-[#004242] text-brand-primary font-bold hover:bg-[#005252] border-y border-border/10 rounded-none",
|
||||||
"hs-nav": "bg-[#1a1a1a] text-[#ff9800] font-bold hover:bg-[#222] border-r border-border/10 rounded-none",
|
"hs-nav": "bg-brand-bg text-brand-primary font-bold hover:bg-brand-surface border-r border-border/10 rounded-none",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { Tabs as TabsPrimitive } from "radix-ui"
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Tabs({
|
function Tabs({
|
||||||
className,
|
className,
|
||||||
|
|
@ -18,29 +18,30 @@ function Tabs({
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabsListVariants = cva(
|
const tabsListVariants = cva(
|
||||||
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none data-[variant=hs-home]:rounded-none data-[variant=hs-home]:bg-[#1a1a1a] data-[variant=hs-nav]:rounded-none data-[variant=hs-nav]:bg-[#1a1a1a] group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none data-[variant=hs-home]:rounded-none data-[variant=hs-home]:bg-brand-bg data-[variant=hs-nav]:rounded-none data-[variant=hs-nav]:bg-brand-bg group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-muted",
|
default: "bg-muted",
|
||||||
line: "gap-1 bg-transparent",
|
line: "gap-1 bg-transparent",
|
||||||
"hs-home": "w-full gap-0 border border-border/20",
|
"hs-home": "w-full gap-0 border border-border/20",
|
||||||
"hs-nav": "w-full gap-0 border border-border/20 overflow-x-auto justify-start",
|
"hs-nav":
|
||||||
|
"w-full gap-0 border border-border/20 flex-wrap justify-start overflow-hidden min-h-14 h-auto py-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function TabsList({
|
function TabsList({
|
||||||
className,
|
className,
|
||||||
|
|
@ -55,7 +56,7 @@ function TabsList({
|
||||||
className={cn(tabsListVariants({ variant }), className)}
|
className={cn(tabsListVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsTrigger({
|
function TabsTrigger({
|
||||||
|
|
@ -69,14 +70,14 @@ function TabsTrigger({
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
||||||
"group-data-[variant=hs-home]/tabs-list:rounded-none group-data-[variant=hs-home]/tabs-list:h-12 group-data-[variant=hs-home]/tabs-list:text-[13px] group-data-[variant=hs-home]/tabs-list:font-extrabold group-data-[variant=hs-home]/tabs-list:uppercase group-data-[variant=hs-home]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=hs-home]/tabs-list:data-[state=active]:text-white",
|
"group-data-[variant=hs-home]/tabs-list:rounded-none group-data-[variant=hs-home]/tabs-list:h-12 group-data-[variant=hs-home]/tabs-list:text-[13px] group-data-[variant=hs-home]/tabs-list:font-extrabold group-data-[variant=hs-home]/tabs-list:uppercase group-data-[variant=hs-home]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=hs-home]/tabs-list:data-[state=active]:text-white",
|
||||||
"group-data-[variant=hs-nav]/tabs-list:rounded-none group-data-[variant=hs-nav]/tabs-list:flex-col group-data-[variant=hs-nav]/tabs-list:min-w-[70px] group-data-[variant=hs-nav]/tabs-list:py-2 group-data-[variant=hs-nav]/tabs-list:gap-1 group-data-[variant=hs-nav]/tabs-list:border-r group-data-[variant=hs-nav]/tabs-list:border-border/10 group-data-[variant=hs-nav]/tabs-list:data-[state=active]:bg-[#222]",
|
"group-data-[variant=hs-nav]/tabs-list:rounded-none group-data-[variant=hs-nav]/tabs-list:flex-col group-data-[variant=hs-nav]/tabs-list:min-w-[70px] group-data-[variant=hs-nav]/tabs-list:py-2 group-data-[variant=hs-nav]/tabs-list:gap-1 group-data-[variant=hs-nav]/tabs-list:border-r group-data-[variant=hs-nav]/tabs-list:border-border/10 group-data-[variant=hs-nav]/tabs-list:data-[state=active]:bg-brand-surface",
|
||||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
|
||||||
"after:bg-[#ff9800] after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-0 group-data-[orientation=horizontal]/tabs:after:h-[3px] group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100 group-data-[variant=hs-home]/tabs-list:data-[state=active]:after:opacity-100",
|
"after:bg-brand-primary after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-0 group-data-[orientation=horizontal]/tabs:after:h-[3px] group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100 group-data-[variant=hs-home]/tabs-list:data-[state=active]:after:opacity-100",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsContent({
|
function TabsContent({
|
||||||
|
|
@ -89,7 +90,7 @@ function TabsContent({
|
||||||
className={cn("flex-1 outline-none", className)}
|
className={cn("flex-1 outline-none", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
||||||
|
|
|
||||||
47
lib/api.ts
Normal file
47
lib/api.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Create a configured Axios instance
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080/api/v1',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request interceptor for adding the auth token
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// Only access localStorage if we are running in the browser
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// const token = localStorage.getItem('token');
|
||||||
|
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb3J0dW5lLWJldCIsImF1ZCI6WyJhcGkuZm9ydHVuZWJldHMubmV0Il0sImV4cCI6MTc3MjI3NzQxNSwibmJmIjoxNzcyMjc2ODE1LCJpYXQiOjE3NzIyNzY4MTUsIlVzZXJJZCI6NCwiUm9sZSI6InN1cGVyX2FkbWluIiwiQ29tcGFueUlEIjp7IlZhbHVlIjowLCJWYWxpZCI6ZmFsc2V9fQ.QJJ1KAFkWWCMmxxBi8rQc9C5aChN2XmTys-RCufV_Zo";
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Response interceptor for handling common errors (like 401 Unauthorized)
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
// Handle unauthorized errors, e.g., redirecting to login or clearing the token
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
// Uncomment the line below to redirect automatically
|
||||||
|
// window.location.href = '/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default api;
|
||||||
144
lib/mock-data.ts
144
lib/mock-data.ts
|
|
@ -220,6 +220,31 @@ export const mockEvents: Event[] = [
|
||||||
],
|
],
|
||||||
totalMarkets: 110,
|
totalMarkets: 110,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "01890",
|
||||||
|
sport: "Soccer",
|
||||||
|
sportIcon: "⚽",
|
||||||
|
league: "Premier League",
|
||||||
|
country: "England",
|
||||||
|
homeTeam: "Burnley",
|
||||||
|
awayTeam: "Brentford",
|
||||||
|
time: "05:00",
|
||||||
|
date: "Feb 28, 26",
|
||||||
|
isLive: false,
|
||||||
|
markets: [
|
||||||
|
{ id: "1", label: "1", odds: 4.58 },
|
||||||
|
{ id: "x", label: "x", odds: 4.00 },
|
||||||
|
{ id: "2", label: "2", odds: 1.78 },
|
||||||
|
{ id: "o25", label: "Over (2.5)", odds: 1.80 },
|
||||||
|
{ id: "u25", label: "Under (2.5)", odds: 2.05 },
|
||||||
|
{ id: "1x", label: "1X", odds: 2.10 },
|
||||||
|
{ id: "12", label: "12", odds: 1.25 },
|
||||||
|
{ id: "x2", label: "X2", odds: 1.20 },
|
||||||
|
{ id: "yes", label: "Yes", odds: 1.71 },
|
||||||
|
{ id: "no", label: "No", odds: 2.15 },
|
||||||
|
],
|
||||||
|
totalMarkets: 112,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "01124",
|
id: "01124",
|
||||||
sport: "Soccer",
|
sport: "Soccer",
|
||||||
|
|
@ -247,6 +272,125 @@ export const mockEvents: Event[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function getEventById(id: string): Event | undefined {
|
||||||
|
return mockEvents.find((e) => e.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DetailMarketOutcome = { label: string; odds: number }
|
||||||
|
export type DetailMarketSection = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
outcomes: DetailMarketOutcome[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEventDetailMarkets(eventId: string): DetailMarketSection[] {
|
||||||
|
const event = getEventById(eventId)
|
||||||
|
if (!event) return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "1x2",
|
||||||
|
title: "1x2",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Home", odds: 4.58 },
|
||||||
|
{ label: "Draw", odds: 4.0 },
|
||||||
|
{ label: "Away", odds: 1.78 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "total",
|
||||||
|
title: "Total",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Over (2.5)", odds: 1.8 },
|
||||||
|
{ label: "Under (2.5)", odds: 2.05 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "draw-no-bet",
|
||||||
|
title: "Draw No Bet",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Home", odds: 3.25 },
|
||||||
|
{ label: "Away", odds: 1.35 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "asian-handicap",
|
||||||
|
title: "Asian Handicap 0.5",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Home (+0.5)", odds: 2.1 },
|
||||||
|
{ label: "Away (-0.5)", odds: 1.76 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "double-chance",
|
||||||
|
title: "Double Chance",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Home or Draw", odds: 1.96 },
|
||||||
|
{ label: "Home or Away", odds: 1.25 },
|
||||||
|
{ label: "Draw or Away", odds: 1.2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "btts",
|
||||||
|
title: "Both teams to score",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Yes", odds: 1.71 },
|
||||||
|
{ label: "No", odds: 2.15 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "1st-half-1x2",
|
||||||
|
title: "1st Half - 1X2",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Home", odds: 4.8 },
|
||||||
|
{ label: "Draw", odds: 2.4 },
|
||||||
|
{ label: "Away", odds: 2.3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cards/Bookings markets: left column and right column of sections */
|
||||||
|
export function getCardsBookingsMarkets(eventId: string): {
|
||||||
|
left: DetailMarketSection[]
|
||||||
|
right: DetailMarketSection[]
|
||||||
|
} {
|
||||||
|
getEventById(eventId) // ensure event exists
|
||||||
|
return {
|
||||||
|
left: [
|
||||||
|
{ id: "bookings-1x2", title: "Bookings 1X2", outcomes: [{ label: "Home", odds: 2.2 }, { label: "Draw", odds: 3.7 }, { label: "Away", odds: 2.5 }] },
|
||||||
|
{ id: "bookings-ou", title: "Bookings - Over/Under", outcomes: [] },
|
||||||
|
{ id: "sending-off", title: "Sending Off", outcomes: [{ label: "Yes", odds: 5.4 }, { label: "No", odds: 1.12 }] },
|
||||||
|
{ id: "1st-half-bookings-ou", title: "1st Half Bookings - Over/Under", outcomes: [] },
|
||||||
|
{ id: "1st-booking", title: "1st Booking", outcomes: [{ label: "Home", odds: 1.79 }, { label: "None", odds: 28.11 }, { label: "Away", odds: 1.9 }] },
|
||||||
|
{ id: "home-exact-bookings", title: "Home Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "1st-half-bookings-1x2", title: "1st Half - Bookings 1X2", outcomes: [{ label: "Home", odds: 2.95 }, { label: "Draw", odds: 2.15 }, { label: "Away", odds: 3.2 }] },
|
||||||
|
{ id: "1st-half-away-exact", title: "1st Half - Away Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "away-sending-off", title: "Away Sending Off", outcomes: [] },
|
||||||
|
{ id: "1st-half-home-sending", title: "1st Half - Home Sending Off", outcomes: [] },
|
||||||
|
],
|
||||||
|
right: [
|
||||||
|
{
|
||||||
|
id: "booking-points-ou",
|
||||||
|
title: "Booking Points-Over/Under",
|
||||||
|
outcomes: [
|
||||||
|
{ label: "Over 35.5", odds: 1.85 }, { label: "Under 35.5", odds: 1.85 },
|
||||||
|
{ label: "Over 45.5", odds: 2.85 }, { label: "Under 45.5", odds: 1.38 },
|
||||||
|
{ label: "Over 55.5", odds: 4.6 }, { label: "Under 55.5", odds: 1.16 },
|
||||||
|
{ label: "Over 15.5", odds: 1.1 }, { label: "Under 15.5", odds: 5.8 },
|
||||||
|
{ label: "Over 25.5", odds: 1.35 }, { label: "Under 25.5", odds: 2.95 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: "exact-bookings", title: "Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "1st-half-total-points", title: "1st Half - Total Booking Points", outcomes: [] },
|
||||||
|
{ id: "1st-half-exact-bookings", title: "1st Half - Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "1st-half-1st-booking", title: "1st Half - 1st Booking", outcomes: [{ label: "Home", odds: 2.5 }, { label: "None", odds: 2.95 }, { label: "Away", odds: 2.65 }] },
|
||||||
|
{ id: "away-exact-bookings", title: "Away Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "1st-half-home-exact", title: "1st Half - Home Exact Bookings", outcomes: [] },
|
||||||
|
{ id: "home-sending-off", title: "Home Sending Off", outcomes: [] },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const popularLeagues = [
|
export const popularLeagues = [
|
||||||
{ id: "ucl", name: "UEFA Champions League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/b/bf/UEFA_Champions_League_logo_2.svg/300px-UEFA_Champions_League_logo_2.svg.png" },
|
{ id: "ucl", name: "UEFA Champions League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/b/bf/UEFA_Champions_League_logo_2.svg/300px-UEFA_Champions_League_logo_2.svg.png" },
|
||||||
{ id: "uel", name: "UEFA Europa League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/UEFA_Europa_League_logo_%282021%29.svg/300px-UEFA_Europa_League_logo_%282021%29.svg.png" },
|
{ id: "uel", name: "UEFA Europa League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/UEFA_Europa_League_logo_%282021%29.svg/300px-UEFA_Europa_League_logo_%282021%29.svg.png" },
|
||||||
|
|
|
||||||
107
package-lock.json
generated
107
package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
||||||
"name": "fortune-play",
|
"name": "fortune-play",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.13.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.574.0",
|
"lucide-react": "^0.574.0",
|
||||||
|
|
@ -4827,6 +4828,11 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
|
|
@ -4853,6 +4859,16 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||||
|
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.11",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
|
|
@ -5011,7 +5027,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -5256,6 +5271,17 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "14.0.3",
|
"version": "14.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||||
|
|
@ -5604,6 +5630,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
|
@ -5670,7 +5704,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
|
@ -5837,7 +5870,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -5847,7 +5879,6 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -5885,7 +5916,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
|
|
@ -5898,7 +5928,6 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -6742,6 +6771,25 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
|
|
@ -6758,6 +6806,40 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/formdata-polyfill": {
|
"node_modules/formdata-polyfill": {
|
||||||
"version": "4.0.10",
|
"version": "4.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||||
|
|
@ -6810,7 +6892,6 @@
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
|
@ -6908,7 +6989,6 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
|
@ -6955,7 +7035,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
|
|
@ -7060,7 +7139,6 @@
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -7142,7 +7220,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -7155,7 +7232,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
|
|
@ -7171,7 +7247,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
|
|
@ -8539,7 +8614,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -9555,6 +9629,11 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.13.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.574.0",
|
"lucide-react": "^0.574.0",
|
||||||
|
|
|
||||||
BIN
public/aviator-bannsser.jpg
Normal file
BIN
public/aviator-bannsser.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
111
tenant.config.js
Normal file
111
tenant.config.js
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
module.exports = {
|
||||||
|
tenant: "fortunebets",
|
||||||
|
tenant_name: "FortuneBets",
|
||||||
|
tenant_email: "customer@fortunebets.net",
|
||||||
|
tenant_phone_number: "",
|
||||||
|
tenant_location: "Around Lem Hotel , Addis Ababa Ethiopia",
|
||||||
|
tenant_map_link: "https://maps.app.goo.gl/zvhxt8sh7JbsVpbc8",
|
||||||
|
tenant_telegram_link: "https://t.me/fortunebetts",
|
||||||
|
tenant_facebook_link: "https://www.facebook.com/profile.php?id=61565869044955",
|
||||||
|
tenant_bot_link: "",
|
||||||
|
app_title: "FortuneBets",
|
||||||
|
theme_color: "#ffffff",
|
||||||
|
robots_sitemap: "https://fortunebets.net/sitemap.xml",
|
||||||
|
root_url: "https://fortunebets.net",
|
||||||
|
tile_color: "#00a300",
|
||||||
|
theme: "/tenant/fortunebets/theme.json",
|
||||||
|
logo: "/tenant/fortunebets/Logo.webp",
|
||||||
|
favicon: "/tenant/fortunebets/favicon.ico",
|
||||||
|
favicon96: "/tenant/fortunebets/favicon-96x96.png",
|
||||||
|
favicon_svg: "/tenant/fortunebets/favicon.svg",
|
||||||
|
apple_touch_icon: "/tenant/fortunebets/apple-touch-icon.png",
|
||||||
|
logo_small: "/tenant/fortunebets/logo-small.webp",
|
||||||
|
logo_white: "/tenant/fortunebets/logo-white.webp",
|
||||||
|
site_manifest: "/tenant/fortunebets/site.webmanifest",
|
||||||
|
slider_1: "/tenant/fortunebets/fortunebet-image-1.webp",
|
||||||
|
slider_2: "/tenant/fortunebets/fortunebet-image-2.webp",
|
||||||
|
slider_3: "/tenant/fortunebets/fortunebet-image-3.webp",
|
||||||
|
home_page_canonical: "https://fortunebets.net",
|
||||||
|
homepage_title:
|
||||||
|
"FortuneBets - Premier Ethiopia Online Casino and Sports Betting",
|
||||||
|
homepage_desc:
|
||||||
|
"Discover Ethiopia's premier betting destination. Premier League, Champions League, MMA, Dog and Horse Racing and Virtual Games Betting!",
|
||||||
|
homepage_keywords:
|
||||||
|
"Casino, Sports Betting, Ethiopia, MMA, Keno, Spin to Win, Fire Crash, Aviator, Football, GoldenRace, Premier League, Champions League, Chapa, Telebirr, CBEBirr, Betika, Betking, QwickBirr, Anbessabet, Vamos bet, Habeshabet, Hulusport, ZuraPlay",
|
||||||
|
homepage_openGraph: {
|
||||||
|
images: [
|
||||||
|
"https://fortunebets.net/Logo.webp",
|
||||||
|
"https://demo.fortunebets.net/Logo.webp",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
jsonD: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Organization",
|
||||||
|
name: "Fortunebets",
|
||||||
|
image: "https://fortunebets.net/Logo.webp",
|
||||||
|
"@id": "",
|
||||||
|
url: "https://fortunebets.net",
|
||||||
|
telephone: "",
|
||||||
|
address: {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
streetAddress: "Lem Hotel, Megenagna",
|
||||||
|
addressLocality: "Addis Ababa",
|
||||||
|
postalCode: "",
|
||||||
|
addressCountry: "ET",
|
||||||
|
},
|
||||||
|
geo: {
|
||||||
|
"@type": "GeoCoordinates",
|
||||||
|
latitude: 9.018046093660232,
|
||||||
|
longitude: 38.79564644773323,
|
||||||
|
},
|
||||||
|
sameAs: ["https://www.facebook.com/profile.php?id=61565869044955"],
|
||||||
|
},
|
||||||
|
|
||||||
|
deposit_title: "Deposit - FortuneBets",
|
||||||
|
deposit_description:
|
||||||
|
"Deposit cash into your FortuneBets Account so that you can start playing games for cash",
|
||||||
|
deposit_keyword:
|
||||||
|
"withdraw, Birr, Chapa, CBEBirr, TeleBirr, Deposit, FortuneBets, Ethiopia, Sports Betting",
|
||||||
|
deposit_alt_canonical: "https://fortunebets.net/deposit",
|
||||||
|
games_title: "FortuneBetss - Ethiopia Online Casino and Sports Betting",
|
||||||
|
games_desc:
|
||||||
|
"FortuneBetss is Ethiopia's premier betting destination. Win cash prizes by playing Number-Games like Spin To Win, roulette, online slots and Keno, as well as adrenaline-pumping Crash-Games including Fire Crash, Bank Run, and Meteoroid. You can preview or play the game. Join now and win your fortune!",
|
||||||
|
games_keywords:
|
||||||
|
"FortuneBetss, Casino, Sports Betting, Ethiopia, Addis Ababa, Hawassa, Fire Crash, Bank Run, Meteoroid, Keno, MMA, Spin to Win , Aviator , Virtual Football,Football, GoldenRace, Champions League, QwickBirr, Anbessabet, Vamos bet, Vamosbet, Habeshabet, Hulusport, ZuraPlay",
|
||||||
|
games_openGraph: {
|
||||||
|
images: ["https://fortunebets.net/Logo.webp"],
|
||||||
|
},
|
||||||
|
games_alt_canonical: "https://fortunebets.net/games",
|
||||||
|
login_title: "Login - FortuneBets",
|
||||||
|
login_desc:
|
||||||
|
"Login to FortuneBets Account so that you can start playing games for cash",
|
||||||
|
login_keywords:
|
||||||
|
"Sign Up, Register, Reset Password, Login, FortuneBets, Ethiopia, Sports Betting",
|
||||||
|
login_alt_canonical: "https://fortunebets.net/login",
|
||||||
|
register_title: "Register - FortuneBets",
|
||||||
|
register_desc:
|
||||||
|
"Create a new FortuneBets Account so that you can start playing games for cash",
|
||||||
|
register_keywords:
|
||||||
|
"Sign Up, Register, Reset Password, Login, FortuneBets, Ethiopia, Sports Betting",
|
||||||
|
register_alt_canonical: "https://fortunebets.net/register",
|
||||||
|
reset_title: "Reset Password - FortuneBets",
|
||||||
|
reset_desc:
|
||||||
|
"Did you forget your FortuneBets Password? Send a code to your account to create a new password",
|
||||||
|
reset_keywords:
|
||||||
|
"Sign Up, Register, Reset Password, Login, FortuneBets, Ethiopia, Sports Betting",
|
||||||
|
reset_alt_canonical: "https://fortunebets.net/reset-password",
|
||||||
|
rules_title: "Rules - FortuneBets",
|
||||||
|
rules_desc:
|
||||||
|
"Terms and Conditions, Responsible Gambling, FortuneBets is committed to providing a safe and responsible platform where Players can use the Services",
|
||||||
|
rules_keywords:
|
||||||
|
"FortuneBets, Ethiopia, Sports Betting, Terms and Conditions, Responsible Gambling, Account Suspension,",
|
||||||
|
rules_alt_canonical: "https://fortunebets.net/rules",
|
||||||
|
virtual_sports_title: "Virtual Sports - FortuneBets",
|
||||||
|
virtual_sports_desc:
|
||||||
|
"Win Cash Prizes by betting on FortuneBets Virtual Sports! Football tournaments like Premier League, Bundesliga, La Liga, Ligue 1, Series A, Champions League, World Cup. Mixed Martial Arts Betting. Racing sports like Motorcycle, Horse, Dog, Speedway Racing.",
|
||||||
|
virtual_sports_keywords:
|
||||||
|
"Virtual Sports Betting, Football Betting, QwickBirr Premier League, Bundesliga, La Liga, Ligue 1, Serie A, Champions League, World Cup, MMA, Motoracing, Horse Racing, Dog Racing, Basketball, Speedway Racing, Betika",
|
||||||
|
virtual_sports_alt_canonical: "https://fortunebets.net/virtual",
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user