- forms collection: targeting group (all/users/roles/qualifications), activeFrom/activeUntil window, radio/date field blocks enabled - form-submissions: server-side user attribution, expiry and audience gates, one attempt per user - forms/submissions permissions in the Surveys registry group + admin role seed grants - frontend /surveys list and /surveys/[id] take page with client block renderer - register the pending role permission enum values migration
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import config from "@payload-config";
|
|
import { headers as nextHeaders } from "next/headers";
|
|
import { getPayload } from "payload";
|
|
import { ClipboardCheck } from "lucide-react";
|
|
import { SurveyList, type SurveyListItem } from "@/components/frontend/surveys/SurveyList";
|
|
import { isSurveyOpen, surveyAudienceMatches } from "@/lib/surveys/evaluate";
|
|
import type { User } from "@/payload-types";
|
|
|
|
export const metadata = {
|
|
title: "Surveys: Polaris Task Force",
|
|
};
|
|
|
|
function audienceContext(user: User) {
|
|
const roleIds = (user.roleDocs ?? [])
|
|
.map((r) => (typeof r === "object" ? r.id : r))
|
|
.filter((id): id is number => typeof id === "number");
|
|
|
|
return { userId: user.id, roleIds };
|
|
}
|
|
|
|
export default async function SurveysPage() {
|
|
const headers = await nextHeaders();
|
|
const payload = await getPayload({ config });
|
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
|
|
|
// The (frontend) layout renders LandingPage for guests; this only guards
|
|
// the authed render path's types.
|
|
if (!user || user.collection !== "users") return null;
|
|
|
|
const [formsResult, profileRes] = await Promise.all([
|
|
payload.find({
|
|
collection: "forms",
|
|
sort: "-createdAt",
|
|
limit: 100,
|
|
depth: 1,
|
|
}),
|
|
payload.find({
|
|
collection: "profiles",
|
|
where: { user: { equals: user.id } },
|
|
limit: 1,
|
|
depth: 1,
|
|
}),
|
|
]);
|
|
|
|
const heldQualifications = (profileRes.docs[0]?.progression?.qualifications ?? [])
|
|
.map((q) => (typeof q === "object" && q !== null && "name" in q ? (q.name as string) : typeof q === "string" ? q : null))
|
|
.filter((n): n is string => n !== null);
|
|
|
|
const ctx = { ...audienceContext(user), qualificationNames: heldQualifications };
|
|
const now = Date.now();
|
|
|
|
const items: SurveyListItem[] = formsResult.docs
|
|
.filter((form) => isSurveyOpen(form, now) && surveyAudienceMatches(form, ctx))
|
|
.map((form) => ({
|
|
id: form.id,
|
|
title: form.title,
|
|
openUntil: form.activeUntil ?? null,
|
|
questionCount: form.fields?.filter((f) => f.blockType !== "message").length ?? 0,
|
|
}));
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<ClipboardCheck className="text-muted-foreground size-6" />
|
|
<div>
|
|
<h1 className="text-2xl font-semibold">Surveys</h1>
|
|
<p className="text-muted-foreground text-sm">Open surveys for you to take.</p>
|
|
</div>
|
|
</div>
|
|
<SurveyList items={items} />
|
|
</div>
|
|
);
|
|
}
|