SPB Git

spb/spinza

Public
7.1 KB · 138 lines tsx
1import { Suspense } from "react";2import Link from "next/link";3import { Play, Info, Sparkles } from "lucide-react";4import type { GameCard as GameCardData, PublicUser } from "@spinza/shared";5import { Button } from "@/components/ui";6import { ServerUnavailable } from "@/components/shell/api-error";7import { GameArt } from "./game-art";8import { GameCard, GameRow, GameTile, VolatilityMeter } from "./game-card";9import { RewardsStrip } from "./rewards-strip";10import { WelcomeOverlay } from "./welcome-overlay";11import { timeAgo } from "@/lib/utils";1213export interface LobbyProps {14  user: PublicUser;15  games: GameCardData[] | null;16  livePlayers: number;17  recommendations: string[];18}1920const FLAGSHIP_SLUG = "spinza-original";2122export function Lobby({ user, games, livePlayers, recommendations }: LobbyProps) {23  const list = games ?? [];24  const flagship = list.find((g) => g.slug === FLAGSHIP_SLUG) ?? list.find((g) => g.isFeatured) ?? list[0] ?? null;25  const continuePlaying = list.filter((g) => g.lastPlayedAt).sort((a, b) => new Date(b.lastPlayedAt!).getTime() - new Date(a.lastPlayedAt!).getTime());26  const featured = list.filter((g) => g.isFeatured);27  const fresh = list.filter((g) => g.isNew);28  const popular = [...list].sort((a, b) => b.popularity - a.popularity).slice(0, 10);29  const highVol = list.filter((g) => g.volatility === "high" || g.volatility === "extreme");30  const relaxed = list.filter((g) => g.volatility === "low" || g.volatility === "medium");31  const jackpots = list.filter((g) => g.isJackpot);32  const picks = recommendations.map((s) => list.find((g) => g.slug === s)).filter((g): g is GameCardData => !!g);33  const welcomePicks = picks.length ? picks : list.slice(0, 3);3435  return (36    <div className="space-y-10 lg:space-y-12">37      <Suspense fallback={null}>38        <WelcomeOverlay picks={welcomePicks} />39      </Suspense>4041      {/* ---------------------------------------------------------- hero */}42      {flagship ? (43        <section className="relative overflow-hidden rounded-xl border border-line bg-bg-1" aria-labelledby="lobby-hero-title">44          <div className="absolute inset-0">45            <GameArt slug={flagship.slug} name={flagship.name} palette={flagship.palette} variant="hero" showName={false} />46            <div className="absolute inset-0 bg-[linear-gradient(90deg,rgba(7,8,12,0.92)_0%,rgba(7,8,12,0.7)_45%,rgba(7,8,12,0.15)_100%)]" />47            <div className="absolute inset-x-0 bottom-0 h-2/3 bg-[linear-gradient(180deg,transparent,rgba(7,8,12,0.85))]" />48          </div>49          <div className="relative flex min-h-[340px] flex-col justify-end p-6 sm:min-h-[420px] sm:p-10">50            <div className="eyebrow mb-3 flex items-center gap-2 text-accent">51              <Sparkles className="h-3.5 w-3.5" /> {flagship.slug === FLAGSHIP_SLUG ? "Flagship title" : "Featured"}52            </div>53            <h1 id="lobby-hero-title" className="text-4xl font-semibold leading-[0.95] tracking-[-0.04em] sm:text-6xl">54              {flagship.name}55            </h1>56            <p className="mt-3 max-w-md text-[15px] text-fg-2 sm:text-base">{flagship.tagline}</p>57            <div className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-[13px] text-fg-3">58              <VolatilityMeter volatility={flagship.volatility} />59              <span>{flagship.grid.reels}×{flagship.grid.rows} · {flagship.features.slice(0, 2).join(" · ")}</span>60            </div>61            <div className="mt-6 flex flex-wrap gap-3">62              <Button size="lg" href={`/games/${flagship.slug}`} className="min-w-[160px]">63                <Play className="h-4 w-4 fill-current" /> Play now64              </Button>65              <Button variant="outline" size="lg" href={`/games/${flagship.slug}#info`} className="glass">66                <Info className="h-4 w-4" /> Game info67              </Button>68            </div>69          </div>70        </section>71      ) : games === null ? (72        <ServerUnavailable />73      ) : (74        <section className="surface rounded-xl p-8 text-center sm:p-12">75          <div className="eyebrow mb-2">Lobby</div>76          <h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">Welcome back, {user.username}.</h1>77          <p className="mx-auto mt-2 max-w-md text-sm text-fg-3">New games are being certified and will appear here as soon as they pass. Your credits and progress are safe.</p>78          <Button className="mt-6" variant="secondary" href="/rewards">79            Visit rewards80          </Button>81        </section>82      )}8384      {/* --------------------------------------------------------- strip */}85      <RewardsStrip livePlayers={livePlayers} />8687      {games === null && flagship ? <ServerUnavailable compact /> : null}8889      {/* ------------------------------------------------------ sections */}90      {continuePlaying.length ? (91        <section aria-labelledby="continue-title">92          <div className="mb-3">93            <div className="eyebrow mb-1">Pick up where you left off</div>94            <h2 id="continue-title" className="text-xl font-semibold tracking-tight sm:text-2xl">95              Continue playing96            </h2>97          </div>98          <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">99            {continuePlaying.slice(0, 6).map((g) => (100              <GameTile key={g.slug} game={g} meta={`Played ${timeAgo(g.lastPlayedAt!)}`} />101            ))}102          </div>103        </section>104      ) : null}105106      <GameRow id="featured" title="Featured games" eyebrow="Curated tonight" games={featured} href="/games?filter=featured" />107      <GameRow id="new" title="New releases" eyebrow="Fresh from the studio" games={fresh} href="/games?filter=new" />108      <GameRow id="popular" title="Popular" eyebrow="Most played this week" games={popular} href="/games" />109      <GameRow id="high" title="High volatility" eyebrow="Big swings, big moments" games={highVol} href="/games?filter=high" />110      <GameRow id="relaxed" title="Relaxed games" eyebrow="Steady, easy sessions" games={relaxed} href="/games?filter=relaxed" />111      <GameRow id="jackpot" title="Jackpot games" eyebrow="Hold, respin, fill the grid" games={jackpots} href="/games?filter=jackpot" />112113      {list.length ? (114        <section aria-labelledby="all-title">115          <div className="mb-4 flex items-end justify-between">116            <div>117              <div className="eyebrow mb-1">{list.length} titles</div>118              <h2 id="all-title" className="text-xl font-semibold tracking-tight sm:text-2xl">119                All games120              </h2>121            </div>122            <Link href="/games" className="tap inline-flex h-9 min-h-0 items-center text-sm font-medium text-fg-3 hover:text-fg">123              Filter &amp; search124            </Link>125          </div>126          <div className="grid grid-cols-2 gap-x-3 gap-y-6 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">127            {list.map((g) => (128              <GameCard key={g.slug} game={g} className="w-full sm:w-full" />129            ))}130          </div>131        </section>132      ) : null}133134      <p className="pt-4 text-center text-[12px] text-fg-4">Virtual credits only. No deposits. No withdrawals. No cash value. 18+.</p>135    </div>136  );137}138
Files · 3b4179a710ab1cd5f12ca7cfd253b81674f1521e
Menu

Theme