games pages

This commit is contained in:
brooktewabe 2026-03-01 14:24:51 +03:00
parent 463cabbdd0
commit 263cd4b65e
4 changed files with 702 additions and 7 deletions

385
app/print-odds/page.tsx Normal file
View 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>
)
}

View File

@ -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&apos;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
View 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
View 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>
)
}