1
0
Fork 0

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
This commit is contained in:
Jason Fraley 2026-08-11 23:25:27 -04:00
parent c81036bd82
commit caae76e6b2
4 changed files with 49 additions and 3 deletions

View file

@ -1,7 +1,10 @@
import config from "@payload-config"; import config from "@payload-config";
import { getPayload } from "payload"; import { getPayload } from "payload";
import { headers as nextHeaders } from "next/headers";
import { redirect } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import type { Campaign, Mission, Faction } from "@/payload-types"; import type { Campaign, Mission, Faction } from "@/payload-types";
import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification";
import { CrosshairIcon, ArrowRightIcon } from "lucide-react"; import { CrosshairIcon, ArrowRightIcon } from "lucide-react";
import { import {
Item, Item,
@ -18,6 +21,12 @@ export const metadata = {
export default async function IntelligenceOverviewPage() { export default async function IntelligenceOverviewPage() {
const payload = await getPayload({ config }); 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([ const [campaignsRes, factionsRes] = await Promise.all([
payload payload

View file

@ -3,6 +3,7 @@ import { getPayload } from "payload";
import { headers as nextHeaders } from "next/headers"; import { headers as nextHeaders } from "next/headers";
import type { User, Rank, Media, Assignment, Campaign, Map, GameStructure } from "@/payload-types"; import type { User, Rank, Media, Assignment, Campaign, Map, GameStructure } from "@/payload-types";
import { resolveLevel } from "@/utils/xp/resolveLevel"; import { resolveLevel } from "@/utils/xp/resolveLevel";
import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification";
import { ProfileSummary } from "@/components/frontend/dashboard/ProfileSummary"; import { ProfileSummary } from "@/components/frontend/dashboard/ProfileSummary";
import { MissionBriefing } from "@/components/frontend/dashboard/MissionBriefing"; import { MissionBriefing } from "@/components/frontend/dashboard/MissionBriefing";
import { RecentEvents } from "@/components/frontend/dashboard/RecentEvents"; import { RecentEvents } from "@/components/frontend/dashboard/RecentEvents";
@ -19,6 +20,8 @@ export default async function HomePage() {
const userId = user.id; const userId = user.id;
const hasIntelligence = await hasIntelligenceQualification(payload, user).catch(() => false);
const [profileRes, experienceRes, assignmentRes] = await Promise.all([ const [profileRes, experienceRes, assignmentRes] = await Promise.all([
payload.find({ payload.find({
collection: "profiles", collection: "profiles",
@ -229,8 +232,8 @@ export default async function HomePage() {
{/* ── Right Sidebar (1/3) ─────────────────────────── */} {/* ── Right Sidebar (1/3) ─────────────────────────── */}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<UpcomingMissions missions={upcomingMissions} /> {hasIntelligence && <UpcomingMissions missions={upcomingMissions} />}
<ActiveCampaigns campaigns={activeCampaigns} /> {hasIntelligence && <ActiveCampaigns campaigns={activeCampaigns} />}
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,32 @@
import type { Payload } from "payload";
export async function hasIntelligenceQualification(
payload: Payload,
user: { id: number | string; roles?: unknown } | null | undefined,
): Promise<boolean> {
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<any> };
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";
});
}

View file

@ -9,6 +9,8 @@ export async function hasLogisticsQualification(
payload: Payload, payload: Payload,
user: { id: number | string; roles?: unknown } | null | undefined, user: { id: number | string; roles?: unknown } | null | undefined,
): Promise<boolean> { ): Promise<boolean> {
if (!user) return false;
const roles = user.roles as string[] | undefined; const roles = user.roles as string[] | undefined;
if (roles?.includes("developer") || roles?.includes("admin")) { if (roles?.includes("developer") || roles?.includes("admin")) {
return true; return true;
@ -16,7 +18,7 @@ export async function hasLogisticsQualification(
const profileRes = await payload.find({ const profileRes = await payload.find({
collection: "profiles", collection: "profiles",
where: { user: { equals: user.id } }, where: { user: { equals: user.id as number } },
limit: 1, limit: 1,
depth: 1, depth: 1,
select: { select: {