175 lines
7.6 KiB
TypeScript
175 lines
7.6 KiB
TypeScript
import Link from "next/link"
|
|
import { ArrowRight, BookOpen, Sparkles, Flame, Heart, Swords, Building2, Rocket, Crown, Laugh, Search, Shield } from "lucide-react"
|
|
import { NovelCard } from "@/components/novel-card"
|
|
|
|
import { prisma } from "@/lib/prisma"
|
|
|
|
const iconMap: Record<string, React.ReactNode> = {
|
|
Sparkles: <Sparkles className="h-5 w-5" />,
|
|
Flame: <Flame className="h-5 w-5" />,
|
|
Heart: <Heart className="h-5 w-5" />,
|
|
Sword: <Swords className="h-5 w-5" />,
|
|
Building: <Building2 className="h-5 w-5" />,
|
|
Rocket: <Rocket className="h-5 w-5" />,
|
|
Crown: <Crown className="h-5 w-5" />,
|
|
Laugh: <Laugh className="h-5 w-5" />,
|
|
Search: <Search className="h-5 w-5" />,
|
|
Shield: <Shield className="h-5 w-5" />,
|
|
}
|
|
|
|
export const dynamic = "force-dynamic"
|
|
|
|
export default async function HomePage() {
|
|
let popularNovels: any[] = []
|
|
let latestNovels: any[] = []
|
|
let topRated: any[] = []
|
|
let genres: any[] = []
|
|
let featured = null
|
|
|
|
try {
|
|
popularNovels = await prisma.novel.findMany({
|
|
take: 20,
|
|
orderBy: { views: "desc" },
|
|
})
|
|
|
|
latestNovels = await prisma.novel.findMany({
|
|
take: 20,
|
|
orderBy: { updatedAt: "desc" },
|
|
})
|
|
|
|
topRated = await prisma.novel.findMany({
|
|
take: 4,
|
|
orderBy: { rating: "desc" },
|
|
})
|
|
|
|
genres = await prisma.genre.findMany({
|
|
take: 8,
|
|
})
|
|
|
|
featured = popularNovels.length > 0 ? popularNovels[0] : null
|
|
} catch (error) {
|
|
console.error("Failed to fetch data for homepage during build/runtime", error)
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto max-w-6xl px-4 py-6">
|
|
{/* Hero / Featured Novel */}
|
|
{featured && (
|
|
<section className="mb-10">
|
|
<Link
|
|
href={`/truyen/${featured.slug}`}
|
|
className="group relative flex flex-col overflow-hidden rounded-xl border border-border bg-card md:flex-row"
|
|
>
|
|
<img src={featured.coverUrl || "/default-cover.svg"} alt={featured.title} className="h-48 w-full object-cover md:h-auto md:w-72" />
|
|
<div className="flex flex-1 flex-col justify-center gap-3 p-6">
|
|
<span className="text-xs font-semibold uppercase tracking-wider text-primary">Truyện Nổi Bật</span>
|
|
<h1 title={featured.title} className="text-2xl font-bold text-foreground group-hover:text-primary transition-colors text-balance md:text-3xl">
|
|
{featured.title}
|
|
</h1>
|
|
<p className="text-sm text-muted-foreground">Tác giả: {featured.authorName}</p>
|
|
<p className="line-clamp-2 text-sm leading-relaxed text-muted-foreground">
|
|
{featured.description}
|
|
</p>
|
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
<span>{featured.totalChapters} chương</span>
|
|
<span>{featured.status}</span>
|
|
<span className="flex items-center gap-1 text-primary">
|
|
<Sparkles className="h-3.5 w-3.5" />
|
|
{featured.rating}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</section>
|
|
)}
|
|
|
|
{/* Popular Novels */}
|
|
<section className="mb-10">
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<h2 className="text-xl font-bold text-foreground">Truyện Hot</h2>
|
|
<Link href="/tim-kiem?sort=popular" className="flex items-center gap-1 text-sm text-primary hover:underline">
|
|
Xem tất cả <ArrowRight className="h-3.5 w-3.5" />
|
|
</Link>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
|
{popularNovels.length > 0 ? popularNovels.map((novel) => (
|
|
<NovelCard key={novel.id} novel={novel} />
|
|
)) : <p className="text-sm text-muted-foreground col-span-full">Chưa có truyện nào trong hệ thống.</p>}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Latest Updated */}
|
|
<section className="mb-10">
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<h2 className="text-xl font-bold text-foreground">Mới Cập Nhật</h2>
|
|
<Link href="/tim-kiem?sort=latest" className="flex items-center gap-1 text-sm text-primary hover:underline">
|
|
Xem tất cả <ArrowRight className="h-3.5 w-3.5" />
|
|
</Link>
|
|
</div>
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{latestNovels.length > 0 ? latestNovels.map((novel) => (
|
|
<NovelCard key={novel.id} novel={novel} variant="compact" />
|
|
)) : <p className="text-sm text-muted-foreground col-span-full">Chưa có truyện nào được cập nhật.</p>}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Two columns: Top Rated + Genres */}
|
|
<div className="grid gap-10 lg:grid-cols-2">
|
|
{/* Top Rated */}
|
|
<section>
|
|
<h2 className="mb-4 text-xl font-bold text-foreground">Đánh Giá Cao</h2>
|
|
<div className="flex flex-col gap-3">
|
|
{topRated.length > 0 ? topRated.map((novel, idx) => (
|
|
<Link
|
|
key={novel.id}
|
|
href={`/truyen/${novel.slug}`}
|
|
className="group flex items-center gap-4 rounded-lg border border-border bg-card p-3 transition-colors hover:border-primary/30"
|
|
>
|
|
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-sm font-bold text-primary">
|
|
{idx + 1}
|
|
</span>
|
|
<img src={novel.coverUrl || "/default-cover.svg"} alt={novel.title} className="h-12 w-9 shrink-0 rounded object-cover" />
|
|
<div className="min-w-0 flex-1">
|
|
<h3 title={novel.title} className="truncate text-sm font-semibold text-foreground group-hover:text-primary transition-colors">{novel.title}</h3>
|
|
<p className="text-xs text-muted-foreground">{novel.authorName} - Ch. {novel.totalChapters}</p>
|
|
</div>
|
|
<div className="flex items-center gap-1 text-sm font-semibold text-primary">
|
|
<Sparkles className="h-3.5 w-3.5" />
|
|
{novel.rating}
|
|
</div>
|
|
</Link>
|
|
)) : <p className="text-sm text-muted-foreground">Chưa có đánh giá.</p>}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Genres */}
|
|
<section>
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<h2 className="text-xl font-bold text-foreground">Thể Loại</h2>
|
|
<Link href="/the-loai" className="flex items-center gap-1 text-sm text-primary hover:underline">
|
|
Xem tất cả <ArrowRight className="h-3.5 w-3.5" />
|
|
</Link>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{genres.slice(0, 8).map((genre) => (
|
|
<Link
|
|
key={genre.id}
|
|
href={`/the-loai/${genre.slug}`}
|
|
className="group flex items-center gap-3 rounded-lg border border-border bg-card p-3 transition-colors hover:border-primary/30 hover:bg-accent/50"
|
|
>
|
|
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
|
{genre.icon && iconMap[genre.icon] ? iconMap[genre.icon] : <BookOpen className="h-5 w-5" />}
|
|
</span>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-foreground group-hover:text-primary transition-colors">{genre.name}</h3>
|
|
<p className="text-xs text-muted-foreground line-clamp-1">{genre.description}</p>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|