From caae76e6b2159ac802b1db687945d2bd6ecb6918 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Tue, 11 Aug 2026 23:25:27 -0400 Subject: [PATCH] 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 --- src/app/(frontend)/intelligence/page.tsx | 9 ++++++ src/app/(frontend)/page.tsx | 7 ++-- .../hasIntelligenceQualification.ts | 32 +++++++++++++++++++ .../hasLogisticsQualification.ts | 4 ++- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/utils/access-control/hasIntelligenceQualification.ts diff --git a/src/app/(frontend)/intelligence/page.tsx b/src/app/(frontend)/intelligence/page.tsx index 71f2caa..91ec18a 100644 --- a/src/app/(frontend)/intelligence/page.tsx +++ b/src/app/(frontend)/intelligence/page.tsx @@ -1,7 +1,10 @@ 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, @@ -18,6 +21,12 @@ export const metadata = { 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 diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index 12f3fff..59bc4d0 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -3,6 +3,7 @@ import { getPayload } from "payload"; import { headers as nextHeaders } from "next/headers"; import type { User, Rank, Media, Assignment, Campaign, Map, GameStructure } from "@/payload-types"; import { resolveLevel } from "@/utils/xp/resolveLevel"; +import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification"; import { ProfileSummary } from "@/components/frontend/dashboard/ProfileSummary"; import { MissionBriefing } from "@/components/frontend/dashboard/MissionBriefing"; import { RecentEvents } from "@/components/frontend/dashboard/RecentEvents"; @@ -19,6 +20,8 @@ export default async function HomePage() { const userId = user.id; + const hasIntelligence = await hasIntelligenceQualification(payload, user).catch(() => false); + const [profileRes, experienceRes, assignmentRes] = await Promise.all([ payload.find({ collection: "profiles", @@ -229,8 +232,8 @@ export default async function HomePage() { {/* ── Right Sidebar (1/3) ─────────────────────────── */}
- - + {hasIntelligence && } + {hasIntelligence && }
diff --git a/src/utils/access-control/hasIntelligenceQualification.ts b/src/utils/access-control/hasIntelligenceQualification.ts new file mode 100644 index 0000000..e27965c --- /dev/null +++ b/src/utils/access-control/hasIntelligenceQualification.ts @@ -0,0 +1,32 @@ +import type { Payload } from "payload"; + +export async function hasIntelligenceQualification( + payload: Payload, + user: { id: number | string; roles?: unknown } | null | undefined, +): Promise { + if (!user) return false; + + const roles = user.roles as string[] | undefined; + if (roles?.includes("developer") || roles?.includes("admin")) { + return true; + } + + const profile = (await payload.find({ + collection: "profiles", + where: { user: { equals: user.id as number } }, + depth: 4, + limit: 1, + })) as unknown as { docs: Array }; + + const userProfile = profile.docs[0]; + + if (!userProfile || !userProfile.progression || !userProfile.progression.qualifications) { + return false; + } + + return userProfile.progression.qualifications.some((qualification: any) => { + return typeof qualification === "string" + ? qualification.toLowerCase() === "intelligence" + : qualification?.name?.toLowerCase() === "intelligence"; + }); +} diff --git a/src/utils/access-control/hasLogisticsQualification.ts b/src/utils/access-control/hasLogisticsQualification.ts index ddb4006..410dc9e 100644 --- a/src/utils/access-control/hasLogisticsQualification.ts +++ b/src/utils/access-control/hasLogisticsQualification.ts @@ -9,6 +9,8 @@ export async function hasLogisticsQualification( payload: Payload, user: { id: number | string; roles?: unknown } | null | undefined, ): Promise { + if (!user) return false; + const roles = user.roles as string[] | undefined; if (roles?.includes("developer") || roles?.includes("admin")) { return true; @@ -16,7 +18,7 @@ export async function hasLogisticsQualification( const profileRes = await payload.find({ collection: "profiles", - where: { user: { equals: user.id } }, + where: { user: { equals: user.id as number } }, limit: 1, depth: 1, select: {