diff --git a/src/app/(frontend)/intelligence/missions/[id]/page.tsx b/src/app/(frontend)/intelligence/missions/[id]/page.tsx index 0ce8270..3f8183d 100644 --- a/src/app/(frontend)/intelligence/missions/[id]/page.tsx +++ b/src/app/(frontend)/intelligence/missions/[id]/page.tsx @@ -11,6 +11,7 @@ import type { Faction, Qualification, Rank, + Media, } from "@/payload-types"; import { ArrowLeftIcon, @@ -31,7 +32,6 @@ import { ItemMedia, ItemTitle, } from "@/components/ui/item"; -import { MissionHeader } from "@/components/frontend/intelligence/MissionHeader"; import { MissionBriefingSection } from "@/components/frontend/intelligence/MissionBriefingSection"; import { MissionObjectives } from "@/components/frontend/intelligence/MissionObjectives"; import { MissionFactions } from "@/components/frontend/intelligence/MissionFactions"; @@ -50,6 +50,10 @@ interface MissionDetailPageProps { params: Promise<{ id: string }>; } +type MissionWithCoverImage = Mission & { + coverImage?: number | null | Media; +}; + const statusColors: Record = { Concept: "bg-muted text-muted-foreground", Planning: "bg-muted text-muted-foreground", @@ -60,9 +64,19 @@ const statusColors: Record = { Cancelled: "bg-red-500/20 text-red-400", }; -export default async function MissionDetailPage({ - params, -}: MissionDetailPageProps) { +const operationTypeColors: Record = { + main: "bg-primary/20 text-primary", + side: "bg-muted text-muted-foreground", + external: "bg-purple-500/20 text-purple-400", +}; + +const operationTypeLabels: Record = { + main: "Main Operation", + side: "Side Operation", + external: "External Operation", +}; + +export default async function MissionDetailPage({ params }: MissionDetailPageProps) { const { id } = await params; const payload = await getPayload({ config }); @@ -79,13 +93,18 @@ export default async function MissionDetailPage({ } const map = - typeof mission.classification.map === "object" - ? (mission.classification.map as Map) - : null; + typeof mission.classification.map === "object" ? (mission.classification.map as Map) : null; const campaign = typeof mission.classification.campaign === "object" ? (mission.classification.campaign as Campaign) : null; + const missionWithCoverImage = mission as MissionWithCoverImage; + const coverImage = + typeof missionWithCoverImage.coverImage === "object" && + missionWithCoverImage.coverImage !== null + ? (missionWithCoverImage.coverImage as Media) + : null; + const coverImageUrl = coverImage?.url ?? null; const startDate = new Date(mission.classification.startDateTime); const dateStr = startDate.toLocaleDateString("en-US", { @@ -98,14 +117,9 @@ export default async function MissionDetailPage({ hour: "2-digit", minute: "2-digit", }); - const durationHours = Math.floor( - mission.classification.estimatedDuration / 60, - ); + const durationHours = Math.floor(mission.classification.estimatedDuration / 60); const durationMins = mission.classification.estimatedDuration % 60; - const durationStr = - durationMins > 0 - ? `${durationHours}h ${durationMins}m` - : `${durationHours}h`; + const durationStr = durationMins > 0 ? `${durationHours}h ${durationMins}m` : `${durationHours}h`; // Fetch sibling missions in the same campaign const campaignId = @@ -160,244 +174,265 @@ export default async function MissionDetailPage({ }; return ( -
- - - Back to Missions - +
+
+
+ + + Back to Missions + +
+ {coverImageUrl ? ( + + ) : ( +
+ )} +
+
- +
+ + {mission.ownershipAndStatus.status} + + + {operationTypeLabels[mission.operationType] ?? mission.operationType} + +
-
- {/* ── Main Content (2/3) ──────────────────────────── */} -
-
- - - - +
+
+
+ + {mission.codeName} +
+

+ {mission.name} +

+

+ {mission.summary} +

+ +
+
+ + + + Date + + {dateStr} + +
+
+ + + + Time + + + {timeStr} · {durationStr} + + +
+ {map && ( +
+ + + + Map + + {map.name} + +
+ )} + {campaign && ( + + + + + Campaign + + {campaign.name} + + + )} +
+
+
+
+ +
+
+ {/* ── Main Content (2/3) ──────────────────────────── */} +
+ + + Mission Details + - Date - {dateStr} +
+
+ Type +

{mission.classification.missionType}

+
+
+ Operation Type +

{mission.operationType}

+
+
+ Max Players +

{mission.missionRoles?.maxPlayers ?? "—"}

+
+
- - - - - - Time & Duration - - {timeStr} · {durationStr} - - - - {map && ( - - - - + + + + + + + + + + + + +
+ + {/* ── Right Sidebar (1/3) ─────────────────────────── */} +
+ + + {/* Campaign Card */} + {campaign && ( + + +
+ + Campaign +
+ + View + +
- Map - {map.name} +
+
+

{campaign.name}

+ + {campaign.status === "inProgress" ? "In Progress" : campaign.status} + +
+

{campaign.summary}

+
)} - {campaign && ( - - - - - - - Campaign - {campaign.name} - - - + + {/* Sibling Missions */} + {siblingMissions.length > 0 && ( + + +
+ + Other Missions in Campaign +
+
+ +
+ {siblingMissions.map((m) => { + const mDate = new Date(m.classification.startDateTime); + return ( + +
+ + {m.codeName} + + + {m.name} + +
+
+ + {m.ownershipAndStatus.status} + + +
+ + ); + })} +
+
+
+ )} + + {/* Server Details */} + {mission.gameDetails?.serverDetails?.serverIp && ( + + +
+ + Server +
+
+ +
+
+ Address + + {mission.gameDetails.serverDetails.serverIp} + +
+
+ Port + + {mission.gameDetails.serverDetails.serverPort} + +
+
+
+
)}
- -

{mission.summary}

- - - - Mission Details - - -
-
- Type -

- {mission.classification.missionType} -

-
-
- Operation Type -

- {mission.operationType} -

-
-
- Max Players -

- {mission.missionRoles?.maxPlayers ?? "—"} -

-
-
-
-
- - - - - - - - - - - - -
- - {/* ── Right Sidebar (1/3) ─────────────────────────── */} -
- - - {/* Campaign Card */} - {campaign && ( - - -
- - Campaign -
- - View - -
- -
-
-

{campaign.name}

- - {campaign.status === "inProgress" - ? "In Progress" - : campaign.status} - -
-

- {campaign.summary} -

-
-
-
- )} - - {/* Sibling Missions */} - {siblingMissions.length > 0 && ( - - -
- - Other Missions in Campaign -
-
- -
- {siblingMissions.map((m) => { - const mDate = new Date( - m.classification.startDateTime, - ); - return ( - -
- - {m.codeName} - - - {m.name} - -
-
- - {m.ownershipAndStatus.status} - - -
- - ); - })} -
-
-
- )} - - {/* Server Details */} - {mission.gameDetails?.serverDetails?.serverIp && ( - - -
- - Server -
-
- -
-
- Address - - {mission.gameDetails.serverDetails.serverIp} - -
-
- Port - - {mission.gameDetails.serverDetails.serverPort} - -
-
-
-
- )}
diff --git a/src/collections/intelligence/Missions.ts b/src/collections/intelligence/Missions.ts index 553a0d7..1b310b8 100644 --- a/src/collections/intelligence/Missions.ts +++ b/src/collections/intelligence/Missions.ts @@ -37,6 +37,7 @@ export const Missions: CollectionConfig = { slug: "missions", admin: { group: "Intelligence", + useAsTitle: "name", }, access: { create: isDeveloper, @@ -164,6 +165,15 @@ export const Missions: CollectionConfig = { hidden: true, }, }, + { + name: "coverImage", + type: "upload", + relationTo: "media", + admin: { + description: + "Background image for the mission page hero header. Renders translucent with a top-to-bottom fade to black. Recommended aspect ratio: 16:9 or wider.", + }, + }, { type: "tabs", tabs: [