diff --git a/apps/web/src/app/[locale]/games/[slug]/page.tsx b/apps/web/src/app/[locale]/games/[slug]/page.tsx new file mode 100644 index 0000000..fd4f388 --- /dev/null +++ b/apps/web/src/app/[locale]/games/[slug]/page.tsx @@ -0,0 +1,35 @@ +import { notFound } from "next/navigation"; +import { getGameBySlug } from "@/lib/api"; +import { GameHeader } from "@/components/game/GameHeader"; +import { GameInfo } from "@/components/game/GameInfo"; +import { ScreenshotGallery } from "@/components/game/ScreenshotGallery"; + +export default async function GamePage({ + params, +}: { + params: Promise<{ locale: string; slug: string }>; +}) { + const { locale, slug } = await params; + + let game; + try { + const res = await getGameBySlug(slug, locale); + game = Array.isArray(res.data) ? res.data[0] : res.data; + } catch { + notFound(); + } + + if (!game) notFound(); + + return ( + <> + +
+ + {game.screenshots && ( + + )} +
+ + ); +} diff --git a/apps/web/src/components/game/GameHeader.tsx b/apps/web/src/components/game/GameHeader.tsx new file mode 100644 index 0000000..9f1202a --- /dev/null +++ b/apps/web/src/components/game/GameHeader.tsx @@ -0,0 +1,29 @@ +import Image from "next/image"; +import type { Game } from "@afterlife/shared"; + +interface GameHeaderProps { + game: Game; +} + +export function GameHeader({ game }: GameHeaderProps) { + return ( +
+ {game.coverImage && ( + {game.title} + )} +
+
+

{game.title}

+

+ {game.developer} · {game.releaseYear}–{game.shutdownYear} +

+
+
+ ); +} diff --git a/apps/web/src/components/game/GameInfo.tsx b/apps/web/src/components/game/GameInfo.tsx new file mode 100644 index 0000000..7239988 --- /dev/null +++ b/apps/web/src/components/game/GameInfo.tsx @@ -0,0 +1,79 @@ +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import type { Game } from "@afterlife/shared"; + +interface GameInfoProps { + game: Game; + locale: string; +} + +export function GameInfo({ game, locale }: GameInfoProps) { + const t = useTranslations("game"); + + const statusColors = { + online: "text-green-400", + maintenance: "text-yellow-400", + coming_soon: "text-blue-400", + }; + + return ( +
+
+
+
+
+
+
+
+
{t("developer")}
+
{game.developer}
+
+ {game.publisher && ( +
+
{t("publisher")}
+
{game.publisher}
+
+ )} +
+
{t("released")}
+
{game.releaseYear}
+
+
+
{t("shutdown")}
+
{game.shutdownYear}
+
+
+
{t("server_status")}
+
+ {t(`status_${game.serverStatus}`)} +
+
+
+
+ {game.serverLink && game.serverStatus === "online" && ( + + {t("play_now")} + + )} + {game.documentary && ( + + {t("view_documentary")} + + )} +
+
+
+
+ ); +} diff --git a/apps/web/src/components/game/ScreenshotGallery.tsx b/apps/web/src/components/game/ScreenshotGallery.tsx new file mode 100644 index 0000000..d5b4a5a --- /dev/null +++ b/apps/web/src/components/game/ScreenshotGallery.tsx @@ -0,0 +1,48 @@ +"use client"; + +import Image from "next/image"; +import { useState } from "react"; +import type { StrapiMedia } from "@afterlife/shared"; + +interface ScreenshotGalleryProps { + screenshots: StrapiMedia[]; +} + +export function ScreenshotGallery({ screenshots }: ScreenshotGalleryProps) { + const [selected, setSelected] = useState(0); + + if (screenshots.length === 0) return null; + + return ( +
+
+ {screenshots[selected].alternativeText +
+ {screenshots.length > 1 && ( +
+ {screenshots.map((ss, i) => ( + + ))} +
+ )} +
+ ); +}