1
0
Fork 0
polaris-task-force/src/app/(frontend)/page.tsx

248 lines
8.2 KiB
TypeScript

import config from "@payload-config";
import { getPayload } from "payload";
import { headers as nextHeaders } from "next/headers";
import type { Assignment, Campaign, GameStructure, Map, Media, Rank, User } 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";
import { QuickStats } from "@/components/frontend/dashboard/QuickStats";
import { UpcomingMissions } from "@/components/frontend/dashboard/UpcomingMissions";
import { ActiveCampaigns } from "@/components/frontend/dashboard/ActiveCampaigns";
export default async function HomePage() {
const payload = await getPayload({ config });
const headers = await nextHeaders();
const { user } = await payload.auth({ headers, canSetHeaders: false });
if (!user) return null;
const userId = user.id;
const hasIntelligence = await hasIntelligenceQualification(payload, user).catch(() => false);
const [profileRes, experienceRes, assignmentRes] = await Promise.all([
payload.find({
collection: "profiles",
where: { user: { equals: userId } },
depth: 2,
select: {
user: true,
rank: true,
progression: { xp: true, awards: true, scoreboard: true, minigames: true },
createdAt: true,
},
}),
payload.find({
collection: "experience",
sort: "requiredPoints",
limit: 100,
select: { name: true, requiredPoints: true },
}),
payload.find({
collection: "assignments",
where: { members: { equals: userId } },
limit: 10,
select: { name: true, type: true, callsign: true },
}),
]);
const profile = profileRes.docs[0];
const xp = profile?.progression?.xp ?? 0;
const xpLevel = resolveLevel(xp, experienceRes.docs);
const profileUser = profile?.user as User | undefined;
const rank = profile?.rank as Rank | undefined;
const assignments = assignmentRes.docs as Pick<Assignment, "name" | "type" | "callsign">[];
const rankData = rank
? {
name: rank.name,
abbreviation: rank.abbreviation,
insigniaUrl:
rank.insignia && typeof rank.insignia === "object" ? (rank.insignia as Media).url : null,
}
: { name: "Unknown", abbreviation: "?" };
// Fetch upcoming missions (Scheduled or Ready) — up to 5 for the sidebar
const upcomingRes = await payload
.find({
collection: "missions",
where: {
"ownershipAndStatus.status": { in: ["Scheduled", "Ready"] },
},
sort: "classification.startDateTime",
limit: 5,
depth: 2,
})
.catch(() => ({ docs: [] }));
// If no upcoming, get most recently completed for the briefing card
let briefingMission = upcomingRes.docs[0] ?? null;
if (!briefingMission) {
const completedRes = await payload
.find({
collection: "missions",
where: {
"ownershipAndStatus.status": { equals: "Completed" },
},
sort: "-classification.startDateTime",
limit: 1,
depth: 2,
})
.catch(() => ({ docs: [] }));
briefingMission = completedRes.docs[0] ?? null;
}
const mission = briefingMission
? {
id: briefingMission.id,
name: briefingMission.name,
codeName: briefingMission.codeName,
summary: briefingMission.summary,
operationType: briefingMission.operationType,
classification: {
missionType: briefingMission.classification.missionType,
startDateTime: briefingMission.classification.startDateTime,
estimatedDuration: briefingMission.classification.estimatedDuration,
map: briefingMission.classification.map as number | Map,
campaign: briefingMission.classification.campaign as number | Campaign,
},
ownershipAndStatus: {
status: briefingMission.ownershipAndStatus.status,
},
missionRoles: briefingMission.missionRoles
? { maxPlayers: briefingMission.missionRoles.maxPlayers }
: null,
coverImage: briefingMission.coverImage,
}
: null;
const upcomingMissions = upcomingRes.docs.map((m) => ({
id: m.id,
name: m.name,
codeName: m.codeName,
summary: m.summary,
operationType: m.operationType,
classification: {
missionType: m.classification.missionType,
startDateTime: m.classification.startDateTime,
estimatedDuration: m.classification.estimatedDuration,
map: m.classification.map as number | Map,
campaign: m.classification.campaign as number | Campaign,
},
ownershipAndStatus: {
status: m.ownershipAndStatus.status,
},
missionRoles: m.missionRoles ? { maxPlayers: m.missionRoles.maxPlayers } : null,
coverImage: m.coverImage,
}));
// Fetch recent events
const eventsRes = await payload.find({
collection: "game-event-logs",
sort: "-timestamp",
limit: 8,
depth: 1,
});
const events = eventsRes.docs.map((e) => ({
id: e.id,
timestamp: e.timestamp,
type: e.type,
message: e.message,
system: e.system ?? true,
actor: e.actor as number | User | null | undefined,
structure: e.structure as number | GameStructure | null | undefined,
}));
// Fetch active campaigns for the sidebar
const activeCampaignsRes = await payload
.find({
collection: "campaigns",
where: { status: { equals: "inProgress" } },
limit: 5,
sort: "-createdAt",
})
.catch(() => ({ docs: [] }));
const activeCampaigns = activeCampaignsRes.docs.map((c) => ({
id: c.id,
name: c.name,
summary: c.summary,
status: c.status,
}));
// Fetch counts
const [campaignsRes, missionsCountRes, structuresRes, factionsRes, profilesRes] =
await Promise.all([
payload.find({
collection: "campaigns",
where: { status: { equals: "inProgress" } },
limit: 0,
}),
payload
.find({
collection: "missions",
limit: 0,
})
.catch(() => ({ totalDocs: 0 })),
payload.find({
collection: "game-structures",
limit: 0,
}),
payload.find({
collection: "factions",
limit: 0,
}),
payload.find({
collection: "profiles",
limit: 0,
}),
]);
return (
<div className="p-5 flex flex-col gap-6">
<div className="flex flex-col gap-1">
<h1 className="text-lg font-semibold">
Welcome back, {rankData.name}. {/*{profileUser?.displayName ?? user.username}*/}
</h1>
<p className="text-sm text-muted-foreground">Here&apos;s your operations dashboard.</p>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
{/* ── Main Content (2/3) ──────────────────────────── */}
<div className="xl:col-span-2 flex flex-col gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{profile && profileUser && (
<ProfileSummary
displayName={profileUser.displayName}
username={profileUser.username}
rank={rankData}
xpLevel={xpLevel}
assignments={assignments}
/>
)}
<MissionBriefing mission={mission} />
</div>
<RecentEvents events={events} currentUserId={userId} />
<QuickStats
activeCampaigns={campaignsRes.totalDocs}
totalMissions={missionsCountRes.totalDocs}
totalStructures={structuresRes.totalDocs}
totalFactions={factionsRes.totalDocs}
totalMembers={profilesRes.totalDocs}
/>
</div>
{/* ── Right Sidebar (1/3) ─────────────────────────── */}
<div className="flex flex-col gap-4">
{hasIntelligence && <UpcomingMissions missions={upcomingMissions} />}
{hasIntelligence && <ActiveCampaigns campaigns={activeCampaigns} />}
</div>
</div>
</div>
);
}