import { asc, eq, inArray } from "drizzle-orm"; import { notFound, redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { closeHelpTicket, replyHelpTicket } from "@/actions/help-tickets"; import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; import { positiveBigInt } from "@/lib/api"; import { auth } from "@/lib/auth"; import { db, User, WebsiteHelpCenterTicketReplies, WebsiteHelpCenterTickets, } from "@/lib/db"; import { formatDate } from "@/lib/format-date"; type SearchParams = Promise<{ replied?: string; closed?: 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 HelpTicketDetailPage({ params, searchParams, }: { params: Promise<{ id: string }>; searchParams: SearchParams; }) { const session = await auth(); if (!session?.user?.id) redirect("/login"); const t = await getTranslations("pages.helpTicketDetail"); const { id } = await params; const { replied, closed, error } = await searchParams; const ticketId = positiveBigInt(id); if (!ticketId) notFound(); const userId = Number(session.user.id); let ticket: { id: bigint; userId: number | null; title: string; content: string; open: boolean; createdAt: Date | null; } | null = null; let replies: { id: bigint; userId: number; content: string; createdAt: Date | null; }[] = []; try { const [row] = await db .select({ id: WebsiteHelpCenterTickets.id, userId: WebsiteHelpCenterTickets.userId, title: WebsiteHelpCenterTickets.title, content: WebsiteHelpCenterTickets.content, open: WebsiteHelpCenterTickets.open, createdAt: WebsiteHelpCenterTickets.createdAt, }) .from(WebsiteHelpCenterTickets) .where(eq(WebsiteHelpCenterTickets.id, ticketId)) .limit(1); ticket = row ?? null; } catch { ticket = null; } if (!ticket || ticket.userId !== userId) notFound(); try { replies = await db .select({ id: WebsiteHelpCenterTicketReplies.id, userId: WebsiteHelpCenterTicketReplies.userId, content: WebsiteHelpCenterTicketReplies.content, createdAt: WebsiteHelpCenterTicketReplies.createdAt, }) .from(WebsiteHelpCenterTicketReplies) .where(eq(WebsiteHelpCenterTicketReplies.ticketId, ticket.id)) .orderBy(asc(WebsiteHelpCenterTicketReplies.id)); } catch { replies = []; } const authorIds = [...new Set([userId, ...replies.map((r) => r.userId)])]; let users: { id: number; username: string }[] = []; try { users = await db .select({ id: User.id, username: User.username }) .from(User) .where(inArray(User.id, authorIds)); } catch { users = []; } const usernameById = new Map(users.map((u) => [u.id, u.username])); const openerName = usernameById.get(userId) ?? t("userNumber", { id: userId }); const errorMessage = error === "invalid" ? t("errors.invalid") : error === "not_found" ? t("errors.notFound") : error === "closed_ticket" ? t("errors.closedTicket") : error === "moderated" ? t("errors.moderated") : error === "ratelimit" ? t("errors.ratelimit") : error ? t("errors.error") : null; type MessageRow = { key: string; authorId: number; content: string; createdAt: Date | null; }; const messages: MessageRow[] = [ { key: "opening", authorId: userId, content: ticket.content, createdAt: ticket.createdAt, }, ...replies.map((r) => ({ key: String(r.id), authorId: r.userId, content: r.content, createdAt: r.createdAt, })), ]; return (
{replied === "1" ? (
{t("success.replied")}
) : null} {closed === "1" ? (
{t("success.closed")}
) : null} {errorMessage ? (
{errorMessage}
) : null}

{t("backToList")}

{ticket.open ? t("statusOpen") : t("statusClosed")} {ticket.open ? ( <>