import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { replyToThread } from "@/actions/social"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { formatDate } from "@/lib/format-date"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; export const revalidate = 60; type SearchParams = Promise<{ replied?: string; error?: string }>; function feedbackStyle(tone: "success" | "error"): CSSProperties { const accent = tone === "error" ? "var(--color-danger)" : "var(--color-primary)"; return { margin: 0, padding: "0.85rem 1rem", borderRadius: "var(--radius-md)", border: `1px solid ${accent}`, color: "var(--color-text-readable, var(--color-text))", fontSize: "0.9rem", fontWeight: 600, background: "var(--color-surface)", borderLeft: `4px solid ${accent}`, }; } export default async function GuildForumThreadPage({ params, searchParams, }: { params: Promise<{ id: string; threadId: string }>; searchParams: SearchParams; }) { const t = await getTranslations("pages.guildForumThread"); const { id, threadId: threadIdRaw } = await params; const { replied, error } = await searchParams; const guildId = Number(id); const threadId = Number(threadIdRaw); if (!Number.isInteger(guildId) || guildId <= 0) notFound(); if (!Number.isInteger(threadId) || threadId <= 0) notFound(); const session = await auth(); const viewerId = Number(session?.user?.id); const isLoggedIn = Number.isInteger(viewerId) && viewerId > 0; let guild: { id: number; name: string } | null = null; let thread: { id: number; subject: string | null; locked: number | null; pinned: number | null; openerId: number | null; } | null = null; let posts: { id: number; userId: number; message: string; createdAt: number; }[] = []; try { [guild, thread] = await Promise.all([ prisma.guilds.findUnique({ where: { id: guildId }, select: { id: true, name: true }, }), prisma.guildsForumsThreads.findFirst({ where: { id: threadId, guildId, state: 0 }, select: { id: true, subject: true, locked: true, pinned: true, openerId: true, }, }), ]); } catch { guild = null; thread = null; } if (!guild || !thread) notFound(); try { posts = await prisma.guildsForumsComments.findMany({ where: { threadId: thread.id, state: 0 }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], select: { id: true, userId: true, message: true, createdAt: true, }, }); } catch { posts = []; } const authorIds = Array.from( new Set(posts.map((p) => p.userId).filter((v) => v > 0)), ); let users: { id: number; username: string }[] = []; try { users = authorIds.length ? await prisma.user.findMany({ where: { id: { in: authorIds } }, select: { id: true, username: true }, }) : []; } catch { users = []; } const usernameById = new Map(users.map((u) => [u.id, u.username])); const isLocked = !!thread.locked; const canReply = isLoggedIn && !isLocked; const errorMessage = error === "invalid" ? t("errors.invalid") : error === "not_found" ? t("errors.notFound") : error === "locked" ? t("errors.locked") : error === "ratelimit" ? t("errors.ratelimit") : error ? t("errors.error") : null; return (
{replied === "1" ? (
{t("success.replied")}
) : null} {errorMessage ? (
{errorMessage}
) : null}

{t("backToForum")}

{thread.pinned ? ( 📌 {t("pinned")} ) : null} {isLocked ? ( 🔒 {t("locked")} ) : null}
{posts.length === 0 ? ( {t("postsEmpty")} ) : ( )} {canReply ? (