1
0
Fork 0
polaris-task-force/src/app/(frontend)/intelligence/page.tsx
Z8MB1E caae76e6b2 feat(intelligence): gate intelligence content behind the intelligence qualification
- Add hasIntelligenceQualification access helper
- Redirect non-qualified users away from /intelligence
- Hide mission and campaign widgets on the dashboard for non-qualified users
- Guard hasLogisticsQualification against a null user
2026-08-11 23:25:27 -04:00

129 lines
4.4 KiB
TypeScript

import config from "@payload-config";
import { getPayload } from "payload";
import { headers as nextHeaders } from "next/headers";
import { redirect } from "next/navigation";
import Link from "next/link";
import type { Campaign, Mission, Faction } from "@/payload-types";
import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification";
import { CrosshairIcon, ArrowRightIcon } from "lucide-react";
import {
Item,
ItemContent,
ItemDescription,
ItemHeader,
ItemTitle,
} from "@/components/ui/item";
import { CampaignCard } from "@/components/frontend/intelligence/CampaignCard";
export const metadata = {
title: "Intelligence — Polaris Task Force",
};
export default async function IntelligenceOverviewPage() {
const payload = await getPayload({ config });
const headers = await nextHeaders();
const { user } = await payload.auth({ headers, canSetHeaders: false });
if (!user || !(await hasIntelligenceQualification(payload, user))) {
redirect("/");
}
const [campaignsRes, factionsRes] = await Promise.all([
payload
.find({
collection: "campaigns",
where: { status: { equals: "inProgress" } },
limit: 10,
sort: "-createdAt",
})
.catch(() => ({ docs: [] })),
payload.find({
collection: "factions",
limit: 100,
select: { name: true, type: true },
}),
]);
const campaigns = campaignsRes.docs as Campaign[];
const factions = factionsRes.docs as Faction[];
return (
<div className="p-5 flex flex-col gap-6">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<CrosshairIcon className="size-5 text-muted-foreground" />
<h1 className="text-lg font-semibold">Intelligence</h1>
</div>
<p className="text-sm text-muted-foreground">
Campaigns, missions, and faction intelligence for the theater of
operations.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Link href="/intelligence/campaigns">
<Item
variant="outline"
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01]"
>
<ItemHeader>
<ItemTitle>Campaigns</ItemTitle>
<ArrowRightIcon className="size-4 text-muted-foreground group-hover:text-primary transition-colors" />
</ItemHeader>
<ItemContent>
<ItemDescription>
Active and completed campaigns across all theaters.
</ItemDescription>
</ItemContent>
</Item>
</Link>
<Link href="/intelligence/missions">
<Item
variant="outline"
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01]"
>
<ItemHeader>
<ItemTitle>Missions</ItemTitle>
<ArrowRightIcon className="size-4 text-muted-foreground group-hover:text-primary transition-colors" />
</ItemHeader>
<ItemContent>
<ItemDescription>
Operation briefings, objectives, and schedules.
</ItemDescription>
</ItemContent>
</Item>
</Link>
<Link href="/intelligence/factions">
<Item
variant="outline"
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01]"
>
<ItemHeader>
<ItemTitle>Factions</ItemTitle>
<ArrowRightIcon className="size-4 text-muted-foreground group-hover:text-primary transition-colors" />
</ItemHeader>
<ItemContent>
<ItemDescription>
{factions.length} faction{factions.length !== 1 ? "s" : ""}{" "}
tracked in the AO.
</ItemDescription>
</ItemContent>
</Item>
</Link>
</div>
{campaigns.length > 0 && (
<section className="flex flex-col gap-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground">
Active Campaigns
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{campaigns.map((campaign) => (
<CampaignCard key={campaign.id} campaign={campaign} />
))}
</div>
</section>
)}
</div>
);
}