Files
nanami-web/src/app/(public)/page.tsx
2026-05-15 19:17:31 +08:00

206 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Link from "next/link";
import { prisma } from "@/lib/db";
import { Button } from "@/components/ui/button";
import { AddonCard } from "@/components/public/AddonCard";
import { HeroBanner } from "@/components/public/HeroBanner";
import { GameGallery } from "@/components/public/GameGallery";
import { ShutdownBanner } from "@/components/public/ShutdownBanner";
import { T } from "@/components/public/T";
import { ArticleCard } from "@/components/public/ArticleCard";
import { getSiteSettings } from "@/lib/site-settings";
import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions";
import { Sparkles, Shield, Zap } from "lucide-react";
export const dynamic = "force-dynamic";
export default async function HomePage() {
const siteSettings = await getSiteSettings();
const wowVersion = DEFAULT_WOW_VERSION;
const [featuredAddons, launcher, launcherDownloads, banners, galleryImages, latestArticles] =
await Promise.all([
prisma.addon.findMany({
where: { published: true },
include: {
releases: {
where: { isLatest: true, wowVersion },
select: { version: true },
},
screenshots: {
orderBy: { sortOrder: "asc" },
take: 1,
select: { imageUrl: true },
},
},
orderBy: { totalDownloads: "desc" },
take: 6,
}),
prisma.software.findUnique({
where: { slug: "nanami-launcher" },
include: {
versions: {
where: { isLatest: true, wowVersion },
take: 1,
},
},
}),
prisma.softwareVersion.aggregate({
where: { software: { slug: "nanami-launcher" }, wowVersion },
_sum: { downloadCount: true },
}),
prisma.bannerImage.findMany({
where: { enabled: true },
orderBy: { sortOrder: "asc" },
select: { imageUrl: true },
}),
prisma.galleryImage.findMany({
where: { enabled: true },
orderBy: { sortOrder: "asc" },
select: { imageUrl: true, title: true, titleEn: true },
}),
prisma.article.findMany({
where: { published: true },
orderBy: { createdAt: "desc" },
take: 3,
}),
]);
const launcherVersion = launcher?.versions[0]?.version ?? null;
const totalDownloads = launcherDownloads._sum.downloadCount ?? 0;
return (
<>
{siteSettings.shutdownBannerEnabled && siteSettings.shutdownAt && (
<ShutdownBanner
title={siteSettings.shutdownTitle || "旅途总有终点,\n但我们的故事未完待续"}
titleEn={
siteSettings.shutdownTitleEn ||
"Every journey reaches its end —\nbut our story is yet to be told"
}
subtitle={
siteSettings.shutdownSubtitle ||
"《Turtle WoW》即将关闭服务器将于欧洲时间 5月15日 凌晨 00:00 正式关闭。"
}
subtitleEn={
siteSettings.shutdownSubtitleEn ||
"Turtle WoW is closing its gates. Servers will go dark at 00:00 European time on May 15."
}
shutdownAt={siteSettings.shutdownAt.toISOString()}
/>
)}
<HeroBanner
totalDownloads={totalDownloads || undefined}
launcherVersion={launcherVersion}
banners={banners}
/>
<GameGallery items={galleryImages} />
{/* Features */}
<section className="relative border-t border-amber-900/20 bg-gradient-to-b from-[#0d0b15] to-[#110f1a] dark:from-[#0d0b15] dark:to-[#110f1a]">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(168,85,247,0.06)_0%,transparent_70%)]" />
<div className="relative mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
<div className="grid gap-4 sm:gap-8 md:grid-cols-3">
<div className="flex flex-col items-center rounded-xl border border-amber-500/10 bg-white/5 p-6 text-center backdrop-blur transition-colors hover:border-amber-500/25">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-amber-500/10">
<Sparkles className="h-6 w-6 text-amber-400" />
</div>
<h3 className="mt-4 font-semibold text-amber-100">
<T section="home" k="featureDeepTitle" />
</h3>
<p className="mt-2 text-sm text-gray-400">
<T section="home" k="featureDeepDesc" />
</p>
</div>
<div className="flex flex-col items-center rounded-xl border border-amber-500/10 bg-white/5 p-6 text-center backdrop-blur transition-colors hover:border-amber-500/25">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-purple-500/10">
<Shield className="h-6 w-6 text-purple-400" />
</div>
<h3 className="mt-4 font-semibold text-amber-100">
<T section="home" k="featureInstallTitle" />
</h3>
<p className="mt-2 text-sm text-gray-400">
<T section="home" k="featureInstallDesc" />
</p>
</div>
<div className="flex flex-col items-center rounded-xl border border-amber-500/10 bg-white/5 p-6 text-center backdrop-blur transition-colors hover:border-amber-500/25">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-cyan-500/10">
<Zap className="h-6 w-6 text-cyan-400" />
</div>
<h3 className="mt-4 font-semibold text-amber-100">
<T section="home" k="featureAITitle" />
</h3>
<p className="mt-2 text-sm text-gray-400">
<T section="home" k="featureAIDesc" />
</p>
</div>
</div>
</div>
</section>
{/* Latest Articles */}
{latestArticles.length > 0 && (
<section className="border-t border-amber-900/20 bg-gradient-to-b from-[#110f1a] to-[#0d0b15] dark:from-[#110f1a] dark:to-[#0d0b15]">
<div className="mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
<div className="mb-6 flex items-center justify-between sm:mb-8">
<h2 className="text-2xl font-bold text-amber-100">
<T section="home" k="latestArticles" />
</h2>
<Button
variant="outline"
className="border-amber-500/20 text-amber-200 hover:border-amber-500/40 hover:bg-amber-500/10 hover:text-amber-100"
render={<Link href="/articles" />}
>
<T section="home" k="viewMore" />
</Button>
</div>
<div className="grid gap-4 sm:gap-6 md:grid-cols-3">
{latestArticles.map((article) => (
<ArticleCard
key={article.id}
variant="compact"
article={{
id: article.id,
slug: article.slug,
title: article.title,
titleEn: article.titleEn,
summary: article.summary,
summaryEn: article.summaryEn,
coverImage: article.coverImage,
createdAt: article.createdAt.toISOString(),
}}
/>
))}
</div>
</div>
</section>
)}
{/* Featured Addons */}
{featuredAddons.length > 0 && (
<section className="border-t border-amber-900/20 bg-gradient-to-b from-[#110f1a] to-[#0d0b15] dark:from-[#110f1a] dark:to-[#0d0b15]">
<div className="mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
<div className="mb-6 flex items-center justify-between sm:mb-8">
<h2 className="text-2xl font-bold text-amber-100">
<T section="home" k="hotAddons" />
</h2>
<Button
variant="outline"
className="border-amber-500/20 text-amber-200 hover:border-amber-500/40 hover:bg-amber-500/10 hover:text-amber-100"
render={<Link href="/addons" />}
>
<T section="home" k="viewAll" />
</Button>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{featuredAddons.map((addon) => (
<AddonCard key={addon.id} addon={addon} />
))}
</div>
</div>
</section>
)}
</>
);
}