games pages
This commit is contained in:
parent
463cabbdd0
commit
263cd4b65e
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>
|
||||
{/* 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">
|
||||
HARIF
|
||||
</span>
|
||||
</div>
|
||||
{/* 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
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -78,7 +78,7 @@ export default function RegisterPage() {
|
|||
type="tel"
|
||||
value={phone}
|
||||
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>
|
||||
|
|
@ -93,7 +93,7 @@ export default function RegisterPage() {
|
|||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
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>
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ export default function RegisterPage() {
|
|||
value={repeatPassword}
|
||||
onChange={(e) => setRepeatPassword(e.target.value)}
|
||||
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>
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ export default function RegisterPage() {
|
|||
type="checkbox"
|
||||
checked={ageConfirmed}
|
||||
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">
|
||||
I confirm I'm over 21 years old
|
||||
|
|
@ -139,7 +139,7 @@ export default function RegisterPage() {
|
|||
{/* Login link */}
|
||||
<p className="text-center text-[11px] text-white/50">
|
||||
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
|
||||
</Link>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user