1
0
Fork 0

feat(helpdesk): add helpdesk pages

Add the helpdesk list page and ticket detail page routes.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-08-16 18:12:40 -04:00
parent 40f23e8774
commit 14d92bf277
2 changed files with 130 additions and 0 deletions

View file

@ -0,0 +1,83 @@
import config from "@payload-config";
import { headers as nextHeaders } from "next/headers";
import { notFound, redirect } from "next/navigation";
import { getPayload } from "payload";
import type { Ticket } from "@/payload-types";
import { TicketDetail } from "@/components/frontend/helpdesk/TicketDetail";
import hasRoles from "@/utils/access-control/hasRoles";
import { ArrowLeftIcon, LifeBuoyIcon } from "lucide-react";
import Link from "next/link";
export const metadata = {
title: "Ticket — Polaris Task Force",
};
interface TicketPageProps {
params: Promise<{ id: string }>;
}
export default async function TicketPage({ params }: TicketPageProps) {
const { id } = await params;
const payload = await getPayload({ config });
const { user } = await payload.auth({
headers: await nextHeaders(),
canSetHeaders: false,
});
if (!user) {
redirect("/login");
}
const ticket = await payload
.findByID({
collection: "tickets",
id: Number(id),
depth: 2,
})
.catch(() => null);
if (!ticket) {
notFound();
}
const typedTicket = ticket as unknown as Ticket;
const isStaff = hasRoles(["admin", "developer"], user);
const assigneeOptions = isStaff
? (
await payload.find({
collection: "users",
sort: "username",
limit: 100,
depth: 0,
})
).docs.map((staffUser) => ({
id: staffUser.id,
label: staffUser.payloadDisplayName || staffUser.username,
}))
: [];
return (
<div className="flex flex-col gap-6 p-5">
<div className="flex flex-col gap-3">
<Link
href="/helpdesk"
className="flex w-fit items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-primary"
>
<ArrowLeftIcon className="size-3" />
Helpdesk
</Link>
<div className="flex items-center gap-2">
<LifeBuoyIcon className="size-5 text-muted-foreground" />
<h1 className="text-lg font-semibold">Ticket #{typedTicket.id}</h1>
</div>
</div>
<TicketDetail
ticket={typedTicket}
currentUserId={user.id}
isStaff={isStaff}
assigneeOptions={assigneeOptions}
/>
</div>
);
}

View file

@ -0,0 +1,47 @@
import config from "@payload-config";
import { headers as nextHeaders } from "next/headers";
import { redirect } from "next/navigation";
import { getPayload } from "payload";
import type { Ticket } from "@/payload-types";
import { TicketsView } from "@/components/frontend/helpdesk/TicketsView";
import hasRoles from "@/utils/access-control/hasRoles";
import { LifeBuoyIcon } from "lucide-react";
export const metadata = {
title: "Helpdesk — Polaris Task Force",
};
export default async function HelpdeskPage() {
const payload = await getPayload({ config });
const { user } = await payload.auth({
headers: await nextHeaders(),
canSetHeaders: false,
});
if (!user) {
redirect("/login");
}
const ticketsRes = await payload.find({
collection: "tickets",
sort: "-updatedAt",
limit: 200,
depth: 2,
});
const tickets = ticketsRes.docs as unknown as Ticket[];
const isStaff = hasRoles(["admin", "developer"], user);
return (
<div className="flex flex-col gap-6 p-5">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<LifeBuoyIcon className="size-5 text-muted-foreground" />
<h1 className="text-lg font-semibold">Helpdesk</h1>
</div>
<p className="text-sm text-muted-foreground">Submit and track support requests.</p>
</div>
<TicketsView tickets={tickets} currentUserId={user.id} isStaff={isStaff} />
</div>
);
}