Files
buzz-sheet/components/comments/comment-board.tsx
T
gunshiz 9bc710c94b
CI / Verify (push) Successful in 1m50s
CI / Build immutable images and deploy (push) Successful in 2m59s
feat : ask for noti
2026-10-08 02:23:59 +07:00

63 lines
5.1 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { RefreshCwIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Empty, EmptyHeader, EmptyTitle, EmptyDescription } from "@/components/ui/empty";
import type { CommentPage } from "@/lib/comments/types";
import { CommentPushPrompt } from "./comment-push-prompt";
import { CommentComposer } from "./comment-composer";
import { CommentCard, CommentThread } from "./comment-card";
import { commentSignInHref } from "./client";
import { CommentSkeleton } from "./comment-skeleton";
import { useCommentEvents } from "./use-comment-events";
import { useCommentFeed } from "./use-comment-feed";
export function CommentBoard({ target, initial }: { target: string; initial: CommentPage }) {
const router = useRouter();
const [sort, setSort] = useState("top");
const [linkedReply, setLinkedReply] = useState<string | null>(null);
const [linkedThread, setLinkedThread] = useState<string | null>(null);
const feed = useCommentFeed(`/api/comments?target=${encodeURIComponent(target)}&sort=${sort}`, initial);
useCommentEvents({ target }, (changed) => feed.refresh({ silent: true, forceRevision: changed }));
useEffect(() => {
const showThread = () => {
const match = /^#comment-([0-9a-f-]{36})$/i.exec(window.location.hash);
if (match) {
setLinkedThread(match[1]);
const reply = new URLSearchParams(window.location.search).get("reply");
setLinkedReply(reply && /^[0-9a-f-]{36}$/i.test(reply) ? reply : null);
}
};
showThread();
window.addEventListener("hashchange", showThread);
return () => window.removeEventListener("hashchange", showThread);
}, []);
return <div className="flex flex-col gap-6 py-2">
{feed.page.viewer && <CommentPushPrompt userId={feed.page.viewer.id} admin={feed.page.viewer.admin} />}
<div className="flex items-start gap-3"><Avatar className="size-10 shrink-0"><AvatarImage src={feed.page.viewer?.image ?? undefined} alt={feed.page.viewer?.name ?? "โปรไฟล์"} /><AvatarFallback>{feed.page.viewer?.name?.slice(0, 2) || "คุณ"}</AvatarFallback></Avatar><div className="min-w-0 flex-1">{feed.page.viewer ? <CommentComposer target={target} onSaved={() => void feed.refresh()} />
: <div className="flex flex-col items-start gap-2"><p className="text-sm text-muted-foreground">เข้าสู่ระบบเพื่อแสดงความคิดเห็น ตอบกลับ และกดถูกใจ</p><Button onClick={() => router.push(commentSignInHref())}>เข้าสู่ระบบ</Button></div>}</div></div>
<div className="order-first flex flex-wrap items-center gap-3"><h2 className="font-heading text-xl font-semibold">ความคิดเห็น</h2>
<ToggleGroup value={[sort]} onValueChange={(values) => { if (values[0]) setSort(values[0]); }} variant="outline" aria-label="เรียงความคิดเห็น">
<ToggleGroupItem value="newest">ล่าสุด</ToggleGroupItem><ToggleGroupItem value="top">ยอดนิยม</ToggleGroupItem>
</ToggleGroup>
<Button variant="ghost" className="ml-auto rounded-full" aria-label="รีเฟรชความคิดเห็น" size="icon" disabled={feed.busy} onClick={() => void feed.refresh()}><RefreshCwIcon /></Button>
</div>
{feed.error && <Alert variant="destructive"><AlertDescription>{feed.error}</AlertDescription></Alert>}
{feed.page.items.filter((item) => item.id !== linkedThread).map((item) => <CommentCard key={item.id} item={item} viewer={feed.page.viewer} onChange={() => feed.refresh({ silent: true })} revision={feed.revision} />)}
{!feed.page.items.length && !feed.busy && !feed.error && <Empty><EmptyHeader><EmptyTitle>ยังไม่มีความคิดเห็น</EmptyTitle><EmptyDescription>เป็นคนแรกที่ร่วมพูดคุยเกี่ยวกับไกด์นี้</EmptyDescription></EmptyHeader></Empty>}
{feed.busy && <CommentSkeleton count={feed.page.items.length ? 1 : 3} />}
{feed.page.nextCursor && <Button variant="outline" disabled={feed.busy} onClick={() => void feed.more()}>ดูความคิดเห็นเพิ่มเติม</Button>}
{linkedThread && <section aria-label="การสนทนาที่เลือก" className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-2"><h3 className="font-semibold">การสนทนา</h3><Button variant="ghost" onClick={() => { setLinkedThread(null); setLinkedReply(null); window.history.replaceState(null, "", window.location.pathname + window.location.search); }}>ปิดการสนทนา</Button></div>
<CommentThread rootId={linkedThread} focusReplyId={linkedReply ?? undefined} expectedTarget={target} onChange={() => feed.refresh({ silent: true })} />
</section>}
</div>;
}