From eabc858f9aebb9e7b6f048e75124dbd04ac96fd3 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Sun, 22 Feb 2026 04:00:00 +0000 Subject: [PATCH] feat: build landing page with hero, latest games, and donation CTA Add GameCard shared component, HeroSection with framer-motion animations, LatestGames grid section, and DonationCTA banner. Wire up the home page to fetch games from Strapi and render all landing page sections. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/app/[locale]/page.tsx | 29 +++++++++-- apps/web/src/components/home/DonationCTA.tsx | 23 +++++++++ apps/web/src/components/home/HeroSection.tsx | 54 ++++++++++++++++++++ apps/web/src/components/home/LatestGames.tsx | 34 ++++++++++++ apps/web/src/components/shared/GameCard.tsx | 46 +++++++++++++++++ 5 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/components/home/DonationCTA.tsx create mode 100644 apps/web/src/components/home/HeroSection.tsx create mode 100644 apps/web/src/components/home/LatestGames.tsx create mode 100644 apps/web/src/components/shared/GameCard.tsx diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 9971fb0..b7065a2 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -1,7 +1,28 @@ -export default function HomePage() { +import { getGames } from "@/lib/api"; +import { HeroSection } from "@/components/home/HeroSection"; +import { LatestGames } from "@/components/home/LatestGames"; +import { DonationCTA } from "@/components/home/DonationCTA"; + +export default async function HomePage({ + 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 yet — render page without games + } + return ( -
-

Project Afterlife

-
+ <> + + + + ); } diff --git a/apps/web/src/components/home/DonationCTA.tsx b/apps/web/src/components/home/DonationCTA.tsx new file mode 100644 index 0000000..3270f46 --- /dev/null +++ b/apps/web/src/components/home/DonationCTA.tsx @@ -0,0 +1,23 @@ +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { useLocale } from "next-intl"; + +export function DonationCTA() { + const t = useTranslations("donate"); + const locale = useLocale(); + + return ( +
+
+

{t("title")}

+

{t("description")}

+ + {t("patreon")} + +
+
+ ); +} diff --git a/apps/web/src/components/home/HeroSection.tsx b/apps/web/src/components/home/HeroSection.tsx new file mode 100644 index 0000000..b7e5fa2 --- /dev/null +++ b/apps/web/src/components/home/HeroSection.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { motion } from "framer-motion"; +import Link from "next/link"; +import { useLocale } from "next-intl"; + +export function HeroSection() { + const t = useTranslations("home"); + const locale = useLocale(); + + return ( +
+
+
+ + {t("hero_title")} + + + {t("hero_subtitle")} + + + + {t("view_all")} + + + {t("donate_cta")} + + +
+
+ ); +} diff --git a/apps/web/src/components/home/LatestGames.tsx b/apps/web/src/components/home/LatestGames.tsx new file mode 100644 index 0000000..499f73f --- /dev/null +++ b/apps/web/src/components/home/LatestGames.tsx @@ -0,0 +1,34 @@ +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import type { Game } from "@afterlife/shared"; +import { GameCard } from "../shared/GameCard"; + +interface LatestGamesProps { + games: Game[]; + locale: string; +} + +export function LatestGames({ games, locale }: LatestGamesProps) { + const t = useTranslations("home"); + + if (games.length === 0) return null; + + return ( +
+
+

{t("latest_games")}

+ + {t("view_all")} → + +
+
+ {games.slice(0, 6).map((game) => ( + + ))} +
+
+ ); +} diff --git a/apps/web/src/components/shared/GameCard.tsx b/apps/web/src/components/shared/GameCard.tsx new file mode 100644 index 0000000..c70d55e --- /dev/null +++ b/apps/web/src/components/shared/GameCard.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; +import Image from "next/image"; +import type { Game } from "@afterlife/shared"; + +interface GameCardProps { + game: Game; + locale: string; +} + +export function GameCard({ game, locale }: GameCardProps) { + const statusColors = { + online: "bg-green-500", + maintenance: "bg-yellow-500", + coming_soon: "bg-blue-500", + }; + + return ( + +
+ {game.coverImage && ( +
+ {game.coverImage.alternativeText +
+
+ )} +
+
+ + {game.genre} +
+

+ {game.title} +

+

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

+
+
+ + ); +}