feat: payload cms

This commit is contained in:
2026-02-26 01:32:22 +01:00
parent 1963a93123
commit 7d65237ee9
67 changed files with 3179 additions and 760 deletions

View File

@@ -0,0 +1,285 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
/**
* Migration: native_localization
*
* Transforms the existing schema (manual `locale` select column on each row)
* into Payload's native localization join-table structure.
*
* Each statement is a separate db.execute call to avoid Drizzle multi-statement issues.
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
// ── 1. Global locale enum ────────────────────────────────────────────────────
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__locales" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_pages_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 2. Alter pages table ─────────────────────────────────────────────────────
await db.execute(sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "layout" "enum_pages_layout" DEFAULT 'default'`);
await db.execute(sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "_status" "enum_pages_status" DEFAULT 'draft'`);
// ── 3. Create pages_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "pages_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "pages"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 4. Backfill pages_locales from old pages rows ────────────────────────────
await db.execute(sql`
INSERT INTO "pages_locales" ("title", "slug", "excerpt", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "pages" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"content" = EXCLUDED."content"
`);
// ── 5. Drop old columns from pages ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "locale"`);
// ── 6. Create posts_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "posts_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"category" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "posts"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 7. Backfill posts_locales ────────────────────────────────────────────────
await db.execute(sql`
INSERT INTO "posts_locales" ("title", "slug", "excerpt", "category", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.category, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "posts" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"category" = EXCLUDED."category",
"content" = EXCLUDED."content"
`);
// ── 8. Drop old columns from posts ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "category"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "locale"`);
// ── 9. Create products_locales join table ────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "products_locales" (
"title" varchar,
"description" varchar,
"application" jsonb,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "products"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 10. Backfill products_locales ────────────────────────────────────────────
// Products were separate DE/EN rows — each becomes a locale entry on its own id
await db.execute(sql`
INSERT INTO "products_locales" ("title", "description", "application", "content", "_locale", "_parent_id")
SELECT
p.title, p.description, p.application, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "products" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO NOTHING
`);
// ── 11. Drop old columns from products ───────────────────────────────────────
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "description"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "application"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "locale"`);
// ── 12. Version tables (_posts_v, _products_v, _pages_v) locale columns ──────
await db.execute(sql`ALTER TABLE "_posts_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__posts_v_published_locale"`);
await db.execute(sql`ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__products_v_published_locale"`);
await db.execute(sql`ALTER TABLE "_pages_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__pages_v_published_locale"`);
// ── 13. Create _posts_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_posts_v_locales" (
"version_title" varchar, "version_slug" varchar, "version_excerpt" varchar,
"version_category" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_posts_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 14. Create _products_v_locales ───────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_products_v_locales" (
"version_title" varchar, "version_description" varchar,
"version_application" jsonb, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_products_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 15. Create _pages_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_pages_v_locales" (
"version_title" varchar, "version_slug" varchar,
"version_excerpt" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_pages_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 16. Drop the now-redundant old locale enum ───────────────────────────────
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_pages_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_posts_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_products_locale"`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`DROP TABLE IF EXISTS "pages_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_pages_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "posts_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_posts_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "products_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_products_v_locales" CASCADE`);
}

View File

@@ -1,6 +1,7 @@
import * as migration_20260223_195005_products_collection from './20260223_195005_products_collection';
import * as migration_20260223_195151_remove_sku_unique from './20260223_195151_remove_sku_unique';
import * as migration_20260225_003500_add_pages_collection from './20260225_003500_add_pages_collection';
import * as migration_20260225_175000_native_localization from './20260225_175000_native_localization';
export const migrations = [
{
@@ -18,4 +19,9 @@ export const migrations = [
down: migration_20260225_003500_add_pages_collection.down,
name: '20260225_003500_add_pages_collection',
},
{
up: migration_20260225_175000_native_localization.up,
down: migration_20260225_175000_native_localization.down,
name: '20260225_175000_native_localization',
},
];

View File

@@ -22,27 +22,43 @@ import {
pgEnum,
} from '@payloadcms/db-postgres/drizzle/pg-core';
import { sql, relations } from '@payloadcms/db-postgres/drizzle';
export const enum_posts_locale = pgEnum('enum_posts_locale', ['en', 'de']);
export const enum__locales = pgEnum('enum__locales', ['de', 'en']);
export const enum_posts_status = pgEnum('enum_posts_status', ['draft', 'published']);
export const enum__posts_v_version_locale = pgEnum('enum__posts_v_version_locale', ['en', 'de']);
export const enum__posts_v_version_status = pgEnum('enum__posts_v_version_status', [
'draft',
'published',
]);
export const enum__posts_v_published_locale = pgEnum('enum__posts_v_published_locale', [
'de',
'en',
]);
export const enum_form_submissions_type = pgEnum('enum_form_submissions_type', [
'contact',
'product_quote',
]);
export const enum_products_locale = pgEnum('enum_products_locale', ['en', 'de']);
export const enum_products_status = pgEnum('enum_products_status', ['draft', 'published']);
export const enum__products_v_version_locale = pgEnum('enum__products_v_version_locale', [
'en',
'de',
]);
export const enum__products_v_version_status = pgEnum('enum__products_v_version_status', [
'draft',
'published',
]);
export const enum__products_v_published_locale = pgEnum('enum__products_v_published_locale', [
'de',
'en',
]);
export const enum_pages_layout = pgEnum('enum_pages_layout', ['default', 'fullBleed']);
export const enum_pages_status = pgEnum('enum_pages_status', ['draft', 'published']);
export const enum__pages_v_version_layout = pgEnum('enum__pages_v_version_layout', [
'default',
'fullBleed',
]);
export const enum__pages_v_version_status = pgEnum('enum__pages_v_version_status', [
'draft',
'published',
]);
export const enum__pages_v_published_locale = pgEnum('enum__pages_v_published_locale', [
'de',
'en',
]);
export const users_sessions = pgTable(
'users_sessions',
@@ -130,18 +146,12 @@ export const media = pgTable(
sizes_card_mimeType: varchar('sizes_card_mime_type'),
sizes_card_filesize: numeric('sizes_card_filesize', { mode: 'number' }),
sizes_card_filename: varchar('sizes_card_filename'),
sizes_hero_url: varchar('sizes_hero_url'),
sizes_hero_width: numeric('sizes_hero_width', { mode: 'number' }),
sizes_hero_height: numeric('sizes_hero_height', { mode: 'number' }),
sizes_hero_mimeType: varchar('sizes_hero_mime_type'),
sizes_hero_filesize: numeric('sizes_hero_filesize', { mode: 'number' }),
sizes_hero_filename: varchar('sizes_hero_filename'),
sizes_hero_mobile_url: varchar('sizes_hero_mobile_url'),
sizes_hero_mobile_width: numeric('sizes_hero_mobile_width', { mode: 'number' }),
sizes_hero_mobile_height: numeric('sizes_hero_mobile_height', { mode: 'number' }),
sizes_hero_mobile_mimeType: varchar('sizes_hero_mobile_mime_type'),
sizes_hero_mobile_filesize: numeric('sizes_hero_mobile_filesize', { mode: 'number' }),
sizes_hero_mobile_filename: varchar('sizes_hero_mobile_filename'),
sizes_tablet_url: varchar('sizes_tablet_url'),
sizes_tablet_width: numeric('sizes_tablet_width', { mode: 'number' }),
sizes_tablet_height: numeric('sizes_tablet_height', { mode: 'number' }),
sizes_tablet_mimeType: varchar('sizes_tablet_mime_type'),
sizes_tablet_filesize: numeric('sizes_tablet_filesize', { mode: 'number' }),
sizes_tablet_filename: varchar('sizes_tablet_filename'),
},
(columns) => [
index('media_updated_at_idx').on(columns.updatedAt),
@@ -151,10 +161,7 @@ export const media = pgTable(
columns.sizes_thumbnail_filename,
),
index('media_sizes_card_sizes_card_filename_idx').on(columns.sizes_card_filename),
index('media_sizes_hero_sizes_hero_filename_idx').on(columns.sizes_hero_filename),
index('media_sizes_hero_mobile_sizes_hero_mobile_filename_idx').on(
columns.sizes_hero_mobile_filename,
),
index('media_sizes_tablet_sizes_tablet_filename_idx').on(columns.sizes_tablet_filename),
],
);
@@ -162,16 +169,10 @@ export const posts = pgTable(
'posts',
{
id: serial('id').primaryKey(),
title: varchar('title'),
slug: varchar('slug'),
excerpt: varchar('excerpt'),
date: timestamp('date', { mode: 'string', withTimezone: true, precision: 3 }),
featuredImage: integer('featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
locale: enum_posts_locale('locale').default('en'),
category: varchar('category'),
content: jsonb('content'),
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
@@ -181,7 +182,6 @@ export const posts = pgTable(
_status: enum_posts_status('_status').default('draft'),
},
(columns) => [
uniqueIndex('posts_slug_idx').on(columns.slug),
index('posts_featured_image_idx').on(columns.featuredImage),
index('posts_updated_at_idx').on(columns.updatedAt),
index('posts_created_at_idx').on(columns.createdAt),
@@ -189,6 +189,28 @@ export const posts = pgTable(
],
);
export const posts_locales = pgTable(
'posts_locales',
{
title: varchar('title'),
slug: varchar('slug'),
excerpt: varchar('excerpt'),
category: varchar('category'),
content: jsonb('content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('posts_locales_locale_parent_id_unique').on(columns._locale, columns._parentID),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [posts.id],
name: 'posts_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const _posts_v = pgTable(
'_posts_v',
{
@@ -196,16 +218,10 @@ export const _posts_v = pgTable(
parent: integer('parent_id').references(() => posts.id, {
onDelete: 'set null',
}),
version_title: varchar('version_title'),
version_slug: varchar('version_slug'),
version_excerpt: varchar('version_excerpt'),
version_date: timestamp('version_date', { mode: 'string', withTimezone: true, precision: 3 }),
version_featuredImage: integer('version_featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
version_locale: enum__posts_v_version_locale('version_locale').default('en'),
version_category: varchar('version_category'),
version_content: jsonb('version_content'),
version_updatedAt: timestamp('version_updated_at', {
mode: 'string',
withTimezone: true,
@@ -223,21 +239,46 @@ export const _posts_v = pgTable(
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
snapshot: boolean('snapshot'),
publishedLocale: enum__posts_v_published_locale('published_locale'),
latest: boolean('latest'),
},
(columns) => [
index('_posts_v_parent_idx').on(columns.parent),
index('_posts_v_version_version_slug_idx').on(columns.version_slug),
index('_posts_v_version_version_featured_image_idx').on(columns.version_featuredImage),
index('_posts_v_version_version_updated_at_idx').on(columns.version_updatedAt),
index('_posts_v_version_version_created_at_idx').on(columns.version_createdAt),
index('_posts_v_version_version__status_idx').on(columns.version__status),
index('_posts_v_created_at_idx').on(columns.createdAt),
index('_posts_v_updated_at_idx').on(columns.updatedAt),
index('_posts_v_snapshot_idx').on(columns.snapshot),
index('_posts_v_published_locale_idx').on(columns.publishedLocale),
index('_posts_v_latest_idx').on(columns.latest),
],
);
export const _posts_v_locales = pgTable(
'_posts_v_locales',
{
version_title: varchar('version_title'),
version_slug: varchar('version_slug'),
version_excerpt: varchar('version_excerpt'),
version_category: varchar('version_category'),
version_content: jsonb('version_content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('_posts_v_locales_locale_parent_id_unique').on(columns._locale, columns._parentID),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [_posts_v.id],
name: '_posts_v_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const form_submissions = pgTable(
'form_submissions',
{
@@ -283,13 +324,11 @@ export const products = pgTable(
'products',
{
id: serial('id').primaryKey(),
title: varchar('title'),
sku: varchar('sku'),
slug: varchar('slug'),
description: varchar('description'),
locale: enum_products_locale('locale').default('de'),
application: jsonb('application'),
content: jsonb('content'),
featuredImage: integer('featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
@@ -299,13 +338,34 @@ export const products = pgTable(
_status: enum_products_status('_status').default('draft'),
},
(columns) => [
uniqueIndex('products_sku_idx').on(columns.sku),
index('products_featured_image_idx').on(columns.featuredImage),
index('products_updated_at_idx').on(columns.updatedAt),
index('products_created_at_idx').on(columns.createdAt),
index('products__status_idx').on(columns._status),
],
);
export const products_locales = pgTable(
'products_locales',
{
title: varchar('title'),
description: varchar('description'),
application: jsonb('application'),
content: jsonb('content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('products_locales_locale_parent_id_unique').on(columns._locale, columns._parentID),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [products.id],
name: 'products_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const products_rels = pgTable(
'products_rels',
{
@@ -360,13 +420,11 @@ export const _products_v = pgTable(
parent: integer('parent_id').references(() => products.id, {
onDelete: 'set null',
}),
version_title: varchar('version_title'),
version_sku: varchar('version_sku'),
version_slug: varchar('version_slug'),
version_description: varchar('version_description'),
version_locale: enum__products_v_version_locale('version_locale').default('de'),
version_application: jsonb('version_application'),
version_content: jsonb('version_content'),
version_featuredImage: integer('version_featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
version_updatedAt: timestamp('version_updated_at', {
mode: 'string',
withTimezone: true,
@@ -384,20 +442,48 @@ export const _products_v = pgTable(
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
snapshot: boolean('snapshot'),
publishedLocale: enum__products_v_published_locale('published_locale'),
latest: boolean('latest'),
},
(columns) => [
index('_products_v_parent_idx').on(columns.parent),
index('_products_v_version_version_sku_idx').on(columns.version_sku),
index('_products_v_version_version_featured_image_idx').on(columns.version_featuredImage),
index('_products_v_version_version_updated_at_idx').on(columns.version_updatedAt),
index('_products_v_version_version_created_at_idx').on(columns.version_createdAt),
index('_products_v_version_version__status_idx').on(columns.version__status),
index('_products_v_created_at_idx').on(columns.createdAt),
index('_products_v_updated_at_idx').on(columns.updatedAt),
index('_products_v_snapshot_idx').on(columns.snapshot),
index('_products_v_published_locale_idx').on(columns.publishedLocale),
index('_products_v_latest_idx').on(columns.latest),
],
);
export const _products_v_locales = pgTable(
'_products_v_locales',
{
version_title: varchar('version_title'),
version_description: varchar('version_description'),
version_application: jsonb('version_application'),
version_content: jsonb('version_content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('_products_v_locales_locale_parent_id_unique').on(
columns._locale,
columns._parentID,
),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [_products_v.id],
name: '_products_v_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const _products_v_rels = pgTable(
'_products_v_rels',
{
@@ -425,6 +511,118 @@ export const _products_v_rels = pgTable(
],
);
export const pages = pgTable(
'pages',
{
id: serial('id').primaryKey(),
layout: enum_pages_layout('layout').default('default'),
featuredImage: integer('featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
createdAt: timestamp('created_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
_status: enum_pages_status('_status').default('draft'),
},
(columns) => [
index('pages_featured_image_idx').on(columns.featuredImage),
index('pages_updated_at_idx').on(columns.updatedAt),
index('pages_created_at_idx').on(columns.createdAt),
index('pages__status_idx').on(columns._status),
],
);
export const pages_locales = pgTable(
'pages_locales',
{
title: varchar('title'),
slug: varchar('slug'),
excerpt: varchar('excerpt'),
content: jsonb('content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('pages_locales_locale_parent_id_unique').on(columns._locale, columns._parentID),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [pages.id],
name: 'pages_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const _pages_v = pgTable(
'_pages_v',
{
id: serial('id').primaryKey(),
parent: integer('parent_id').references(() => pages.id, {
onDelete: 'set null',
}),
version_layout: enum__pages_v_version_layout('version_layout').default('default'),
version_featuredImage: integer('version_featured_image_id').references(() => media.id, {
onDelete: 'set null',
}),
version_updatedAt: timestamp('version_updated_at', {
mode: 'string',
withTimezone: true,
precision: 3,
}),
version_createdAt: timestamp('version_created_at', {
mode: 'string',
withTimezone: true,
precision: 3,
}),
version__status: enum__pages_v_version_status('version__status').default('draft'),
createdAt: timestamp('created_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
updatedAt: timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 3 })
.defaultNow()
.notNull(),
snapshot: boolean('snapshot'),
publishedLocale: enum__pages_v_published_locale('published_locale'),
latest: boolean('latest'),
},
(columns) => [
index('_pages_v_parent_idx').on(columns.parent),
index('_pages_v_version_version_featured_image_idx').on(columns.version_featuredImage),
index('_pages_v_version_version_updated_at_idx').on(columns.version_updatedAt),
index('_pages_v_version_version_created_at_idx').on(columns.version_createdAt),
index('_pages_v_version_version__status_idx').on(columns.version__status),
index('_pages_v_created_at_idx').on(columns.createdAt),
index('_pages_v_updated_at_idx').on(columns.updatedAt),
index('_pages_v_snapshot_idx').on(columns.snapshot),
index('_pages_v_published_locale_idx').on(columns.publishedLocale),
index('_pages_v_latest_idx').on(columns.latest),
],
);
export const _pages_v_locales = pgTable(
'_pages_v_locales',
{
version_title: varchar('version_title'),
version_slug: varchar('version_slug'),
version_excerpt: varchar('version_excerpt'),
version_content: jsonb('version_content'),
id: serial('id').primaryKey(),
_locale: enum__locales('_locale').notNull(),
_parentID: integer('_parent_id').notNull(),
},
(columns) => [
uniqueIndex('_pages_v_locales_locale_parent_id_unique').on(columns._locale, columns._parentID),
foreignKey({
columns: [columns['_parentID']],
foreignColumns: [_pages_v.id],
name: '_pages_v_locales_parent_id_fk',
}).onDelete('cascade'),
],
);
export const payload_kv = pgTable(
'payload_kv',
{
@@ -466,6 +664,7 @@ export const payload_locked_documents_rels = pgTable(
postsID: integer('posts_id'),
'form-submissionsID': integer('form_submissions_id'),
productsID: integer('products_id'),
pagesID: integer('pages_id'),
},
(columns) => [
index('payload_locked_documents_rels_order_idx').on(columns.order),
@@ -478,6 +677,7 @@ export const payload_locked_documents_rels = pgTable(
columns['form-submissionsID'],
),
index('payload_locked_documents_rels_products_id_idx').on(columns.productsID),
index('payload_locked_documents_rels_pages_id_idx').on(columns.pagesID),
foreignKey({
columns: [columns['parent']],
foreignColumns: [payload_locked_documents.id],
@@ -508,6 +708,11 @@ export const payload_locked_documents_rels = pgTable(
foreignColumns: [products.id],
name: 'payload_locked_documents_rels_products_fk',
}).onDelete('cascade'),
foreignKey({
columns: [columns['pagesID']],
foreignColumns: [pages.id],
name: 'payload_locked_documents_rels_pages_fk',
}).onDelete('cascade'),
],
);
@@ -590,14 +795,31 @@ export const relations_users = relations(users, ({ many }) => ({
}),
}));
export const relations_media = relations(media, () => ({}));
export const relations_posts = relations(posts, ({ one }) => ({
export const relations_posts_locales = relations(posts_locales, ({ one }) => ({
_parentID: one(posts, {
fields: [posts_locales._parentID],
references: [posts.id],
relationName: '_locales',
}),
}));
export const relations_posts = relations(posts, ({ one, many }) => ({
featuredImage: one(media, {
fields: [posts.featuredImage],
references: [media.id],
relationName: 'featuredImage',
}),
_locales: many(posts_locales, {
relationName: '_locales',
}),
}));
export const relations__posts_v = relations(_posts_v, ({ one }) => ({
export const relations__posts_v_locales = relations(_posts_v_locales, ({ one }) => ({
_parentID: one(_posts_v, {
fields: [_posts_v_locales._parentID],
references: [_posts_v.id],
relationName: '_locales',
}),
}));
export const relations__posts_v = relations(_posts_v, ({ one, many }) => ({
parent: one(posts, {
fields: [_posts_v.parent],
references: [posts.id],
@@ -608,6 +830,9 @@ export const relations__posts_v = relations(_posts_v, ({ one }) => ({
references: [media.id],
relationName: 'version_featuredImage',
}),
_locales: many(_posts_v_locales, {
relationName: '_locales',
}),
}));
export const relations_form_submissions = relations(form_submissions, () => ({}));
export const relations_products_categories = relations(products_categories, ({ one }) => ({
@@ -617,6 +842,13 @@ export const relations_products_categories = relations(products_categories, ({ o
relationName: 'categories',
}),
}));
export const relations_products_locales = relations(products_locales, ({ one }) => ({
_parentID: one(products, {
fields: [products_locales._parentID],
references: [products.id],
relationName: '_locales',
}),
}));
export const relations_products_rels = relations(products_rels, ({ one }) => ({
parent: one(products, {
fields: [products_rels.parent],
@@ -629,10 +861,18 @@ export const relations_products_rels = relations(products_rels, ({ one }) => ({
relationName: 'media',
}),
}));
export const relations_products = relations(products, ({ many }) => ({
export const relations_products = relations(products, ({ one, many }) => ({
categories: many(products_categories, {
relationName: 'categories',
}),
featuredImage: one(media, {
fields: [products.featuredImage],
references: [media.id],
relationName: 'featuredImage',
}),
_locales: many(products_locales, {
relationName: '_locales',
}),
_rels: many(products_rels, {
relationName: '_rels',
}),
@@ -647,6 +887,13 @@ export const relations__products_v_version_categories = relations(
}),
}),
);
export const relations__products_v_locales = relations(_products_v_locales, ({ one }) => ({
_parentID: one(_products_v, {
fields: [_products_v_locales._parentID],
references: [_products_v.id],
relationName: '_locales',
}),
}));
export const relations__products_v_rels = relations(_products_v_rels, ({ one }) => ({
parent: one(_products_v, {
fields: [_products_v_rels.parent],
@@ -668,10 +915,57 @@ export const relations__products_v = relations(_products_v, ({ one, many }) => (
version_categories: many(_products_v_version_categories, {
relationName: 'version_categories',
}),
version_featuredImage: one(media, {
fields: [_products_v.version_featuredImage],
references: [media.id],
relationName: 'version_featuredImage',
}),
_locales: many(_products_v_locales, {
relationName: '_locales',
}),
_rels: many(_products_v_rels, {
relationName: '_rels',
}),
}));
export const relations_pages_locales = relations(pages_locales, ({ one }) => ({
_parentID: one(pages, {
fields: [pages_locales._parentID],
references: [pages.id],
relationName: '_locales',
}),
}));
export const relations_pages = relations(pages, ({ one, many }) => ({
featuredImage: one(media, {
fields: [pages.featuredImage],
references: [media.id],
relationName: 'featuredImage',
}),
_locales: many(pages_locales, {
relationName: '_locales',
}),
}));
export const relations__pages_v_locales = relations(_pages_v_locales, ({ one }) => ({
_parentID: one(_pages_v, {
fields: [_pages_v_locales._parentID],
references: [_pages_v.id],
relationName: '_locales',
}),
}));
export const relations__pages_v = relations(_pages_v, ({ one, many }) => ({
parent: one(pages, {
fields: [_pages_v.parent],
references: [pages.id],
relationName: 'parent',
}),
version_featuredImage: one(media, {
fields: [_pages_v.version_featuredImage],
references: [media.id],
relationName: 'version_featuredImage',
}),
_locales: many(_pages_v_locales, {
relationName: '_locales',
}),
}));
export const relations_payload_kv = relations(payload_kv, () => ({}));
export const relations_payload_locked_documents_rels = relations(
payload_locked_documents_rels,
@@ -706,6 +1000,11 @@ export const relations_payload_locked_documents_rels = relations(
references: [products.id],
relationName: 'products',
}),
pagesID: one(pages, {
fields: [payload_locked_documents_rels.pagesID],
references: [pages.id],
relationName: 'pages',
}),
}),
);
export const relations_payload_locked_documents = relations(
@@ -739,27 +1038,39 @@ export const relations_payload_preferences = relations(payload_preferences, ({ m
export const relations_payload_migrations = relations(payload_migrations, () => ({}));
type DatabaseSchema = {
enum_posts_locale: typeof enum_posts_locale;
enum__locales: typeof enum__locales;
enum_posts_status: typeof enum_posts_status;
enum__posts_v_version_locale: typeof enum__posts_v_version_locale;
enum__posts_v_version_status: typeof enum__posts_v_version_status;
enum__posts_v_published_locale: typeof enum__posts_v_published_locale;
enum_form_submissions_type: typeof enum_form_submissions_type;
enum_products_locale: typeof enum_products_locale;
enum_products_status: typeof enum_products_status;
enum__products_v_version_locale: typeof enum__products_v_version_locale;
enum__products_v_version_status: typeof enum__products_v_version_status;
enum__products_v_published_locale: typeof enum__products_v_published_locale;
enum_pages_layout: typeof enum_pages_layout;
enum_pages_status: typeof enum_pages_status;
enum__pages_v_version_layout: typeof enum__pages_v_version_layout;
enum__pages_v_version_status: typeof enum__pages_v_version_status;
enum__pages_v_published_locale: typeof enum__pages_v_published_locale;
users_sessions: typeof users_sessions;
users: typeof users;
media: typeof media;
posts: typeof posts;
posts_locales: typeof posts_locales;
_posts_v: typeof _posts_v;
_posts_v_locales: typeof _posts_v_locales;
form_submissions: typeof form_submissions;
products_categories: typeof products_categories;
products: typeof products;
products_locales: typeof products_locales;
products_rels: typeof products_rels;
_products_v_version_categories: typeof _products_v_version_categories;
_products_v: typeof _products_v;
_products_v_locales: typeof _products_v_locales;
_products_v_rels: typeof _products_v_rels;
pages: typeof pages;
pages_locales: typeof pages_locales;
_pages_v: typeof _pages_v;
_pages_v_locales: typeof _pages_v_locales;
payload_kv: typeof payload_kv;
payload_locked_documents: typeof payload_locked_documents;
payload_locked_documents_rels: typeof payload_locked_documents_rels;
@@ -769,15 +1080,23 @@ type DatabaseSchema = {
relations_users_sessions: typeof relations_users_sessions;
relations_users: typeof relations_users;
relations_media: typeof relations_media;
relations_posts_locales: typeof relations_posts_locales;
relations_posts: typeof relations_posts;
relations__posts_v_locales: typeof relations__posts_v_locales;
relations__posts_v: typeof relations__posts_v;
relations_form_submissions: typeof relations_form_submissions;
relations_products_categories: typeof relations_products_categories;
relations_products_locales: typeof relations_products_locales;
relations_products_rels: typeof relations_products_rels;
relations_products: typeof relations_products;
relations__products_v_version_categories: typeof relations__products_v_version_categories;
relations__products_v_locales: typeof relations__products_v_locales;
relations__products_v_rels: typeof relations__products_v_rels;
relations__products_v: typeof relations__products_v;
relations_pages_locales: typeof relations_pages_locales;
relations_pages: typeof relations_pages;
relations__pages_v_locales: typeof relations__pages_v_locales;
relations__pages_v: typeof relations__pages_v;
relations_payload_kv: typeof relations_payload_kv;
relations_payload_locked_documents_rels: typeof relations_payload_locked_documents_rels;
relations_payload_locked_documents: typeof relations_payload_locked_documents;

View File

@@ -0,0 +1,48 @@
import { Block } from 'payload';
export const CategoryGrid: Block = {
slug: 'categoryGrid',
interfaceName: 'CategoryGridBlock',
fields: [
{
name: 'categories',
type: 'array',
required: true,
minRows: 1,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'description',
type: 'textarea',
required: false,
},
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'icon',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'href',
type: 'text',
required: true,
},
{
name: 'ctaLabel',
type: 'text',
required: false,
},
],
},
],
};

View File

@@ -0,0 +1,54 @@
import { Block } from 'payload';
export const TeamLegacySection: Block = {
slug: 'teamLegacySection',
interfaceName: 'TeamLegacySectionBlock',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'subtitle',
type: 'text',
required: true,
},
{
name: 'paragraph1',
type: 'textarea',
required: true,
},
{
name: 'paragraph2',
type: 'textarea',
required: true,
},
{
name: 'expertiseTitle',
type: 'text',
required: true,
},
{
name: 'expertiseDesc',
type: 'text',
required: true,
},
{
name: 'networkTitle',
type: 'text',
required: true,
},
{
name: 'networkDesc',
type: 'text',
required: true,
},
{
name: 'backgroundImage',
type: 'upload',
relationTo: 'media',
required: false,
},
],
};

View File

@@ -0,0 +1,26 @@
import { Block } from 'payload';
export const ContactSection: Block = {
slug: 'contactSection',
interfaceName: 'ContactSectionBlock',
fields: [
{
name: 'showForm',
type: 'checkbox',
defaultValue: true,
label: 'Show Contact Form',
},
{
name: 'showMap',
type: 'checkbox',
defaultValue: true,
label: 'Show Map',
},
{
name: 'showHours',
type: 'checkbox',
defaultValue: true,
label: 'Show Opening Hours',
},
],
};

View File

@@ -0,0 +1,48 @@
import { Block } from 'payload';
export const HeroSection: Block = {
slug: 'heroSection',
interfaceName: 'HeroSectionBlock',
fields: [
{
name: 'badge',
type: 'text',
required: false,
},
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'subtitle',
type: 'textarea',
required: false,
},
{
name: 'backgroundImage',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'ctaLabel',
type: 'text',
required: false,
},
{
name: 'ctaHref',
type: 'text',
required: false,
},
{
name: 'alignment',
type: 'select',
defaultValue: 'left',
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
],
},
],
};

View File

@@ -0,0 +1,141 @@
import { Block } from 'payload';
export const HomeHero: Block = {
slug: 'homeHero',
interfaceName: 'HomeHeroBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'secondaryCtaLabel', type: 'text', localized: true },
],
};
export const HomeProductCategories: Block = {
slug: 'homeProductCategories',
interfaceName: 'HomeProductCategoriesBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeWhatWeDo: Block = {
slug: 'homeWhatWeDo',
interfaceName: 'HomeWhatWeDoBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'expertiseLabel', type: 'text', localized: true },
{ name: 'quote', type: 'textarea', localized: true },
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
],
},
],
};
export const HomeRecentPosts: Block = {
slug: 'homeRecentPosts',
interfaceName: 'HomeRecentPostsBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeExperience: Block = {
slug: 'homeExperience',
interfaceName: 'HomeExperienceBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'paragraph1', type: 'textarea', localized: true },
{ name: 'paragraph2', type: 'textarea', localized: true },
{ name: 'badge1', type: 'text', localized: true },
{ name: 'badge1Text', type: 'text', localized: true },
{ name: 'badge2', type: 'text', localized: true },
{ name: 'badge2Text', type: 'text', localized: true },
],
};
export const HomeWhyChooseUs: Block = {
slug: 'homeWhyChooseUs',
interfaceName: 'HomeWhyChooseUsBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'tagline', type: 'text', localized: true },
{
name: 'features',
type: 'array',
localized: true,
fields: [{ name: 'feature', type: 'text' }],
},
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
],
},
],
};
export const HomeMeetTheTeam: Block = {
slug: 'homeMeetTheTeam',
interfaceName: 'HomeMeetTheTeamBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'description', type: 'textarea', localized: true },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'networkLabel', type: 'text', localized: true },
],
};
export const HomeGallery: Block = {
slug: 'homeGallery',
interfaceName: 'HomeGalleryBlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeVideo: Block = {
slug: 'homeVideo',
interfaceName: 'HomeVideoBlock',
fields: [{ name: 'title', type: 'text', localized: true }],
};
export const HomeCTA: Block = {
slug: 'homeCTA',
interfaceName: 'HomeCTABlock',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'description', type: 'textarea', localized: true },
{ name: 'buttonLabel', type: 'text', localized: true },
],
};
export const homeBlocksArray = [
HomeHero,
HomeProductCategories,
HomeWhatWeDo,
HomeRecentPosts,
HomeExperience,
HomeWhyChooseUs,
HomeMeetTheTeam,
HomeGallery,
HomeVideo,
HomeCTA,
];

View File

@@ -0,0 +1,27 @@
import { Block } from 'payload';
export const ImageGallery: Block = {
slug: 'imageGallery',
interfaceName: 'ImageGalleryBlock',
fields: [
{
name: 'images',
type: 'array',
required: true,
minRows: 1,
fields: [
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: true,
},
{
name: 'alt',
type: 'text',
required: false,
},
],
},
],
};

View File

@@ -0,0 +1,41 @@
import { Block } from 'payload';
export const ManifestoGrid: Block = {
slug: 'manifestoGrid',
interfaceName: 'ManifestoGridBlock',
fields: [
{
name: 'title',
type: 'text',
required: false,
},
{
name: 'subtitle',
type: 'text',
required: false,
},
{
name: 'tagline',
type: 'textarea',
required: false,
},
{
name: 'items',
type: 'array',
required: true,
minRows: 1,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'description',
type: 'textarea',
required: true,
},
],
},
],
};

View File

@@ -0,0 +1,28 @@
import { Block } from 'payload';
export const SupportCTA: Block = {
slug: 'supportCTA',
interfaceName: 'SupportCTABlock',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'description',
type: 'textarea',
required: true,
},
{
name: 'buttonLabel',
type: 'text',
required: true,
},
{
name: 'buttonHref',
type: 'text',
required: true,
},
],
};

View File

@@ -0,0 +1,62 @@
import { Block } from 'payload';
export const TeamProfile: Block = {
slug: 'teamProfile',
interfaceName: 'TeamProfileBlock',
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'role',
type: 'text',
required: true,
},
{
name: 'quote',
type: 'textarea',
required: false,
},
{
name: 'description',
type: 'textarea',
required: false,
},
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'linkedinUrl',
type: 'text',
required: false,
},
{
name: 'linkedinLabel',
type: 'text',
required: false,
},
{
name: 'layout',
type: 'select',
defaultValue: 'imageRight',
options: [
{ label: 'Image Right', value: 'imageRight' },
{ label: 'Image Left', value: 'imageLeft' },
],
},
{
name: 'colorScheme',
type: 'select',
defaultValue: 'dark',
options: [
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
],
},
],
};

View File

@@ -1,27 +1,41 @@
import { AnimatedImage } from './AnimatedImage';
import { Callout } from './Callout';
import { CategoryGrid } from './CategoryGrid';
import { ChatBubble } from './ChatBubble';
import { ComparisonGrid } from './ComparisonGrid';
import { ContactSection } from './ContactSection';
import { HeroSection } from './HeroSection';
import { HighlightBox } from './HighlightBox';
import { ImageGallery } from './ImageGallery';
import { ManifestoGrid } from './ManifestoGrid';
import { PowerCTA } from './PowerCTA';
import { ProductTabs } from './ProductTabs';
import { SplitHeading } from './SplitHeading';
import { Stats } from './Stats';
import { StickyNarrative } from './StickyNarrative';
import { TeamProfile } from './TeamProfile';
import { TechnicalGrid } from './TechnicalGrid';
import { VisualLinkPreview } from './VisualLinkPreview';
import { homeBlocksArray } from './HomeBlocks';
export const payloadBlocks = [
...homeBlocksArray,
AnimatedImage,
Callout,
CategoryGrid,
ChatBubble,
ComparisonGrid,
ContactSection,
HeroSection,
HighlightBox,
ImageGallery,
ManifestoGrid,
PowerCTA,
ProductTabs,
SplitHeading,
Stats,
StickyNarrative,
TeamProfile,
TechnicalGrid,
VisualLinkPreview,
];

View File

@@ -1,44 +1,65 @@
import { CollectionConfig } from 'payload';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical';
import { payloadBlocks } from '../blocks/allBlocks';
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'slug', 'locale', 'updatedAt'],
defaultColumns: ['title', 'slug', 'layout', '_status', 'updatedAt'],
},
versions: {
drafts: true,
},
access: {
read: () => true,
read: ({ req: { user } }) => {
if (process.env.NODE_ENV === 'development') {
return true;
}
if (user) {
return true;
}
return {
_status: {
equals: 'published',
},
};
},
},
fields: [
{
name: 'title',
type: 'text',
required: true,
localized: true,
},
{
name: 'slug',
type: 'text',
required: true,
localized: true,
admin: {
position: 'sidebar',
description: 'The URL slug for this locale (e.g. "impressum" for DE, "imprint" for EN).',
},
},
{
name: 'locale',
name: 'layout',
type: 'select',
defaultValue: 'default',
options: [
{ label: 'English', value: 'en' },
{ label: 'German', value: 'de' },
{ label: 'Default (Article)', value: 'default' },
{ label: 'Full Bleed (Blocks Only)', value: 'fullBleed' },
],
required: true,
admin: {
position: 'sidebar',
description: 'Full Bleed pages render blocks edge-to-edge without a generic hero wrapper.',
},
},
{
name: 'excerpt',
type: 'textarea',
localized: true,
admin: {
position: 'sidebar',
},
@@ -54,7 +75,15 @@ export const Pages: CollectionConfig = {
{
name: 'content',
type: 'richText',
editor: lexicalEditor({}),
localized: true,
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: payloadBlocks,
}),
],
}),
required: true,
},
],

View File

@@ -19,22 +19,16 @@ export const Posts: CollectionConfig = {
defaultColumns: ['featuredImage', 'title', 'date', 'updatedAt', '_status'],
},
versions: {
drafts: true, // Enables Draft/Published workflows
drafts: true,
},
access: {
read: ({ req: { user } }) => {
// In local development, always show everything (including Drafts and scheduled future posts)
if (process.env.NODE_ENV === 'development') {
return true;
}
// If an Admin user is logged in, they can view everything
if (user) {
return true;
}
// For public unauthenticated visitors in PROD/STAGING contexts:
// Only serve Posts where Status = "published" AND the publish Date is in the past!
return {
and: [
{
@@ -56,19 +50,20 @@ export const Posts: CollectionConfig = {
name: 'title',
type: 'text',
required: true,
localized: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
localized: true,
admin: {
position: 'sidebar',
description: 'Unique slug per locale (e.g. same slug can exist in DE and EN).',
},
hooks: {
beforeValidate: [
({ value, data }) => {
// Auto-generate slug from title if left blank
if (value || !data?.title) return value;
return data.title
.toLowerCase()
@@ -81,6 +76,7 @@ export const Posts: CollectionConfig = {
{
name: 'excerpt',
type: 'text',
localized: true,
admin: {
description: 'A short summary for blog feed cards and SEO.',
},
@@ -104,22 +100,10 @@ export const Posts: CollectionConfig = {
description: 'The primary Hero image used for headers and OpenGraph previews.',
},
},
{
name: 'locale',
type: 'select',
required: true,
admin: {
position: 'sidebar',
},
options: [
{ label: 'English', value: 'en' },
{ label: 'German', value: 'de' },
],
defaultValue: 'en',
},
{
name: 'category',
type: 'text',
localized: true,
admin: {
position: 'sidebar',
description: 'Used for tag bucketing (e.g. "Kabel Technologie").',
@@ -128,6 +112,7 @@ export const Posts: CollectionConfig = {
{
name: 'content',
type: 'richText',
localized: true,
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,

View File

@@ -17,7 +17,7 @@ import { ProductTabs } from '../blocks/ProductTabs';
export const Products: CollectionConfig = {
slug: 'products',
admin: {
defaultColumns: ['featuredImage', 'title', 'sku', 'locale', 'updatedAt', '_status'],
defaultColumns: ['featuredImage', 'title', 'sku', 'updatedAt', '_status'],
},
versions: {
drafts: true,
@@ -42,6 +42,7 @@ export const Products: CollectionConfig = {
name: 'title',
type: 'text',
required: true,
localized: true,
},
{
name: 'sku',
@@ -52,6 +53,7 @@ export const Products: CollectionConfig = {
},
},
{
// slug is shared: the cable name (e.g. "n2xy") is the same in DE and EN
name: 'slug',
type: 'text',
required: true,
@@ -63,19 +65,7 @@ export const Products: CollectionConfig = {
name: 'description',
type: 'textarea',
required: true,
},
{
name: 'locale',
type: 'select',
required: true,
admin: {
position: 'sidebar',
},
options: [
{ label: 'English', value: 'en' },
{ label: 'German', value: 'de' },
],
defaultValue: 'de',
localized: true,
},
{
name: 'categories',
@@ -112,11 +102,13 @@ export const Products: CollectionConfig = {
{
name: 'application',
type: 'richText',
localized: true,
editor: lexicalEditor({}),
},
{
name: 'content',
type: 'richText',
localized: true,
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,

View File

@@ -0,0 +1,12 @@
import React from 'react';
export default function Icon() {
return (
<img
src="/logo-blue.svg"
alt="KLZ"
className="klz-admin-icon"
style={{ maxWidth: '100%', height: 'auto', maxHeight: '32px', display: 'block' }}
/>
);
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
export default function Logo() {
return (
<img
src="/logo-blue.svg"
alt="KLZ Cables"
className="klz-admin-logo"
style={{ maxWidth: '100%', height: 'auto', maxHeight: '40px', display: 'block' }}
/>
);
}

View File

@@ -29,12 +29,12 @@ export async function seedDatabase(payload: Payload) {
payload.logger.info('📦 No products found. Creating smoke test product (NAY2Y)...');
await payload.create({
collection: 'products',
locale: 'de',
data: {
title: 'NAY2Y Smoke Test',
sku: 'SMOKE-TEST-001',
slug: 'nay2y',
description: 'A dummy product for CI/CD smoke testing and OG image verification.',
locale: 'de',
categories: [{ category: 'Power Cables' }],
_status: 'published',
},