- Maps gain worldMap flag, description, freeform weather, latitude/longitude, easeOfAccess and factionControl; picker cards render them as stat tags - resource-nodes gain a shared icon set and may exist with no resources as plain marker nodes (placement ignores marker-only nodes) - sidebar gains the World Map entry; Infrastructure section gets defaultOpen - migrations 20260921_120000/123000/130500/134000; includes the generated types/schema sweep (currency emblem fields land in the next commit)
60 lines
2.3 KiB
TypeScript
60 lines
2.3 KiB
TypeScript
import config from "@payload-config";
|
||
import { getPayload } from "payload";
|
||
import { headers } from "next/headers";
|
||
import { notFound } from "next/navigation";
|
||
import type { Metadata } from "next";
|
||
import { Map as MapIcon } from "lucide-react";
|
||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
|
||
import { MapLoader } from "../MapLoader";
|
||
|
||
interface MapViewPageProps {
|
||
params: Promise<{ id: string }>;
|
||
}
|
||
|
||
export async function generateMetadata({ params }: MapViewPageProps): Promise<Metadata> {
|
||
const { id } = await params;
|
||
return { title: `World Map #${id}: Polaris Task Force` };
|
||
}
|
||
|
||
export default async function MapViewPage({ params }: MapViewPageProps) {
|
||
const { id } = await params;
|
||
const mapId = Number(id);
|
||
const payload = await getPayload({ config: await config });
|
||
const { user } = await payload.auth({ headers: await headers(), canSetHeaders: false });
|
||
|
||
const map = Number.isInteger(mapId)
|
||
? await payload
|
||
.findByID({ collection: "maps", id: mapId, depth: 0, overrideAccess: true })
|
||
.catch(() => null)
|
||
: null;
|
||
|
||
if (!map) notFound();
|
||
|
||
const [canEdit, canPlace] = await Promise.all([
|
||
user ? hasPermission(payload, user, "maps:update") : Promise.resolve(false),
|
||
user ? hasLogisticsQualification(payload, user) : Promise.resolve(false),
|
||
]);
|
||
|
||
// Inactive maps stay reachable for editors previewing them, but are hidden
|
||
// from everyone else.
|
||
if (!map.isActive && !canEdit) notFound();
|
||
|
||
return (
|
||
<div className="relative flex min-h-[240px] flex-1 flex-col">
|
||
<div className="flex items-center justify-between gap-3 border-b border-white/15 bg-black/50 px-5 py-2 backdrop-blur-sm">
|
||
<div className="flex items-center gap-2">
|
||
<MapIcon className="size-4 text-muted-foreground" />
|
||
<h1 className="text-sm font-semibold uppercase tracking-[0.2em]">{map.name}</h1>
|
||
</div>
|
||
<div className="flex items-center gap-4 font-mono text-[9px] uppercase tracking-[0.2em] text-white/60">
|
||
<span>PTF Unit Command // Tactical Grid</span>
|
||
<span>
|
||
{map.worldSizeWidth}×{map.worldSizeHeight} m
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<MapLoader map={{ id: map.id, name: map.name }} canEdit={canEdit} canPlace={canPlace} />
|
||
</div>
|
||
);
|
||
}
|