From f56fc2f9c6d21bac87ed5c04b1adc132f731c883 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Mon, 6 Apr 2026 14:40:07 +0200 Subject: [PATCH] feat(cms-core): add Users, Articles, Media collections and SiteSettings global --- .../src/collections/articles/fields.ts | 60 +++++++++++++++++++ .../articles/hooks/before-change.ts | 20 +++++++ .../src/collections/articles/index.ts | 19 ++++++ .../cms-core/src/collections/media/index.ts | 18 ++++++ .../cms-core/src/collections/users/index.ts | 26 ++++++++ .../cms-core/src/globals/site-settings.ts | 20 +++++++ 6 files changed, 163 insertions(+) create mode 100644 packages/cms-core/src/collections/articles/fields.ts create mode 100644 packages/cms-core/src/collections/articles/hooks/before-change.ts create mode 100644 packages/cms-core/src/collections/articles/index.ts create mode 100644 packages/cms-core/src/collections/media/index.ts create mode 100644 packages/cms-core/src/collections/users/index.ts create mode 100644 packages/cms-core/src/globals/site-settings.ts diff --git a/packages/cms-core/src/collections/articles/fields.ts b/packages/cms-core/src/collections/articles/fields.ts new file mode 100644 index 0000000..1e84cb5 --- /dev/null +++ b/packages/cms-core/src/collections/articles/fields.ts @@ -0,0 +1,60 @@ +import type { Field } from "payload"; + +export const articleFields: Field[] = [ + { + name: "title", + type: "text", + required: true, + maxLength: 255, + }, + { + name: "slug", + type: "text", + unique: true, + admin: { + position: "sidebar", + description: "Auto-generated from title if left empty", + }, + }, + { + name: "content", + type: "richText", + }, + { + name: "status", + type: "select", + options: [ + { label: "Draft", value: "draft" }, + { label: "Published", value: "published" }, + ], + defaultValue: "draft", + required: true, + admin: { + position: "sidebar", + }, + }, + { + name: "author", + type: "relationship", + relationTo: "users", + required: true, + admin: { + position: "sidebar", + }, + }, + { + name: "featuredImage", + type: "upload", + relationTo: "media", + }, + { + name: "publishedAt", + type: "date", + admin: { + position: "sidebar", + date: { + pickerAppearance: "dayAndTime", + }, + }, + }, +]; diff --git a/packages/cms-core/src/collections/articles/hooks/before-change.ts b/packages/cms-core/src/collections/articles/hooks/before-change.ts new file mode 100644 index 0000000..f0edb59 --- /dev/null +++ b/packages/cms-core/src/collections/articles/hooks/before-change.ts @@ -0,0 +1,20 @@ +import type { CollectionBeforeChangeHook } from "payload"; + +function generateSlug(title: string): string { + return title + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); +} + +export const autoGenerateSlug: CollectionBeforeChangeHook = ({ + data, + operation, +}) => { + if (operation === "create" || operation === "update") { + if (data && data.title && !data.slug) { + data.slug = generateSlug(data.title); + } + } + return data; +}; diff --git a/packages/cms-core/src/collections/articles/index.ts b/packages/cms-core/src/collections/articles/index.ts new file mode 100644 index 0000000..cc977fb --- /dev/null +++ b/packages/cms-core/src/collections/articles/index.ts @@ -0,0 +1,19 @@ +import type { CollectionConfig } from "payload"; + +import { articleFields } from "./fields.js"; +import { autoGenerateSlug } from "./hooks/before-change.js"; + +export const Articles: CollectionConfig = { + slug: "articles", + admin: { + useAsTitle: "title", + defaultColumns: ["title", "status", "author", "updatedAt"], + }, + hooks: { + beforeChange: [autoGenerateSlug], + }, + versions: { + drafts: true, + }, + fields: articleFields, +}; diff --git a/packages/cms-core/src/collections/media/index.ts b/packages/cms-core/src/collections/media/index.ts new file mode 100644 index 0000000..174eaea --- /dev/null +++ b/packages/cms-core/src/collections/media/index.ts @@ -0,0 +1,18 @@ +import type { CollectionConfig } from "payload"; + +export const Media: CollectionConfig = { + slug: "media", + upload: { + mimeTypes: ["image/*", "application/pdf"], + }, + admin: { + useAsTitle: "filename", + }, + fields: [ + { + name: "alt", + type: "text", + required: true, + }, + ], +}; diff --git a/packages/cms-core/src/collections/users/index.ts b/packages/cms-core/src/collections/users/index.ts new file mode 100644 index 0000000..ac4a4d1 --- /dev/null +++ b/packages/cms-core/src/collections/users/index.ts @@ -0,0 +1,26 @@ +import type { CollectionConfig } from "payload"; + +export const Users: CollectionConfig = { + slug: "users", + auth: true, + admin: { + useAsTitle: "email", + }, + fields: [ + { + name: "displayName", + type: "text", + }, + { + name: "role", + type: "select", + options: [ + { label: "Admin", value: "admin" }, + { label: "Editor", value: "editor" }, + { label: "Author", value: "author" }, + ], + defaultValue: "author", + required: true, + }, + ], +}; diff --git a/packages/cms-core/src/globals/site-settings.ts b/packages/cms-core/src/globals/site-settings.ts new file mode 100644 index 0000000..a3fd33f --- /dev/null +++ b/packages/cms-core/src/globals/site-settings.ts @@ -0,0 +1,20 @@ +import type { GlobalConfig } from "payload"; + +export const SiteSettings: GlobalConfig = { + slug: "site-settings", + admin: { + group: "Settings", + }, + fields: [ + { + name: "siteName", + type: "text", + required: true, + defaultValue: "My App", + }, + { + name: "siteDescription", + type: "textarea", + }, + ], +};