From 263cd4b65ec0551822ead15abe4f6a67cf44ef5f Mon Sep 17 00:00:00 2001 From: brooktewabe Date: Sun, 1 Mar 2026 14:24:51 +0300 Subject: [PATCH] games pages --- app/print-odds/page.tsx | 385 +++++++++++++++++++++++++++++++++++++ app/register/page.tsx | 14 +- app/special-games/page.tsx | 106 ++++++++++ app/virtual/page.tsx | 204 ++++++++++++++++++++ 4 files changed, 702 insertions(+), 7 deletions(-) create mode 100644 app/print-odds/page.tsx create mode 100644 app/special-games/page.tsx create mode 100644 app/virtual/page.tsx diff --git a/app/print-odds/page.tsx b/app/print-odds/page.tsx new file mode 100644 index 0000000..c13614a --- /dev/null +++ b/app/print-odds/page.tsx @@ -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 = { + "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>(new Set()) + const [selectedTournaments, setSelectedTournaments] = useState>(new Set()) + const [selectedMarkets, setSelectedMarkets] = useState>(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(` + + + Print Odds + + + +

Print Odds โ€“ ${dateHelper}

+

Date range: ${startDate} โ€“ ${endDate}

+

Nations: ${Array.from(selectedNations).join(", ")}

+

Tournaments: ${Array.from(selectedTournaments).join(", ")}

+

Markets: ${Array.from(selectedMarkets).map((id) => ALL_MARKETS.find((m) => m.id === id)?.name ?? id).join(", ")}

+

Odds sheet would be generated based on selected options.

+ + + `) + printWindow.document.close() + printWindow.focus() + printWindow.print() + printWindow.close() + } + + const sport = SPORTS_WITH_NATIONS[0] + + return ( +
+
+ + + + 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" + /> + 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" + /> + +
+ + +
+ + +
+ ) +} diff --git a/app/register/page.tsx b/app/register/page.tsx index 22fef68..b2fc675 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -44,13 +44,13 @@ export default function RegisterPage() { ))}
{/* HARIF box */} -
+
HARIF
{/* SPORT text */} - + SPORT
@@ -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" /> @@ -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" /> @@ -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" /> @@ -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" /> I confirm I'm over 21 years old @@ -139,7 +139,7 @@ export default function RegisterPage() { {/* Login link */}

Already have an account?{" "} - + Login

diff --git a/app/special-games/page.tsx b/app/special-games/page.tsx new file mode 100644 index 0000000..fae44c4 --- /dev/null +++ b/app/special-games/page.tsx @@ -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 ( +
+ + +
+
+ Special Games Banner +
+ +
+ {activeCategory === "all" ? ( +
+ {CATEGORIES_DATA.map((category) => ( + + ))} +
+ ) : ( +
+
+

+ {activeCategoryData?.title || activeCategory.replace("-", " ")} +

+ +
+ +
+ {(activeCategoryData?.games || DUMMY_GAMES).map((game, idx) => ( + + ))} +
+
+ )} +
+
+
+ ) +} diff --git a/app/virtual/page.tsx b/app/virtual/page.tsx new file mode 100644 index 0000000..843169d --- /dev/null +++ b/app/virtual/page.tsx @@ -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([]) + const [games, setGames] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(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 ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+ {/* Banner */} +
+ Virtual Games Banner +
+ +
+ {isLoading ? ( +
+
+ Loading games... +
+ ) : error ? ( +
+ + {error} +
+ ) : activeCategory === "all" ? ( + // Show all categories +
+ {groupedGames.map((category, index) => ( + + ))} +
+ ) : ( + // Show only selected category +
+
+

+ {activeCategoryData?.provider_name || 'Games'} +

+ +
+ +
+ {displayedGames.map((game, idx) => ( + + ))} +
+
+ )} +
+
+
+ ) +}