TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1import { Suspense } from "react";2import type { Metadata } from "next";3import type { GameCard } from "@spinza/shared";4import { apiServer } from "@/lib/api-server";5import { AppShell } from "@/components/shell/app-shell";6import { ServerUnavailable } from "@/components/shell/api-error";7import { GamesBrowser } from "@/components/lobby/games-browser";8import { GameCardSkeleton } from "@/components/lobby/game-card";910export const metadata: Metadata = {11 title: "Games",12 description: "Browse all twenty original Spinza games — cascades, expanding wilds, hold-and-respin jackpots and more. Free fictional credits, no cash value.",13 alternates: { canonical: "/games" },14};1516export default async function GamesPage() {17 const data = await apiServer<{ games: GameCard[] }>("/api/games");18 return (19 <AppShell>20 <div className="mb-6">21 <div className="eyebrow mb-1">Library</div>22 <h1 className="text-3xl font-semibold tracking-tight sm:text-4xl">All games</h1>23 <p className="mt-2 max-w-xl text-sm text-fg-3">Every title is a Spinza original, certified against its published math. Volatility tells you how the ride feels; open Game Info inside any game for the full details.</p>24 </div>25 {data === null ? (26 <ServerUnavailable />27 ) : (28 <Suspense29 fallback={30 <div className="grid grid-cols-2 gap-x-3 gap-y-6 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5" aria-busy>31 {Array.from({ length: 10 }).map((_, i) => (32 <GameCardSkeleton key={i} className="w-full sm:w-full" />33 ))}34 </div>35 }36 >37 <GamesBrowser games={data.games} />38 </Suspense>39 )}40 </AppShell>41 );42}43