163 lines
4.6 KiB
TypeScript
163 lines
4.6 KiB
TypeScript
import { PERMS } from "@/lib/permission-slugs";
|
|
import {
|
|
createCorrelationId,
|
|
fail,
|
|
type HousekeepingCapabilityContext,
|
|
type HousekeepingResult,
|
|
} from "../../../foundation/contracts";
|
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
|
import { type PeopleStaffQueryData, peopleStaffQuery } from "../queries/staff";
|
|
import { PeoplePageFrame, parsePeopleListInput } from "./page-state";
|
|
import { PeopleCommandForm } from "./people-command-form";
|
|
|
|
interface PeopleStaffPageProps {
|
|
readonly context: HousekeepingCapabilityContext;
|
|
readonly result?: HousekeepingResult<PeopleStaffQueryData>;
|
|
}
|
|
|
|
export function PeopleStaffPage({ context, result }: PeopleStaffPageProps) {
|
|
return (
|
|
<PeoplePageFrame
|
|
title="Staff"
|
|
description="Review staff applications and team definitions."
|
|
result={result}
|
|
isEmpty={(data) => data.page.items.length === 0}
|
|
>
|
|
{(data) => (
|
|
<div className="space-y-3">
|
|
<form method="get" className="flex gap-2">
|
|
<input
|
|
name="search"
|
|
maxLength={100}
|
|
aria-label="Search staff workflow"
|
|
/>
|
|
<button type="submit">Search</button>
|
|
</form>
|
|
{data.kind === "applications" ? (
|
|
<ul className="space-y-2">
|
|
{data.page.items.map((application) => (
|
|
<li
|
|
key={application.id}
|
|
className="space-y-3 rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
|
|
>
|
|
<h2>
|
|
{application.username ?? `User #${application.userId}`}
|
|
</h2>
|
|
<p>{application.content}</p>
|
|
{context.has(PERMS.USERS_EDIT) ? (
|
|
<PeopleCommandForm
|
|
commandId="people.application.decide"
|
|
buttonLabel="Dismiss application"
|
|
input={{
|
|
applicationId: application.id,
|
|
decision: "dismiss",
|
|
}}
|
|
requiresReason
|
|
/>
|
|
) : null}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : data.kind === "teams" ? (
|
|
<div className="space-y-3">
|
|
{context.has(PERMS.USERS_EDIT) ? (
|
|
<PeopleCommandForm
|
|
commandId="people.team.change"
|
|
buttonLabel="Create team"
|
|
input={{ action: "create" }}
|
|
fields={[
|
|
{
|
|
name: "rankName",
|
|
label: "Team name",
|
|
type: "text",
|
|
maxLength: 255,
|
|
required: true,
|
|
},
|
|
{
|
|
name: "badge",
|
|
label: "Badge",
|
|
type: "text",
|
|
maxLength: 255,
|
|
},
|
|
{
|
|
name: "jobDescription",
|
|
label: "Job description",
|
|
type: "text",
|
|
maxLength: 255,
|
|
},
|
|
{
|
|
name: "staffColor",
|
|
label: "Staff color",
|
|
type: "text",
|
|
maxLength: 255,
|
|
defaultValue: "#327fa8",
|
|
},
|
|
{
|
|
name: "hiddenRank",
|
|
label: "Hidden rank",
|
|
type: "checkbox",
|
|
},
|
|
]}
|
|
requiresReason
|
|
/>
|
|
) : null}
|
|
<ul className="space-y-2">
|
|
{data.page.items.map((team) => (
|
|
<li
|
|
key={team.id}
|
|
className="space-y-3 rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
|
|
>
|
|
<h2>{team.name}</h2>
|
|
<p>{team.jobDescription ?? "No job description"}</p>
|
|
{context.has(PERMS.USERS_EDIT) ? (
|
|
<PeopleCommandForm
|
|
commandId="people.team.change"
|
|
buttonLabel="Delete team"
|
|
input={{ action: "delete", teamId: team.id }}
|
|
requiresReason
|
|
/>
|
|
) : null}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
) : data.kind === "moderation-team" ? (
|
|
<ul className="space-y-2">
|
|
{data.page.items.map((member) => (
|
|
<li
|
|
key={member.id}
|
|
className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
|
|
>
|
|
<a href={member.href}>{member.username}</a>
|
|
<p>
|
|
{member.openCfh} CFH · {member.openTickets} tickets ·{" "}
|
|
{member.actionCount} actions
|
|
</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
</div>
|
|
)}
|
|
</PeoplePageFrame>
|
|
);
|
|
}
|
|
|
|
export async function renderPeopleStaffPage(input: HousekeepingPageInput) {
|
|
const routeId = input.match.routeId;
|
|
const result =
|
|
routeId === "people.staff.applications" ||
|
|
routeId === "people.staff.teams" ||
|
|
routeId === "people.staff.moderation-team"
|
|
? await peopleStaffQuery.run(input.context, {
|
|
routeId,
|
|
list: parsePeopleListInput(input.searchParams ?? {}),
|
|
})
|
|
: fail(
|
|
"NOT_FOUND",
|
|
"errors.housekeeping.notFound",
|
|
createCorrelationId(),
|
|
);
|
|
return <PeopleStaffPage context={input.context} result={result} />;
|
|
}
|