From b060b967c08cac1e32a5c0cf6adfd91ec6c2acb6 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Fri, 21 Aug 2026 12:46:44 -0400 Subject: [PATCH] feat(awards): add AwardCard and RibbonRack components for award display --- bun.lock | 1 + package.json | 1 + public/images/ribbon-overlay.png | Bin 0 -> 14955 bytes .../(frontend)/profile/[username]/page.tsx | 38 +------- .../admin/awards/RibbonDesigner.tsx | 24 ++--- src/components/frontend/account/AwardCard.tsx | 50 +++++++++++ .../frontend/account/RibbonRack.tsx | 36 ++++++++ .../frontend/account/RibbonViewer.tsx | 73 +++++++++++++++ src/components/ui/popover.tsx | 33 +++++++ src/components/ui/tooltip.tsx | 84 +++++++++++------- 10 files changed, 264 insertions(+), 76 deletions(-) create mode 100644 public/images/ribbon-overlay.png create mode 100644 src/components/frontend/account/AwardCard.tsx create mode 100644 src/components/frontend/account/RibbonRack.tsx create mode 100644 src/components/frontend/account/RibbonViewer.tsx create mode 100644 src/components/ui/popover.tsx diff --git a/bun.lock b/bun.lock index 4f739e3..09c4529 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.23", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.3.7", "@radix-ui/react-separator": "^1.1.8", diff --git a/package.json b/package.json index 885702d..60a9671 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.23", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.3.7", "@radix-ui/react-separator": "^1.1.8", diff --git a/public/images/ribbon-overlay.png b/public/images/ribbon-overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..170ea96aeaead684156fea2d3a66869aed402859 GIT binary patch literal 14955 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 literal 0 HcmV?d00001 diff --git a/src/app/(frontend)/profile/[username]/page.tsx b/src/app/(frontend)/profile/[username]/page.tsx index edf76a6..8f6034e 100644 --- a/src/app/(frontend)/profile/[username]/page.tsx +++ b/src/app/(frontend)/profile/[username]/page.tsx @@ -4,7 +4,6 @@ import { notFound } from "next/navigation"; import Link from "next/link"; import type { Assignment, Award, Media, Qualification, Rank, User } from "@/payload-types"; import { RichText } from "@payloadcms/richtext-lexical/react"; -import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext"; import { resolveLevel } from "@/utils/xp/resolveLevel"; import { ArrowLeftIcon, @@ -32,6 +31,8 @@ import { ItemMedia, ItemTitle, } from "@/components/ui/item"; +import AwardCard from "@/components/frontend/account/AwardCard"; +import { RibbonRack } from "@/components/frontend/account/RibbonRack"; interface ProfilePageProps { params: Promise<{ username: string }>; @@ -151,6 +152,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) { | @{user.username} +
{ const awardImage = award.image && typeof award.image === "object" ? (award.image as Media) : null; - return ( - - -
- {awardImage?.url && ( - {award.name} - )} -
- {award.name} - - {award.type ?? "commendation"} - -
-
-
- - - {award.description - ? convertLexicalToPlaintext({ data: award.description }).slice(0, 120) - : "No description."} - - {award.experiencePoints != null && ( - - +{award.experiencePoints} XP - - )} - -
- ); + return ; })}
) : ( diff --git a/src/components/admin/awards/RibbonDesigner.tsx b/src/components/admin/awards/RibbonDesigner.tsx index 696d274..ad964e4 100644 --- a/src/components/admin/awards/RibbonDesigner.tsx +++ b/src/components/admin/awards/RibbonDesigner.tsx @@ -19,7 +19,7 @@ type Stripe = { width: number; }; -type RibbonDesign = { +export type RibbonDesign = { stripes: Stripe[]; devices: Device[]; mirrored: boolean; @@ -27,7 +27,7 @@ type RibbonDesign = { // ─── Constants ────────────────────────────────────────────────────── -const DEFAULT_RIBBON: RibbonDesign = { +export const DEFAULT_RIBBON: RibbonDesign = { stripes: [ { color: "#b91c1c", width: 30 }, { color: "#f5f5f4", width: 5 }, @@ -62,16 +62,20 @@ const MILITARY_COLORS: { name: string; hex: string }[] = [ { name: "Black", hex: "#18181b" }, ]; -const DEVICE_INFO: Record = { +export const RIBBON_DEVICE_INFO: Record = { star: { label: "Star", symbol: "\u2605" }, oakLeaf: { label: "Oak Leaf", symbol: "\u{1F333}" }, arrowUp: { label: "Arrow", symbol: "\u2191" }, campaignClasp: { label: "Clasp", symbol: "\u2015" }, }; -const RIBBON_ASPECT = 3.667; -const RIBBON_WIDTH = 366; -const PREVIEW_HEIGHT = 80; +export const RIBBON_CONSTANTS = { + RIBBON_ASPECT: 3.667, + RIBBON_WIDTH: 366, + PREVIEW_HEIGHT: 80, +}; + +const { RIBBON_ASPECT, RIBBON_WIDTH, PREVIEW_HEIGHT } = RIBBON_CONSTANTS; // ─── Preview ──────────────────────────────────────────────────────── @@ -103,7 +107,7 @@ function RibbonPreview({ design }: { design: RibbonDesign }) { })()} {devices.map((device) => { - const info = DEVICE_INFO[device.type]; + const info = RIBBON_DEVICE_INFO[device.type]; const cx = (device.position / 100) * viewWidth; const cy = (device.positionY / 100) * viewHeight; const fontSize = Math.min(viewHeight * 0.45, 24); @@ -218,8 +222,8 @@ function DeviceEditor({
Devices
- {(Object.keys(DEVICE_INFO) as DeviceType[]).map((type) => { - const info = DEVICE_INFO[type]; + {(Object.keys(RIBBON_DEVICE_INFO) as DeviceType[]).map((type) => { + const info = RIBBON_DEVICE_INFO[type]; return (