import config from "@payload-config"; import { getPayload } from "payload"; import Link from "next/link"; import { headers as nextHeaders } from "next/headers"; import type { Award, Media } from "@/payload-types"; import { AwardWithGrantButton } from "@/components/frontend/account/AwardWithGrantButton"; import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item"; import { Button } from "@/components/ui/button"; import { MedalIcon } from "lucide-react"; import hasRoles from "@/utils/access-control/hasRoles"; import { isPayloadUser } from "@/utils/access-control/isPayloadUser"; export const metadata = { title: "Awards", description: "Browse every medal and ribbon that can be granted to a member of Polaris Task Force.", }; const TYPE_LABEL: Record<"ribbon" | "medal", string> = { ribbon: "Ribbons", medal: "Medals", }; type AwardCatalogItem = | { readonly kind: "award"; readonly award: Award } | { readonly kind: "advancement"; readonly seriesKey: string; readonly familyName: string; }; function buildRibbonCatalogItems(ribbons: Award[]): AwardCatalogItem[] { const awardsBySeries = new Map(); for (const award of ribbons) { const seriesKey = award.advancement?.seriesKey; if (seriesKey == null) continue; const seriesAwards = awardsBySeries.get(seriesKey); if (seriesAwards) seriesAwards.push(award); else awardsBySeries.set(seriesKey, [award]); } const groupedSeriesKeys = new Set( [...awardsBySeries.entries()] .filter(([, seriesAwards]) => seriesAwards.length > 1) .map(([seriesKey]) => seriesKey), ); const emittedSeriesKeys = new Set(); const items: AwardCatalogItem[] = []; for (const award of ribbons) { const seriesKey = award.advancement?.seriesKey; if (seriesKey == null || !groupedSeriesKeys.has(seriesKey)) { items.push({ kind: "award", award }); continue; } if (emittedSeriesKeys.has(seriesKey)) continue; const seriesAwards = [...(awardsBySeries.get(seriesKey) ?? [])].sort((first, second) => { const tierDifference = (first.advancement?.tier ?? Number.POSITIVE_INFINITY) - (second.advancement?.tier ?? Number.POSITIVE_INFINITY); if (tierDifference !== 0) return tierDifference; if (first.name !== second.name) return first.name < second.name ? -1 : 1; return first.id - second.id; }); const familyAward = seriesAwards.find((seriesAward) => seriesAward.advancement?.tier === 1) ?? seriesAwards[0]; if (!familyAward) continue; items.push({ kind: "advancement", seriesKey, familyName: familyAward.name, }); for (const seriesAward of seriesAwards) { items.push({ kind: "award", award: seriesAward }); } emittedSeriesKeys.add(seriesKey); } return items; } export default async function AwardsPage() { const headers = await nextHeaders(); const payloadConfig = await config; const payload = await getPayload({ config: payloadConfig }); const { user: authUser } = await payload.auth({ headers, canSetHeaders: false }); const user = isPayloadUser(authUser) ? authUser : null; const canReview = hasRoles(["admin", "developer"], user); const res = await payload.find({ collection: "awards", where: { type: { in: ["ribbon", "medal"] }, }, sort: "name", limit: 100, depth: 1, overrideAccess: true, }); const pendingCount = canReview ? ( await payload.count({ collection: "ribbon-submissions", where: { status: { equals: "pending" } }, overrideAccess: true, }) ).totalDocs : 0; const awards: Award[] = res.docs; const ribbons = awards.filter((a) => a.type === "ribbon"); const medals = awards.filter((a) => a.type === "medal"); const catalogSections: { title: string; items: AwardCatalogItem[] }[] = []; if (medals.length > 0) { catalogSections.push({ title: TYPE_LABEL.medal, items: medals.map((award) => ({ kind: "award", award })), }); } if (ribbons.length > 0) { catalogSections.push({ title: TYPE_LABEL.ribbon, items: buildRibbonCatalogItems(ribbons), }); } return (

Awards

A reference of every medal and ribbon that can be earned by a member of Polaris Task Force. Review the requirements for each award and its artwork. Commendations are granted on the fly and are not catalogued here.

{canReview && ( )}
{awards.length === 0 ? ( No awards defined

No medals or ribbons have been catalogued yet. Check back soon.

) : ( catalogSections.map((section) => (

{section.title}

{section.items.map((item) => { switch (item.kind) { case "advancement": return (
Advancement: {item.familyName}
); case "award": { const { award } = item; const awardImage = typeof award.image === "object" && award.image ? (award.image as Media) : null; const backgroundImage = typeof award.backgroundImage === "object" && award.backgroundImage ? (award.backgroundImage as Media) : null; return ( ); } default: { const exhaustiveItem: never = item; return exhaustiveItem; } } })}
)) )}
); }