import type { CollectionConfig } from "payload"; import { FixedToolbarFeature, lexicalEditor } from "@payloadcms/richtext-lexical"; import hasRoles from "@/utils/access-control/hasRoles"; function isStoryAuthor(user: Parameters[1]): boolean { return hasRoles(["admin", "developer"], user); } export const StoryBeats: CollectionConfig = { slug: "story-beats", admin: { group: "Story", useAsTitle: "title", defaultColumns: ["title", "status", "occurredAt", "order", "mission"], listSearchableFields: ["title", "body"], description: "Chronicle story beats. Drafts are visible to staff only; published beats are readable by any logged-in user on /story.", }, access: { // Guests see nothing; admin/developer see everything; any other logged-in // user sees only published beats. read: ({ req }) => { if (!req.user) return false; if (isStoryAuthor(req.user)) return true; return { status: { equals: "published" } }; }, create: ({ req }) => isStoryAuthor(req.user), update: ({ req }) => isStoryAuthor(req.user), delete: ({ req }) => isStoryAuthor(req.user), }, fields: [ { name: "title", type: "text", required: true, }, { name: "occurredAt", type: "date", admin: { description: "In-story timestamp used to order the chronicle.", }, }, { name: "order", type: "number", admin: { description: "Tie-breaker for beats that share the same timestamp.", }, }, { name: "status", type: "select", required: true, defaultValue: "draft", index: true, options: [ { label: "Draft", value: "draft" }, { label: "Published", value: "published" }, ], }, { name: "body", type: "richText", required: true, editor: lexicalEditor({ features: ({ defaultFeatures }) => [...defaultFeatures, FixedToolbarFeature()], }), admin: { description: "Rich text body of the beat, rendered on /story with the Lexical renderer.", }, }, { name: "mission", type: "relationship", relationTo: "missions", admin: { description: "Optional mission this beat is tied to. Beats may stand alone.", }, }, ], };