From b6b8625246375b9579048ee4e48de19a397addee Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 28 Jul 2026 20:26:57 +0200 Subject: [PATCH] feat(mod): help-center tickets queue with reduced PII Co-authored-by: Cursor --- src/actions/admin-help-tickets.ts | 10 +- .../[id]/admin-help-ticket-detail.tsx | 15 +- src/app/admin/help-tickets/[id]/page.tsx | 6 +- .../admin/help-tickets/help-tickets-table.tsx | 4 +- src/app/mod/help-tickets/[id]/page.tsx | 129 ++++++++++++ src/app/mod/help-tickets/page.tsx | 184 ++++++++++++++++++ src/app/mod/layout.tsx | 17 +- src/app/mod/page.tsx | 86 +++++--- src/lib/admin-operations-contract.test.ts | 1 + src/messages/bg.json | 6 +- src/messages/cs.json | 6 +- src/messages/da.json | 6 +- src/messages/de.json | 6 +- src/messages/el.json | 6 +- src/messages/en.json | 6 +- src/messages/es.json | 6 +- src/messages/fr.json | 6 +- src/messages/hr.json | 6 +- src/messages/hu.json | 6 +- src/messages/it.json | 6 +- src/messages/nl.json | 6 +- src/messages/no.json | 6 +- src/messages/pl.json | 6 +- src/messages/pt.json | 6 +- src/messages/ro.json | 6 +- src/messages/ru.json | 6 +- src/messages/sk.json | 6 +- src/messages/sr.json | 6 +- src/messages/sv.json | 6 +- src/messages/tr.json | 6 +- src/messages/uk.json | 6 +- 31 files changed, 502 insertions(+), 82 deletions(-) create mode 100644 src/app/mod/help-tickets/[id]/page.tsx create mode 100644 src/app/mod/help-tickets/page.tsx diff --git a/src/actions/admin-help-tickets.ts b/src/actions/admin-help-tickets.ts index f6a5358f..5973520a 100644 --- a/src/actions/admin-help-tickets.ts +++ b/src/actions/admin-help-tickets.ts @@ -25,12 +25,16 @@ function revalidateHelpCenterTicketPaths(ticketId: bigint) { const id = String(ticketId); revalidatePath("/admin/help-tickets"); revalidatePath(`/admin/help-tickets/${id}`); + revalidatePath("/mod/help-tickets"); + revalidatePath(`/mod/help-tickets/${id}`); revalidatePath("/help/tickets"); revalidatePath(`/help/tickets/${id}`); } +const HELP_TICKET_EDIT = [PERMS.TICKETS_EDIT, PERMS.MOD_TICKETS_EDIT] as const; + export const replyHelpCenterTicket = adminAction( - { permission: PERMS.TICKETS_EDIT, schema: replyHelpCenterTicketSchema }, + { permission: HELP_TICKET_EDIT, schema: replyHelpCenterTicketSchema }, async (ctx) => { const ticketId = ctx.data.ticketId; const ticket = await prisma.websiteHelpCenterTickets.findUnique({ @@ -72,7 +76,7 @@ export const replyHelpCenterTicket = adminAction( ); export const closeHelpCenterTicket = adminAction( - { permission: PERMS.TICKETS_EDIT, schema: helpCenterTicketIdSchema }, + { permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema }, async (ctx) => { const ticketId = ctx.data.ticketId; const ticket = await prisma.websiteHelpCenterTickets.findUnique({ @@ -104,7 +108,7 @@ export const closeHelpCenterTicket = adminAction( ); export const reopenHelpCenterTicket = adminAction( - { permission: PERMS.TICKETS_EDIT, schema: helpCenterTicketIdSchema }, + { permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema }, async (ctx) => { const ticketId = ctx.data.ticketId; const ticket = await prisma.websiteHelpCenterTickets.findUnique({ diff --git a/src/app/admin/help-tickets/[id]/admin-help-ticket-detail.tsx b/src/app/admin/help-tickets/[id]/admin-help-ticket-detail.tsx index 082f5af0..85e9f91a 100644 --- a/src/app/admin/help-tickets/[id]/admin-help-ticket-detail.tsx +++ b/src/app/admin/help-tickets/[id]/admin-help-ticket-detail.tsx @@ -42,10 +42,15 @@ export function AdminHelpTicketDetail({ ticket, messages: initialMessages, canEdit, + listHref = "/admin/help-tickets", + hideRequesterContact = false, }: { ticket: HelpTicketInfo; messages: ThreadMessage[]; canEdit: boolean; + listHref?: string; + /** Hide email/IP-style contact for mod-lite surfaces. */ + hideRequesterContact?: boolean; }) { const t = useTranslations("pages.admin.helpTickets"); const [messages, setMessages] = useState(initialMessages); @@ -92,7 +97,7 @@ export function AdminHelpTicketDetail({ return (
- + @@ -241,7 +246,11 @@ export function AdminHelpTicketDetail({ @@ -250,7 +259,7 @@ export function AdminHelpTicketDetail({ {t("rankLabel", { rank: ticket.creator.rank })} - {ticket.creator.mail ? ( + {!hideRequesterContact && ticket.creator.mail ? (
{ticket.creator.mail} diff --git a/src/app/admin/help-tickets/[id]/page.tsx b/src/app/admin/help-tickets/[id]/page.tsx index 573c98d3..81bc6b67 100644 --- a/src/app/admin/help-tickets/[id]/page.tsx +++ b/src/app/admin/help-tickets/[id]/page.tsx @@ -1,4 +1,5 @@ import { notFound, redirect } from "next/navigation"; +import { getMinStaffRank } from "@/lib/admin/min-staff-rank"; import { positiveBigInt } from "@/lib/api"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; @@ -6,8 +7,6 @@ import { AdminHelpTicketDetail } from "./admin-help-ticket-detail"; export const dynamic = "force-dynamic"; -const STAFF_RANK_THRESHOLD = 4; - export default async function AdminHelpTicketDetailPage({ params, }: { @@ -70,6 +69,7 @@ export default async function AdminHelpTicketDetailPage({ const userById = new Map(users.map((u) => [u.id, u])); const openerId = ticket.userId; + const minStaffRank = await getMinStaffRank(); const messages = [ { @@ -86,7 +86,7 @@ export default async function AdminHelpTicketDetailPage({ ...replies.map((r) => { const user = userById.get(r.userId); const isStaff = - r.userId !== openerId && (user?.rank ?? 0) >= STAFF_RANK_THRESHOLD; + r.userId !== openerId && (user?.rank ?? 0) >= minStaffRank; return { key: String(r.id), userId: r.userId, diff --git a/src/app/admin/help-tickets/help-tickets-table.tsx b/src/app/admin/help-tickets/help-tickets-table.tsx index 30cc90e4..2fe6b46d 100644 --- a/src/app/admin/help-tickets/help-tickets-table.tsx +++ b/src/app/admin/help-tickets/help-tickets-table.tsx @@ -19,8 +19,10 @@ export interface HelpTicketRow { export function HelpTicketsTable({ data, + basePath = "/admin/help-tickets", }: { data: PaginatedResult; + basePath?: string; }) { const t = useTranslations("pages.admin.helpTickets"); @@ -36,7 +38,7 @@ export function HelpTicketsTable({ #{row.id} {row.title} diff --git a/src/app/mod/help-tickets/[id]/page.tsx b/src/app/mod/help-tickets/[id]/page.tsx new file mode 100644 index 00000000..a6dbe421 --- /dev/null +++ b/src/app/mod/help-tickets/[id]/page.tsx @@ -0,0 +1,129 @@ +import { notFound } from "next/navigation"; +import { AdminHelpTicketDetail } from "@/app/admin/help-tickets/[id]/admin-help-ticket-detail"; +import { requireModPermission } from "@/lib/admin/guard"; +import { getMinStaffRank } from "@/lib/admin/min-staff-rank"; +import { positiveBigInt } from "@/lib/api"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +export default async function ModHelpTicketDetailPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + await requireModPermission([PERMS.MOD_TICKETS_VIEW, PERMS.TICKETS_VIEW]); + + const { session, permissions } = await getAdminContext(); + 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, + 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 = + authorIds.length > 0 + ? await prisma.user.findMany({ + where: { id: { in: authorIds } }, + select: { + id: true, + username: true, + rank: true, + mail: true, + }, + }) + : []; + + 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.MOD_TICKETS_EDIT, session.user.rank) || + canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank); + + return ( + + ); +} diff --git a/src/app/mod/help-tickets/page.tsx b/src/app/mod/help-tickets/page.tsx new file mode 100644 index 00000000..92ee8636 --- /dev/null +++ b/src/app/mod/help-tickets/page.tsx @@ -0,0 +1,184 @@ +import { getTranslations } from "next-intl/server"; +import { + type HelpTicketRow, + HelpTicketsTable, +} from "@/app/admin/help-tickets/help-tickets-table"; +import { StatusCard } from "@/components/admin/dashboard"; +import type { Prisma } from "@/generated/prisma/client"; +import { requireModPermission } from "@/lib/admin/guard"; +import { calcPagination, parseListParams } from "@/lib/admin-helpers"; +import { formatDate } from "@/lib/format-date"; +import { PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +export default async function ModHelpTicketsPage({ + searchParams, +}: { + searchParams: Promise>; +}) { + await requireModPermission([PERMS.MOD_TICKETS_VIEW, PERMS.TICKETS_VIEW]); + + const t = await getTranslations("pages.admin.helpTickets"); + const raw = await searchParams; + const parsed = parseListParams(new URLSearchParams(raw)); + const statusFilter = raw.filter_status || "open"; + + const conditions: Prisma.WebsiteHelpCenterTicketsWhereInput[] = []; + + if (statusFilter === "open") { + conditions.push({ open: true }); + } else if (statusFilter === "closed") { + conditions.push({ open: false }); + } + + if (parsed.search.trim()) { + const q = parsed.search.trim(); + const asId = /^\d+$/.test(q) ? BigInt(q) : null; + const matchingUsers = await prisma.user + .findMany({ + where: { username: { contains: q } }, + select: { id: true }, + take: 50, + }) + .catch(() => []); + + conditions.push({ + OR: [ + { title: { contains: q } }, + { content: { contains: q } }, + ...(matchingUsers.length > 0 + ? [{ userId: { in: matchingUsers.map((u) => u.id) } }] + : []), + ...(asId !== null ? [{ id: asId }] : []), + ], + }); + } + + const where: Prisma.WebsiteHelpCenterTicketsWhereInput = + conditions.length === 0 + ? {} + : conditions.length === 1 + ? conditions[0] + : { AND: conditions }; + + const SORT_MAP: Record< + string, + Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput + > = { + title: { title: "asc" }, + open: { open: "desc" }, + user: { userId: "asc" }, + date: { createdAt: "desc" }, + updated: { updatedAt: "desc" }, + id: { id: "desc" }, + }; + + const baseOrder = SORT_MAP[parsed.sort ?? "updated"] ?? { updatedAt: "desc" }; + const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; + const orderBy = { + [orderField]: parsed.order, + } as Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput; + + const [total, openCount, closedCount] = await Promise.all([ + prisma.websiteHelpCenterTickets.count({ where }).catch(() => 0), + prisma.websiteHelpCenterTickets + .count({ where: { open: true } }) + .catch(() => 0), + prisma.websiteHelpCenterTickets + .count({ where: { open: false } }) + .catch(() => 0), + ]); + + const pagination = calcPagination(total, parsed.page, parsed.perPage); + + const tickets = await prisma.websiteHelpCenterTickets + .findMany({ + where, + orderBy, + skip: pagination.offset, + take: pagination.perPage, + select: { + id: true, + userId: true, + title: true, + open: true, + createdAt: true, + updatedAt: true, + }, + }) + .catch(() => []); + + const ticketIds = tickets.map((ticket) => ticket.id); + const userIds = [ + ...new Set( + tickets + .map((ticket) => ticket.userId) + .filter((id): id is number => id != null), + ), + ]; + + const [replyGroups, users] = await Promise.all([ + ticketIds.length > 0 + ? prisma.websiteHelpCenterTicketReplies + .groupBy({ + by: ["ticketId"], + where: { ticketId: { in: ticketIds } }, + _count: true, + }) + .catch(() => []) + : Promise.resolve([]), + userIds.length > 0 + ? prisma.user + .findMany({ + where: { id: { in: userIds } }, + select: { id: true, username: true }, + }) + .catch(() => []) + : Promise.resolve([]), + ]); + + const replyCountByTicket = new Map( + replyGroups.map((g) => [String(g.ticketId), g._count]), + ); + const usernameById = new Map(users.map((u) => [u.id, u.username])); + + const rows: HelpTicketRow[] = tickets.map((ticket) => ({ + id: String(ticket.id), + title: ticket.title, + user: + ticket.userId != null + ? (usernameById.get(ticket.userId) ?? `#${ticket.userId}`) + : "—", + userId: ticket.userId, + replies: replyCountByTicket.get(String(ticket.id)) ?? 0, + open: ticket.open, + date: formatDate(ticket.createdAt, "date"), + updated: formatDate(ticket.updatedAt, "date"), + })); + + return ( +
+
+ + +
+ + {total === 0 && !parsed.search && statusFilter === "open" ? ( +
{t("noTickets")}
+ ) : ( + + )} +
+ ); +} diff --git a/src/app/mod/layout.tsx b/src/app/mod/layout.tsx index 8c7104c4..b98c8ccf 100644 --- a/src/app/mod/layout.tsx +++ b/src/app/mod/layout.tsx @@ -1,4 +1,13 @@ -import { Ban, Gavel, LogOut, Radio, Shield, Ticket, Users } from "lucide-react"; +import { + Ban, + Gavel, + LifeBuoy, + LogOut, + Radio, + Shield, + Ticket, + Users, +} from "lucide-react"; import Link from "next/link"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; @@ -59,6 +68,12 @@ export default async function ModLayout({ children }: { children: ReactNode }) { icon: Ticket, show: showTickets, }, + { + href: "/mod/help-tickets", + label: t("nav.helpTickets"), + icon: LifeBuoy, + show: showTickets, + }, { href: "/mod/actions", label: t("nav.actions"), diff --git a/src/app/mod/page.tsx b/src/app/mod/page.tsx index bc1d96cb..4282f7d8 100644 --- a/src/app/mod/page.tsx +++ b/src/app/mod/page.tsx @@ -1,4 +1,12 @@ -import { Ban, Gavel, Radio, Shield, Ticket, Users } from "lucide-react"; +import { + Ban, + Gavel, + LifeBuoy, + Radio, + Shield, + Ticket, + Users, +} from "lucide-react"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { StatsCard } from "@/components/admin/stats-card"; @@ -30,24 +38,30 @@ export default async function ModDashboardPage() { canAccess(permissions, PERMS.MOD_TEAM_VIEW, rank) || canAccess(permissions, PERMS.MODERATION_VIEW, rank); - const [openCfh, todayBans, modsOnline, openTickets] = await Promise.all([ - showCfh - ? prisma.supportTickets.count({ where: { state: 0 } }).catch(() => 0) - : Promise.resolve(0), - showBans - ? prisma.ban - .count({ where: { timestamp: { gte: todayStart } } }) - .catch(() => 0) - : Promise.resolve(0), - prisma.user - .count({ where: { online: "1", rank: { gte: minStaffRank } } }) - .catch(() => 0), - showTickets - ? prisma.websiteTicket - .count({ where: { status: { not: "closed" } } }) - .catch(() => 0) - : Promise.resolve(0), - ]); + const [openCfh, todayBans, modsOnline, openTickets, openHelpTickets] = + await Promise.all([ + showCfh + ? prisma.supportTickets.count({ where: { state: 0 } }).catch(() => 0) + : Promise.resolve(0), + showBans + ? prisma.ban + .count({ where: { timestamp: { gte: todayStart } } }) + .catch(() => 0) + : Promise.resolve(0), + prisma.user + .count({ where: { online: "1", rank: { gte: minStaffRank } } }) + .catch(() => 0), + showTickets + ? prisma.websiteTicket + .count({ where: { status: { not: "closed" } } }) + .catch(() => 0) + : Promise.resolve(0), + showTickets + ? prisma.websiteHelpCenterTickets + .count({ where: { open: true } }) + .catch(() => 0) + : Promise.resolve(0), + ]); return (
@@ -75,6 +89,14 @@ export default async function ModDashboardPage() { href="/mod/tickets" /> ) : null} + {showTickets ? ( + } + href="/mod/help-tickets" + /> + ) : null} {showBans ? ( ) : null} {showTickets ? ( -
  • - - {t("nav.tickets")} - -
  • + <> +
  • + + {t("nav.tickets")} + +
  • +
  • + + {t("nav.helpTickets")} + +
  • + ) : null} {showActions ? (
  • diff --git a/src/lib/admin-operations-contract.test.ts b/src/lib/admin-operations-contract.test.ts index 19ed9f8c..ac282399 100644 --- a/src/lib/admin-operations-contract.test.ts +++ b/src/lib/admin-operations-contract.test.ts @@ -49,6 +49,7 @@ const MOD_ROUTES: Array<[string, string]> = [ ["actions", "PERMS.MOD_ACTIONS"], ["bans", "PERMS.MOD_BANS_VIEW"], ["tickets", "PERMS.MOD_TICKETS_VIEW"], + ["help-tickets", "PERMS.MOD_TICKETS_VIEW"], ["team", "PERMS.MOD_TEAM_VIEW"], ]; diff --git a/src/messages/bg.json b/src/messages/bg.json index f85fd185..9db6cffe 100644 --- a/src/messages/bg.json +++ b/src/messages/bg.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/cs.json b/src/messages/cs.json index 0808f164..56d97732 100644 --- a/src/messages/cs.json +++ b/src/messages/cs.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/da.json b/src/messages/da.json index 222a158b..db04378d 100644 --- a/src/messages/da.json +++ b/src/messages/da.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/de.json b/src/messages/de.json index bcf1781c..80129071 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/el.json b/src/messages/el.json index 2973315c..be6d909f 100644 --- a/src/messages/el.json +++ b/src/messages/el.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/en.json b/src/messages/en.json index 7ae4d09a..d9a5f79a 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -3008,7 +3008,8 @@ "bans": "Bans", "fullAdmin": "Full admin", "backToSite": "Back to site", - "tickets": "Tickets", + "tickets": "CMS tickets", + "helpTickets": "Help-center tickets", "team": "Team" }, "stats": { @@ -3017,7 +3018,8 @@ "modsOnline": "Mods online", "quickActions": "Quick actions", "quickActionsHint": "Kick, mute, alert", - "openTickets": "Open tickets", + "openTickets": "Open CMS tickets", + "openHelpTickets": "Open help tickets", "team": "Mod team", "teamHint": "Online staff & workload" } diff --git a/src/messages/es.json b/src/messages/es.json index aa7efe20..a46b043e 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/fr.json b/src/messages/fr.json index ea7bcf4f..2d96cb73 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/hr.json b/src/messages/hr.json index bac4c8ad..5ca2b1cf 100644 --- a/src/messages/hr.json +++ b/src/messages/hr.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/hu.json b/src/messages/hu.json index a9aa845d..08901750 100644 --- a/src/messages/hu.json +++ b/src/messages/hu.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/it.json b/src/messages/it.json index 3a2a0f9e..f4122a69 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -3015,7 +3015,8 @@ "bans": "Ban", "fullAdmin": "Admin completo", "backToSite": "Torna al sito", - "tickets": "Ticket", + "tickets": "Ticket CMS", + "helpTickets": "Ticket help center", "team": "Team" }, "stats": { @@ -3024,7 +3025,8 @@ "modsOnline": "Mod online", "quickActions": "Azioni rapide", "quickActionsHint": "Kick, mute, alert", - "openTickets": "Ticket aperti", + "openTickets": "Ticket CMS aperti", + "openHelpTickets": "Help ticket aperti", "team": "Team mod", "teamHint": "Staff online e carico" } diff --git a/src/messages/nl.json b/src/messages/nl.json index 85ce59a6..c61e2197 100644 --- a/src/messages/nl.json +++ b/src/messages/nl.json @@ -3013,7 +3013,8 @@ "bans": "Bans", "fullAdmin": "Volledige admin", "backToSite": "Terug naar site", - "tickets": "Tickets", + "tickets": "CMS-tickets", + "helpTickets": "Helpcenter-tickets", "team": "Team" }, "stats": { @@ -3022,7 +3023,8 @@ "modsOnline": "Mods online", "quickActions": "Snelle acties", "quickActionsHint": "Kick, mute, alert", - "openTickets": "Open tickets", + "openTickets": "Open CMS-tickets", + "openHelpTickets": "Open help-tickets", "team": "Mod team", "teamHint": "Online staff & werklast" } diff --git a/src/messages/no.json b/src/messages/no.json index 293f104f..37ec9114 100644 --- a/src/messages/no.json +++ b/src/messages/no.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/pl.json b/src/messages/pl.json index f0e62e01..ec5a5400 100644 --- a/src/messages/pl.json +++ b/src/messages/pl.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/pt.json b/src/messages/pt.json index 3c755610..be2977ff 100644 --- a/src/messages/pt.json +++ b/src/messages/pt.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/ro.json b/src/messages/ro.json index afee8c92..7da30edd 100644 --- a/src/messages/ro.json +++ b/src/messages/ro.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/ru.json b/src/messages/ru.json index 99e3d7e2..c4cdaf8f 100644 --- a/src/messages/ru.json +++ b/src/messages/ru.json @@ -3005,7 +3005,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3015,7 +3016,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/sk.json b/src/messages/sk.json index 41dc55a0..67d336fc 100644 --- a/src/messages/sk.json +++ b/src/messages/sk.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/sr.json b/src/messages/sr.json index 41fac32c..486d06d2 100644 --- a/src/messages/sr.json +++ b/src/messages/sr.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/sv.json b/src/messages/sv.json index 74dcb1a0..7fdc3251 100644 --- a/src/messages/sv.json +++ b/src/messages/sv.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/tr.json b/src/messages/tr.json index 68055874..d87ff82f 100644 --- a/src/messages/tr.json +++ b/src/messages/tr.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } }, diff --git a/src/messages/uk.json b/src/messages/uk.json index 8188b74e..429140ce 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -3007,7 +3007,8 @@ "fullAdmin": "Full admin", "backToSite": "Back to site", "tickets": "Tickets", - "team": "Team" + "team": "Team", + "helpTickets": "Help-center tickets" }, "stats": { "openCfh": "Open CFH", @@ -3017,7 +3018,8 @@ "quickActionsHint": "Kick, mute, alert", "openTickets": "Open tickets", "team": "Mod team", - "teamHint": "Online staff & workload" + "teamHint": "Online staff & workload", + "openHelpTickets": "Open help tickets" } } },