Files
EpicNext-Cms/src/app/admin/help-tickets/[id]/page.tsx
T
SimoandCursor 3ac5d6f4f6
CI / check (push) Successful in 25s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m8s
chore(ops): strip redundant force-dynamic and probe Redis in ops health
Co-authored-by: Cursor <[email protected]>
2026-07-30 21:33:40 +02:00

157 lines
4.1 KiB
TypeScript

import { notFound, redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
import { getMinStaffRank } from "@/lib/admin/min-staff-rank";
import { positiveBigInt } from "@/lib/api";
import { activeBanWhere } from "@/lib/bans";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { AdminHelpTicketDetail } from "./admin-help-ticket-detail";
export default async function AdminHelpTicketDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.TICKETS_VIEW, session.user.rank)) {
redirect("/admin");
}
const t = await getTranslations("pages.admin.helpTickets");
const { id } = await params;
const ticketId = positiveBigInt(id);
if (!ticketId) notFound();
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
where: { id: ticketId },
select: {
id: true,
userId: true,
categoryId: true,
title: true,
content: true,
open: true,
createdAt: true,
updatedAt: true,
},
});
if (!ticket) notFound();
const replies = await prisma.websiteHelpCenterTicketReplies.findMany({
where: { ticketId: ticket.id },
orderBy: { id: "asc" },
select: {
id: true,
userId: true,
content: true,
createdAt: true,
},
});
const authorIds = [
...new Set([
...(ticket.userId != null ? [ticket.userId] : []),
...replies.map((r) => r.userId),
]),
];
const [users, category, activeBan] = await Promise.all([
authorIds.length > 0
? prisma.user.findMany({
where: { id: { in: authorIds } },
select: {
id: true,
username: true,
rank: true,
mail: true,
},
})
: Promise.resolve([]),
ticket.categoryId != null
? prisma.websiteHelpCenterCategories.findUnique({
where: { id: ticket.categoryId },
select: { name: true },
})
: Promise.resolve(null),
ticket.userId != null
? prisma.ban.findFirst({
where: { userId: ticket.userId, ...activeBanWhere() },
select: { id: true },
})
: Promise.resolve(null),
]);
const userById = new Map(users.map((u) => [u.id, u]));
const openerId = ticket.userId;
const minStaffRank = await getMinStaffRank();
const messages = [
{
key: "opening",
userId: openerId ?? 0,
username:
openerId != null
? (userById.get(openerId)?.username ?? `#${openerId}`)
: "—",
isStaff: false,
content: ticket.content,
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
},
...replies.map((r) => {
const user = userById.get(r.userId);
const isStaff =
r.userId !== openerId && (user?.rank ?? 0) >= minStaffRank;
return {
key: String(r.id),
userId: r.userId,
username: user?.username ?? `#${r.userId}`,
isStaff,
content: r.content,
createdAt: (r.createdAt ?? new Date()).toISOString(),
};
}),
];
const creator = openerId != null ? userById.get(openerId) : undefined;
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
const canLiftBan = canAccess(permissions, PERMS.USERS_BAN, session.user.rank);
const isBanAppeal =
/ban\s*appeal/i.test(ticket.title) ||
/ban\s*appeal/i.test(category?.name ?? "");
return (
<div className="space-y-6">
<TicketQueueBanner
hint={t("queueHint")}
otherHref="/admin/tickets"
otherLabel={t("queueOtherCta")}
/>
<AdminHelpTicketDetail
ticket={{
id: String(ticket.id),
title: ticket.title,
open: ticket.open,
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
updatedAt: (ticket.updatedAt ?? new Date()).toISOString(),
categoryName: category?.name ?? (isBanAppeal ? "Ban appeal" : null),
creator: creator
? {
id: creator.id,
username: creator.username,
rank: creator.rank,
mail: creator.mail ?? "",
}
: null,
}}
messages={messages}
canEdit={canEdit}
canLiftBan={canLiftBan}
hasActiveBan={!!activeBan}
/>
</div>
);
}