227 lines
6.8 KiB
TypeScript
227 lines
6.8 KiB
TypeScript
"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>
|
|
);
|
|
}
|