"use client"; import * as React from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Heart, Play, ChevronRight } from "lucide-react"; import type { GameCard as GameCardData } from "@spinza/shared"; import { api } from "@/lib/api"; import { toast, useSession } from "@/lib/store"; import { cn, VOLATILITY_BARS, VOLATILITY_LABEL } from "@/lib/utils"; import { Badge, Skeleton } from "@/components/ui"; import { GameArt } from "./game-art"; /* ------------------------------------------------------------- favourites */ function useFavorite(game: GameCardData, onChange?: (slug: string, fav: boolean) => void) { const status = useSession((s) => s.status); const router = useRouter(); const [fav, setFav] = React.useState(!!game.favorite); const [busy, setBusy] = React.useState(false); // Keep in sync when the parent re-renders with fresh server data (adjust-state-from-props pattern). const [seen, setSeen] = React.useState(!!game.favorite); if (!!game.favorite !== seen) { setSeen(!!game.favorite); setFav(!!game.favorite); } const toggle = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (status !== "authenticated") { router.push(`/login?next=${encodeURIComponent(window.location.pathname)}`); return; } if (busy) return; const next = !fav; setFav(next); onChange?.(game.slug, next); setBusy(true); try { const res = await api<{ favorite: boolean }>(`/api/games/${game.slug}/favorite`, { method: "POST" }); setFav(res.favorite); onChange?.(game.slug, res.favorite); } catch (err) { setFav(!next); onChange?.(game.slug, !next); toast({ title: "Could not update favourites", description: err instanceof Error ? err.message : undefined, tone: "danger" }); } finally { setBusy(false); } }; return { fav, toggle }; } function HeartButton({ fav, onClick, className }: { fav: boolean; onClick: (e: React.MouseEvent) => void; className?: string }) { return ( ); } /* -------------------------------------------------------------- volatility */ export function VolatilityMeter({ volatility, className, showLabel = true }: { volatility: string; className?: string; showLabel?: boolean }) { const bars = VOLATILITY_BARS[volatility] ?? 2; return ( {[1, 2, 3, 4].map((i) => ( ))} {showLabel ? {VOLATILITY_LABEL[volatility] ?? volatility} : null} ); } /* ---------------------------------------------------------------- GameCard */ export interface GameCardProps { game: GameCardData; className?: string; onFavoriteChange?: (slug: string, fav: boolean) => void; priority?: boolean; } export function GameCard({ game, className, onFavoriteChange }: GameCardProps) { const { fav, toggle } = useFavorite(game, onFavoriteChange); return (
{game.isNew ? New : null} {game.isJackpot ? Jackpot : null}
{game.name}

{game.tagline}

Play
); } export function GameCardSkeleton({ className }: { className?: string }) { return (
); } /* ----------------------------------------------------------------- GameRow */ export function GameRow({ title, eyebrow, games, href, className, id }: { title: string; eyebrow?: string; games: GameCardData[]; href?: string; className?: string; id?: string }) { if (!games.length) return null; return (
{eyebrow ?
{eyebrow}
: null}

{title}

{href ? ( See all ) : null}
{games.map((g) => ( ))}
); } /* ---------------------------------------------------------------- GameTile */ /** Compact row tile for dense lists (continue playing, search results). */ export function GameTile({ game, meta, className, onFavoriteChange }: { game: GameCardData; meta?: React.ReactNode; className?: string; onFavoriteChange?: (slug: string, fav: boolean) => void }) { const { fav, toggle } = useFavorite(game, onFavoriteChange); return (
{game.name} {game.isNew ? New : null} {game.isJackpot ? Jackpot : null}
{meta ? ยท {meta} : null}
); }