feat : comment feature
CI / Verify (push) Successful in 2m30s
CI / Build immutable images and deploy (push) Successful in 3m9s

This commit is contained in:
2026-10-08 01:40:08 +07:00 Unverified
parent 8c8815cda5
commit 5812d99715
65 changed files with 27013 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
export default function Loading() {
return <div className="mx-auto w-full max-w-7xl p-4 sm:p-6 lg:p-10"><CommentBoardSkeleton /></div>;
}
+36
View File
@@ -0,0 +1,36 @@
import { Suspense } from "react";
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { connection } from "next/server";
import Image from "@/components/ui/resilient-image";
import { SiteHeader } from "@/components/public/site-header";
import { SiteFooter } from "@/components/public/site-footer";
import { buttonVariants } from "@/components/ui/button";
import { CommentBoardContent } from "@/components/comments/comment-board-content";
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
import { getCachedStygianSchedules } from "@/lib/cache/stygian";
export const instant = false;
export const metadata: Metadata = { title: "Stygian" };
export default async function StygianCommentPage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
await connection();
const query = await searchParams;
const schedules = await getCachedStygianSchedules();
const raw = Array.isArray(query.schedule) ? query.schedule[0] : query.schedule;
const selected = raw ? schedules.find((entry) => entry.scheduleId === Number(raw)) : schedules[0];
if (!selected) notFound();
const target = `stygian:${selected.scheduleId}`;
return <div className="flex min-h-svh flex-col bg-background"><SiteHeader editHref="/admin/stygian" />
<main className="mx-auto flex w-full max-w-7xl flex-1 flex-col gap-6 p-4 sm:p-6 lg:p-10">
<header className="flex items-center gap-3"><Image src="/icon/nav/Comment.webp" alt="" width={48} height={48} className="size-12 object-contain" />
<div><p className="text-sm text-muted-foreground">Stygian · {selected.challengeName}</p><h1 className="font-heading text-3xl font-semibold">ความคิดเห็น</h1></div>
</header>
<nav aria-label="เมนู Stygian" className="flex flex-wrap gap-2"><Link href={`/stygian?schedule=${selected.scheduleId}`} className={buttonVariants({ variant: "outline" })}>ไกด์</Link>
<Link href={`/stygian/comment?schedule=${selected.scheduleId}`} aria-current="page" className={buttonVariants({ variant: "secondary" })}>ความคิดเห็น</Link></nav>
<nav aria-label="รอบ Stygian" className="flex flex-wrap gap-2">{schedules.map((schedule) => <Link key={schedule.scheduleId}
href={`/stygian/comment?schedule=${schedule.scheduleId}`} prefetch={false} aria-current={schedule.scheduleId === selected.scheduleId ? "page" : undefined}
className={buttonVariants({ variant: schedule.scheduleId === selected.scheduleId ? "default" : "outline" })}>{schedule.challengeName}</Link>)}</nav>
<Suspense key={target} fallback={<CommentBoardSkeleton />}><CommentBoardContent target={target} /></Suspense>
</main><SiteFooter /></div>;
}
+15
View File
@@ -1,10 +1,14 @@
import Link from "next/link";
import { Suspense } from "react";
import Image from "@/components/ui/resilient-image";
import { connection } from "next/server";
import { SiteFooter } from "@/components/public/site-footer";
import { SiteHeader } from "@/components/public/site-header";
import { StygianGuide } from "@/components/public/stygian-guide";
import { LiveRefresh } from "@/components/live-refresh";
import { CommentBoardContent } from "@/components/comments/comment-board-content";
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty";
import { buttonVariants } from "@/components/ui/button";
import { getCachedStygianSchedule, getCachedStygianSchedules } from "@/lib/cache/stygian";
@@ -33,9 +37,20 @@ export default async function StygianPage({ searchParams }: { searchParams: Prom
{schedules.length ? <div className="flex flex-col gap-2"><p className="text-sm font-medium">เลือกเวอร์ชัน</p><nav className="flex flex-wrap gap-2" aria-label="เวอร์ชัน Stygian">{schedules.map((entry) =>
<Link key={entry.scheduleId} href={`/stygian?schedule=${entry.scheduleId}`} aria-current={entry.scheduleId === selectedId ? "page" : undefined}
className={buttonVariants({ variant: entry.scheduleId === selectedId ? "default" : "outline", size: "sm", className: "max-sm:min-h-11" })}>{entry.challengeName}</Link>)}</nav></div> : null}
{schedule && <nav aria-label="Stygian navigation" className="flex flex-wrap gap-2">
<Link href={`/stygian?schedule=${schedule.scheduleId}`} aria-current="page" className={buttonVariants({ variant: "secondary" })}>Guide</Link>
<a href="#comments" className={buttonVariants({ variant: "outline" })}>
<Image src="/icon/nav/Comment.webp" alt="" width={20} height={20} className="size-5 object-contain" />ความคิดเห็น
</a>
</nav>}
{schedule && catalog ? <StygianGuide schedule={schedule} catalog={catalog} /> : (
<Empty className="border"><EmptyHeader><EmptyTitle>ยังไม่มีไกด์ Stygian</EmptyTitle><EmptyDescription>กำลังเตรียมข้อมูล กลับมาอ่านได้ภายหลัง หรือเลือกไกด์ตัวละครระหว่างรอ</EmptyDescription></EmptyHeader><Link href="/" className={buttonVariants({ variant: "outline" })}>เลือกไกด์ตัวละคร</Link></Empty>
)}
{schedule && <section id="comments" aria-label="ความคิดเห็น Stygian" className="scroll-mt-24 border-t pt-8">
<Suspense key={`stygian:${schedule.scheduleId}`} fallback={<CommentBoardSkeleton />}>
<CommentBoardContent target={`stygian:${schedule.scheduleId}`} />
</Suspense>
</section>}
</main>
<SiteFooter />
</div>