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 0000000..170ea96 Binary files /dev/null and b/public/images/ribbon-overlay.png differ 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 (