feat: build game catalog page with filters and grid

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-22 04:01:50 +00:00
parent eabc858f9a
commit 70a603274b
3 changed files with 116 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import { Suspense } from "react";
import { getGames } from "@/lib/api";
import { CatalogFilters } from "@/components/catalog/CatalogFilters";
import { CatalogGrid } from "@/components/catalog/CatalogGrid";
export default async function CatalogPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
let games: any[] = [];
try {
const res = await getGames(locale);
games = res.data;
} catch {
// Strapi not running
}
return (
<div className="max-w-7xl mx-auto px-4 py-12">
<h1 className="text-4xl font-bold mb-8">
{locale === "es" ? "Catálogo de Juegos" : "Game Catalog"}
</h1>
<Suspense>
<CatalogFilters />
</Suspense>
<CatalogGrid games={games} locale={locale} />
</div>
);
}

View File

@@ -0,0 +1,56 @@
"use client";
import { useTranslations } from "next-intl";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import type { Genre, ServerStatus } from "@afterlife/shared";
const GENRES: Genre[] = ["MMORPG", "FPS", "Casual", "Strategy", "Sports", "Other"];
const STATUSES: ServerStatus[] = ["online", "maintenance", "coming_soon"];
export function CatalogFilters() {
const t = useTranslations("catalog");
const tGame = useTranslations("game");
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const currentGenre = searchParams.get("genre") || "";
const currentStatus = searchParams.get("status") || "";
function setFilter(key: string, value: string) {
const params = new URLSearchParams(searchParams.toString());
if (value) {
params.set(key, value);
} else {
params.delete(key);
}
router.push(`${pathname}?${params.toString()}`);
}
return (
<div className="flex flex-wrap gap-4 mb-8">
<select
value={currentGenre}
onChange={(e) => setFilter("genre", e.target.value)}
className="bg-gray-900 border border-white/10 rounded-lg px-4 py-2 text-sm text-white"
>
<option value="">{t("filter_genre")}: {t("all")}</option>
{GENRES.map((g) => (
<option key={g} value={g}>{g}</option>
))}
</select>
<select
value={currentStatus}
onChange={(e) => setFilter("status", e.target.value)}
className="bg-gray-900 border border-white/10 rounded-lg px-4 py-2 text-sm text-white"
>
<option value="">{t("filter_status")}: {t("all")}</option>
{STATUSES.map((s) => (
<option key={s} value={s}>
{tGame(`status_${s}`)}
</option>
))}
</select>
</div>
);
}

View File

@@ -0,0 +1,28 @@
import { useTranslations } from "next-intl";
import type { Game } from "@afterlife/shared";
import { GameCard } from "../shared/GameCard";
interface CatalogGridProps {
games: Game[];
locale: string;
}
export function CatalogGrid({ games, locale }: CatalogGridProps) {
const t = useTranslations("catalog");
if (games.length === 0) {
return (
<div className="text-center py-20">
<p className="text-gray-500 text-lg">{t("no_results")}</p>
</div>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{games.map((game) => (
<GameCard key={game.id} game={game} locale={locale} />
))}
</div>
);
}