feat: complete MDX migration, stabilize environment & verify via E2E tests
Former-commit-id: ec3e64156a2e182535cbdcf0d975cd54603a517d
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const isDockerContainer =
|
||||
process.env.IS_DOCKER === 'true' || process.env.HOSTNAME?.includes('klz-app');
|
||||
const redisUrl =
|
||||
process.env.REDIS_URL ||
|
||||
(isDockerContainer ? 'redis://klz-redis:6379' : 'redis://localhost:6379');
|
||||
|
||||
// Only create a single instance in Node.js
|
||||
const globalForRedis = global as unknown as { redis: Redis };
|
||||
|
||||
export const redis =
|
||||
globalForRedis.redis ||
|
||||
new Redis(redisUrl, {
|
||||
maxRetriesPerRequest: 3,
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForRedis.redis = redis;
|
||||
}
|
||||
|
||||
export default redis;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,356 +0,0 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TYPE "public"."enum_posts_locale" AS ENUM('en', 'de');
|
||||
CREATE TYPE "public"."enum_posts_status" AS ENUM('draft', 'published');
|
||||
CREATE TYPE "public"."enum__posts_v_version_locale" AS ENUM('en', 'de');
|
||||
CREATE TYPE "public"."enum__posts_v_version_status" AS ENUM('draft', 'published');
|
||||
CREATE TYPE "public"."enum_form_submissions_type" AS ENUM('contact', 'product_quote');
|
||||
CREATE TYPE "public"."enum_products_locale" AS ENUM('en', 'de');
|
||||
CREATE TYPE "public"."enum_products_status" AS ENUM('draft', 'published');
|
||||
CREATE TYPE "public"."enum__products_v_version_locale" AS ENUM('en', 'de');
|
||||
CREATE TYPE "public"."enum__products_v_version_status" AS ENUM('draft', 'published');
|
||||
CREATE TABLE "users_sessions" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp(3) with time zone,
|
||||
"expires_at" timestamp(3) with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"email" varchar NOT NULL,
|
||||
"reset_password_token" varchar,
|
||||
"reset_password_expiration" timestamp(3) with time zone,
|
||||
"salt" varchar,
|
||||
"hash" varchar,
|
||||
"login_attempts" numeric DEFAULT 0,
|
||||
"lock_until" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE "media" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"alt" varchar NOT NULL,
|
||||
"caption" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"url" varchar,
|
||||
"thumbnail_u_r_l" varchar,
|
||||
"filename" varchar,
|
||||
"mime_type" varchar,
|
||||
"filesize" numeric,
|
||||
"width" numeric,
|
||||
"height" numeric,
|
||||
"focal_x" numeric,
|
||||
"focal_y" numeric,
|
||||
"sizes_thumbnail_url" varchar,
|
||||
"sizes_thumbnail_width" numeric,
|
||||
"sizes_thumbnail_height" numeric,
|
||||
"sizes_thumbnail_mime_type" varchar,
|
||||
"sizes_thumbnail_filesize" numeric,
|
||||
"sizes_thumbnail_filename" varchar,
|
||||
"sizes_card_url" varchar,
|
||||
"sizes_card_width" numeric,
|
||||
"sizes_card_height" numeric,
|
||||
"sizes_card_mime_type" varchar,
|
||||
"sizes_card_filesize" numeric,
|
||||
"sizes_card_filename" varchar,
|
||||
"sizes_hero_url" varchar,
|
||||
"sizes_hero_width" numeric,
|
||||
"sizes_hero_height" numeric,
|
||||
"sizes_hero_mime_type" varchar,
|
||||
"sizes_hero_filesize" numeric,
|
||||
"sizes_hero_filename" varchar,
|
||||
"sizes_hero_mobile_url" varchar,
|
||||
"sizes_hero_mobile_width" numeric,
|
||||
"sizes_hero_mobile_height" numeric,
|
||||
"sizes_hero_mobile_mime_type" varchar,
|
||||
"sizes_hero_mobile_filesize" numeric,
|
||||
"sizes_hero_mobile_filename" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "posts" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"title" varchar,
|
||||
"slug" varchar,
|
||||
"excerpt" varchar,
|
||||
"date" timestamp(3) with time zone,
|
||||
"featured_image_id" integer,
|
||||
"locale" "enum_posts_locale" DEFAULT 'en',
|
||||
"category" varchar,
|
||||
"content" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"_status" "enum_posts_status" DEFAULT 'draft'
|
||||
);
|
||||
|
||||
CREATE TABLE "_posts_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_title" varchar,
|
||||
"version_slug" varchar,
|
||||
"version_excerpt" varchar,
|
||||
"version_date" timestamp(3) with time zone,
|
||||
"version_featured_image_id" integer,
|
||||
"version_locale" "enum__posts_v_version_locale" DEFAULT 'en',
|
||||
"version_category" varchar,
|
||||
"version_content" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__posts_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
);
|
||||
|
||||
CREATE TABLE "form_submissions" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"email" varchar NOT NULL,
|
||||
"type" "enum_form_submissions_type" NOT NULL,
|
||||
"product_name" varchar,
|
||||
"message" varchar NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "products_categories" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"category" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "products" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"title" varchar,
|
||||
"sku" varchar,
|
||||
"slug" varchar,
|
||||
"description" varchar,
|
||||
"locale" "enum_products_locale" DEFAULT 'de',
|
||||
"application" jsonb,
|
||||
"content" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"_status" "enum_products_status" DEFAULT 'draft'
|
||||
);
|
||||
|
||||
CREATE TABLE "products_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"media_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "_products_v_version_categories" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"category" varchar,
|
||||
"_uuid" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "_products_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_title" varchar,
|
||||
"version_sku" varchar,
|
||||
"version_slug" varchar,
|
||||
"version_description" varchar,
|
||||
"version_locale" "enum__products_v_version_locale" DEFAULT 'de',
|
||||
"version_application" jsonb,
|
||||
"version_content" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__products_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
);
|
||||
|
||||
CREATE TABLE "_products_v_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"media_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_kv" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar NOT NULL,
|
||||
"data" jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_locked_documents" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"global_slug" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_locked_documents_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer,
|
||||
"media_id" integer,
|
||||
"posts_id" integer,
|
||||
"form_submissions_id" integer,
|
||||
"products_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_preferences" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar,
|
||||
"value" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_preferences_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_migrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"batch" numeric,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "posts" ADD CONSTRAINT "posts_featured_image_id_media_id_fk" FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "_posts_v" ADD CONSTRAINT "_posts_v_parent_id_posts_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."posts"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "_posts_v" ADD CONSTRAINT "_posts_v_version_featured_image_id_media_id_fk" FOREIGN KEY ("version_featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "products_categories" ADD CONSTRAINT "products_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "products_rels" ADD CONSTRAINT "products_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "products_rels" ADD CONSTRAINT "products_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "_products_v_version_categories" ADD CONSTRAINT "_products_v_version_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_products_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "_products_v" ADD CONSTRAINT "_products_v_parent_id_products_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."products"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "_products_v_rels" ADD CONSTRAINT "_products_v_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."_products_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "_products_v_rels" ADD CONSTRAINT "_products_v_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_locked_documents"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_posts_fk" FOREIGN KEY ("posts_id") REFERENCES "public"."posts"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_form_submissions_fk" FOREIGN KEY ("form_submissions_id") REFERENCES "public"."form_submissions"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_products_fk" FOREIGN KEY ("products_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "users_sessions_order_idx" ON "users_sessions" USING btree ("_order");
|
||||
CREATE INDEX "users_sessions_parent_id_idx" ON "users_sessions" USING btree ("_parent_id");
|
||||
CREATE INDEX "users_updated_at_idx" ON "users" USING btree ("updated_at");
|
||||
CREATE INDEX "users_created_at_idx" ON "users" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");
|
||||
CREATE INDEX "media_updated_at_idx" ON "media" USING btree ("updated_at");
|
||||
CREATE INDEX "media_created_at_idx" ON "media" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "media_filename_idx" ON "media" USING btree ("filename");
|
||||
CREATE INDEX "media_sizes_thumbnail_sizes_thumbnail_filename_idx" ON "media" USING btree ("sizes_thumbnail_filename");
|
||||
CREATE INDEX "media_sizes_card_sizes_card_filename_idx" ON "media" USING btree ("sizes_card_filename");
|
||||
CREATE INDEX "media_sizes_hero_sizes_hero_filename_idx" ON "media" USING btree ("sizes_hero_filename");
|
||||
CREATE INDEX "media_sizes_hero_mobile_sizes_hero_mobile_filename_idx" ON "media" USING btree ("sizes_hero_mobile_filename");
|
||||
CREATE UNIQUE INDEX "posts_slug_idx" ON "posts" USING btree ("slug");
|
||||
CREATE INDEX "posts_featured_image_idx" ON "posts" USING btree ("featured_image_id");
|
||||
CREATE INDEX "posts_updated_at_idx" ON "posts" USING btree ("updated_at");
|
||||
CREATE INDEX "posts_created_at_idx" ON "posts" USING btree ("created_at");
|
||||
CREATE INDEX "posts__status_idx" ON "posts" USING btree ("_status");
|
||||
CREATE INDEX "_posts_v_parent_idx" ON "_posts_v" USING btree ("parent_id");
|
||||
CREATE INDEX "_posts_v_version_version_slug_idx" ON "_posts_v" USING btree ("version_slug");
|
||||
CREATE INDEX "_posts_v_version_version_featured_image_idx" ON "_posts_v" USING btree ("version_featured_image_id");
|
||||
CREATE INDEX "_posts_v_version_version_updated_at_idx" ON "_posts_v" USING btree ("version_updated_at");
|
||||
CREATE INDEX "_posts_v_version_version_created_at_idx" ON "_posts_v" USING btree ("version_created_at");
|
||||
CREATE INDEX "_posts_v_version_version__status_idx" ON "_posts_v" USING btree ("version__status");
|
||||
CREATE INDEX "_posts_v_created_at_idx" ON "_posts_v" USING btree ("created_at");
|
||||
CREATE INDEX "_posts_v_updated_at_idx" ON "_posts_v" USING btree ("updated_at");
|
||||
CREATE INDEX "_posts_v_latest_idx" ON "_posts_v" USING btree ("latest");
|
||||
CREATE INDEX "form_submissions_updated_at_idx" ON "form_submissions" USING btree ("updated_at");
|
||||
CREATE INDEX "form_submissions_created_at_idx" ON "form_submissions" USING btree ("created_at");
|
||||
CREATE INDEX "products_categories_order_idx" ON "products_categories" USING btree ("_order");
|
||||
CREATE INDEX "products_categories_parent_id_idx" ON "products_categories" USING btree ("_parent_id");
|
||||
CREATE UNIQUE INDEX "products_sku_idx" ON "products" USING btree ("sku");
|
||||
CREATE INDEX "products_updated_at_idx" ON "products" USING btree ("updated_at");
|
||||
CREATE INDEX "products_created_at_idx" ON "products" USING btree ("created_at");
|
||||
CREATE INDEX "products__status_idx" ON "products" USING btree ("_status");
|
||||
CREATE INDEX "products_rels_order_idx" ON "products_rels" USING btree ("order");
|
||||
CREATE INDEX "products_rels_parent_idx" ON "products_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "products_rels_path_idx" ON "products_rels" USING btree ("path");
|
||||
CREATE INDEX "products_rels_media_id_idx" ON "products_rels" USING btree ("media_id");
|
||||
CREATE INDEX "_products_v_version_categories_order_idx" ON "_products_v_version_categories" USING btree ("_order");
|
||||
CREATE INDEX "_products_v_version_categories_parent_id_idx" ON "_products_v_version_categories" USING btree ("_parent_id");
|
||||
CREATE INDEX "_products_v_parent_idx" ON "_products_v" USING btree ("parent_id");
|
||||
CREATE INDEX "_products_v_version_version_sku_idx" ON "_products_v" USING btree ("version_sku");
|
||||
CREATE INDEX "_products_v_version_version_updated_at_idx" ON "_products_v" USING btree ("version_updated_at");
|
||||
CREATE INDEX "_products_v_version_version_created_at_idx" ON "_products_v" USING btree ("version_created_at");
|
||||
CREATE INDEX "_products_v_version_version__status_idx" ON "_products_v" USING btree ("version__status");
|
||||
CREATE INDEX "_products_v_created_at_idx" ON "_products_v" USING btree ("created_at");
|
||||
CREATE INDEX "_products_v_updated_at_idx" ON "_products_v" USING btree ("updated_at");
|
||||
CREATE INDEX "_products_v_latest_idx" ON "_products_v" USING btree ("latest");
|
||||
CREATE INDEX "_products_v_rels_order_idx" ON "_products_v_rels" USING btree ("order");
|
||||
CREATE INDEX "_products_v_rels_parent_idx" ON "_products_v_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "_products_v_rels_path_idx" ON "_products_v_rels" USING btree ("path");
|
||||
CREATE INDEX "_products_v_rels_media_id_idx" ON "_products_v_rels" USING btree ("media_id");
|
||||
CREATE UNIQUE INDEX "payload_kv_key_idx" ON "payload_kv" USING btree ("key");
|
||||
CREATE INDEX "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug");
|
||||
CREATE INDEX "payload_locked_documents_updated_at_idx" ON "payload_locked_documents" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_locked_documents_created_at_idx" ON "payload_locked_documents" USING btree ("created_at");
|
||||
CREATE INDEX "payload_locked_documents_rels_order_idx" ON "payload_locked_documents_rels" USING btree ("order");
|
||||
CREATE INDEX "payload_locked_documents_rels_parent_idx" ON "payload_locked_documents_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_path_idx" ON "payload_locked_documents_rels" USING btree ("path");
|
||||
CREATE INDEX "payload_locked_documents_rels_users_id_idx" ON "payload_locked_documents_rels" USING btree ("users_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_media_id_idx" ON "payload_locked_documents_rels" USING btree ("media_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_posts_id_idx" ON "payload_locked_documents_rels" USING btree ("posts_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_form_submissions_id_idx" ON "payload_locked_documents_rels" USING btree ("form_submissions_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_products_id_idx" ON "payload_locked_documents_rels" USING btree ("products_id");
|
||||
CREATE INDEX "payload_preferences_key_idx" ON "payload_preferences" USING btree ("key");
|
||||
CREATE INDEX "payload_preferences_updated_at_idx" ON "payload_preferences" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_preferences_created_at_idx" ON "payload_preferences" USING btree ("created_at");
|
||||
CREATE INDEX "payload_preferences_rels_order_idx" ON "payload_preferences_rels" USING btree ("order");
|
||||
CREATE INDEX "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "payload_preferences_rels_path_idx" ON "payload_preferences_rels" USING btree ("path");
|
||||
CREATE INDEX "payload_preferences_rels_users_id_idx" ON "payload_preferences_rels" USING btree ("users_id");
|
||||
CREATE INDEX "payload_migrations_updated_at_idx" ON "payload_migrations" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_migrations_created_at_idx" ON "payload_migrations" USING btree ("created_at");`);
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP TABLE "users_sessions" CASCADE;
|
||||
DROP TABLE "users" CASCADE;
|
||||
DROP TABLE "media" CASCADE;
|
||||
DROP TABLE "posts" CASCADE;
|
||||
DROP TABLE "_posts_v" CASCADE;
|
||||
DROP TABLE "form_submissions" CASCADE;
|
||||
DROP TABLE "products_categories" CASCADE;
|
||||
DROP TABLE "products" CASCADE;
|
||||
DROP TABLE "products_rels" CASCADE;
|
||||
DROP TABLE "_products_v_version_categories" CASCADE;
|
||||
DROP TABLE "_products_v" CASCADE;
|
||||
DROP TABLE "_products_v_rels" CASCADE;
|
||||
DROP TABLE "payload_kv" CASCADE;
|
||||
DROP TABLE "payload_locked_documents" CASCADE;
|
||||
DROP TABLE "payload_locked_documents_rels" CASCADE;
|
||||
DROP TABLE "payload_preferences" CASCADE;
|
||||
DROP TABLE "payload_preferences_rels" CASCADE;
|
||||
DROP TABLE "payload_migrations" CASCADE;
|
||||
DROP TYPE "public"."enum_posts_locale";
|
||||
DROP TYPE "public"."enum_posts_status";
|
||||
DROP TYPE "public"."enum__posts_v_version_locale";
|
||||
DROP TYPE "public"."enum__posts_v_version_status";
|
||||
DROP TYPE "public"."enum_form_submissions_type";
|
||||
DROP TYPE "public"."enum_products_locale";
|
||||
DROP TYPE "public"."enum_products_status";
|
||||
DROP TYPE "public"."enum__products_v_version_locale";
|
||||
DROP TYPE "public"."enum__products_v_version_status";`);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP INDEX "products_sku_idx";
|
||||
DROP INDEX "_products_v_version_version_sku_idx";`);
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX "products_sku_idx" ON "products" USING btree ("sku");
|
||||
CREATE INDEX "_products_v_version_version_sku_idx" ON "_products_v" USING btree ("version_sku");`);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "20260225_003500_add_pages_collection",
|
||||
"name": "20260225_003500_add_pages_collection",
|
||||
"batch": 3
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_pages_locale" AS ENUM('en', 'de');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"locale" "enum_pages_locale" NOT NULL,
|
||||
"excerpt" varchar,
|
||||
"featured_image_id" integer,
|
||||
"content" jsonb NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "pages" ADD CONSTRAINT "pages_featured_image_id_media_id_fk"
|
||||
FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id")
|
||||
ON DELETE set null ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_featured_image_idx" ON "pages" USING btree ("featured_image_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_updated_at_idx" ON "pages" USING btree ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "pages_created_at_idx" ON "pages" USING btree ("created_at");
|
||||
|
||||
-- Add pages_id to payload_locked_documents_rels if not already present
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN IF NOT EXISTS "pages_id" integer;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_pages_fk"
|
||||
FOREIGN KEY ("pages_id") REFERENCES "public"."pages"("id")
|
||||
ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_pages_id_idx"
|
||||
ON "payload_locked_documents_rels" USING btree ("pages_id");
|
||||
`);
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT IF EXISTS "payload_locked_documents_rels_pages_fk";
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN IF EXISTS "pages_id";
|
||||
DROP TABLE IF EXISTS "pages" CASCADE;
|
||||
DROP TYPE IF EXISTS "public"."enum_pages_locale";
|
||||
`);
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
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 $$
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_pages_status" AS ENUM('draft', 'published');
|
||||
EXCEPTION WHEN duplicate_object THEN null; END $$
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_posts_status" AS ENUM('draft', 'published');
|
||||
EXCEPTION WHEN duplicate_object THEN null; END $$
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_products_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`
|
||||
CREATE TABLE IF NOT EXISTS "_posts_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_title" varchar,
|
||||
"version_slug" varchar,
|
||||
"version_excerpt" varchar,
|
||||
"version_content" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__posts_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
)
|
||||
`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "_products_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_title" varchar,
|
||||
"version_description" varchar,
|
||||
"version_content" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__products_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
)
|
||||
`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "_pages_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_title" varchar,
|
||||
"version_slug" varchar,
|
||||
"version_excerpt" varchar,
|
||||
"version_content" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__pages_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
)
|
||||
`);
|
||||
|
||||
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`);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
// Add featured_image_id to products and _products_v
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "products" ADD COLUMN IF NOT EXISTS "featured_image_id" integer;
|
||||
`);
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "version_featured_image_id" integer;
|
||||
`);
|
||||
|
||||
// Add foreign key constraints
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "products" ADD CONSTRAINT "products_featured_image_id_media_id_fk" FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN null; END $$;
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_products_v" ADD CONSTRAINT "_products_v_version_featured_image_id_media_id_fk" FOREIGN KEY ("version_featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN null; END $$;
|
||||
`);
|
||||
|
||||
// Add indexes
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "products_featured_image_idx" ON "products" USING btree ("featured_image_id");
|
||||
`);
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "_products_v_version_version_featured_image_idx" ON "_products_v" USING btree ("version_featured_image_id");
|
||||
`);
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "products_featured_image_id_media_id_fk";
|
||||
`);
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "_products_v" DROP CONSTRAINT IF EXISTS "_products_v_version_featured_image_id_media_id_fk";
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DROP INDEX IF EXISTS "products_featured_image_idx";
|
||||
`);
|
||||
await db.execute(sql`
|
||||
DROP INDEX IF EXISTS "_products_v_version_version_featured_image_idx";
|
||||
`);
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "products" DROP COLUMN IF EXISTS "featured_image_id";
|
||||
`);
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "_products_v" DROP COLUMN IF EXISTS "version_featured_image_id";
|
||||
`);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_url" varchar;
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_width" numeric;
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_height" numeric;
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_mime_type" varchar;
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_filesize" numeric;
|
||||
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_filename" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "media_sizes_tablet_sizes_tablet_filename_idx" ON "media" USING btree ("sizes_tablet_filename");
|
||||
`);
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP INDEX IF EXISTS "media_sizes_tablet_sizes_tablet_filename_idx";
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_filename";
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_filesize";
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_mime_type";
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_height";
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_width";
|
||||
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_url";
|
||||
`);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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';
|
||||
import * as migration_20260305_215000_products_featured_image from './20260305_215000_products_featured_image';
|
||||
import * as migration_20260305_222000_media_sizes_tablet from './20260305_222000_media_sizes_tablet';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20260223_195005_products_collection.up,
|
||||
down: migration_20260223_195005_products_collection.down,
|
||||
name: '20260223_195005_products_collection',
|
||||
},
|
||||
{
|
||||
up: migration_20260223_195151_remove_sku_unique.up,
|
||||
down: migration_20260223_195151_remove_sku_unique.down,
|
||||
name: '20260223_195151_remove_sku_unique',
|
||||
},
|
||||
{
|
||||
up: migration_20260225_003500_add_pages_collection.up,
|
||||
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',
|
||||
},
|
||||
{
|
||||
up: migration_20260305_215000_products_featured_image.up,
|
||||
down: migration_20260305_215000_products_featured_image.down,
|
||||
name: '20260305_215000_products_featured_image',
|
||||
},
|
||||
{
|
||||
up: migration_20260305_222000_media_sizes_tablet.up,
|
||||
down: migration_20260305_222000_media_sizes_tablet.down,
|
||||
name: '20260305_222000_media_sizes_tablet',
|
||||
},
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
import type { Access } from 'payload';
|
||||
|
||||
export const anyone: Access = () => true;
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { Access } from 'payload';
|
||||
|
||||
export const authenticated: Access = ({ req: { user } }) => {
|
||||
return Boolean(user);
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import AnimatedImageComponent from '@/components/blog/AnimatedImage';
|
||||
|
||||
export const AnimatedImage: EtibBlock = {
|
||||
slug: 'animatedImage',
|
||||
render: AnimatedImageComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'src',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { Callout as CalloutComponent } from '@/components/ui/Callout';
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const Callout: EtibBlock = {
|
||||
slug: 'callout',
|
||||
render: CalloutComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
options: ['info', 'warning', 'important', 'tip', 'caution'],
|
||||
defaultValue: 'info',
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({}),
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
import ChatBubbleComponent from '@/components/blog/ChatBubble';
|
||||
|
||||
export const ChatBubble: EtibBlock = {
|
||||
slug: 'chatBubble',
|
||||
render: ChatBubbleComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'author',
|
||||
type: 'text',
|
||||
defaultValue: 'KLZ Team',
|
||||
},
|
||||
{
|
||||
name: 'avatar',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
type: 'text',
|
||||
defaultValue: 'Assistant',
|
||||
},
|
||||
{
|
||||
name: 'align',
|
||||
type: 'select',
|
||||
options: ['left', 'right'],
|
||||
defaultValue: 'left',
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({}),
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { CompanyTimeline } from '@/components/blocks/CompanyTimeline';
|
||||
|
||||
export const CompanyTimelineBlock: EtibBlock = {
|
||||
slug: 'companyTimeline',
|
||||
interfaceName: 'CompanyTimelineBlock',
|
||||
render: CompanyTimeline,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
],
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import ComparisonGridComponent from '@/components/blog/ComparisonGrid';
|
||||
|
||||
export const ComparisonGrid: EtibBlock = {
|
||||
slug: 'comparisonGrid',
|
||||
render: ComparisonGridComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Main Heading',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'leftLabel',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'rightLabel',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
label: 'Row Label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'leftValue',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'rightValue',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { ContactSection as ContactSectionComponent } from '@/components/blocks/ContactSection';
|
||||
|
||||
export const ContactSection: EtibBlock = {
|
||||
slug: 'contactSection',
|
||||
interfaceName: 'ContactSectionBlock',
|
||||
render: ContactSectionComponent,
|
||||
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',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { HeroSection as HeroSectionComponent } from '@/components/blocks/HeroSection';
|
||||
|
||||
export const HeroSection: EtibBlock = {
|
||||
slug: 'heroSection',
|
||||
interfaceName: 'HeroSectionBlock',
|
||||
render: HeroSectionComponent,
|
||||
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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Block } from 'payload';
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const HighlightBox: Block = {
|
||||
slug: 'highlightBox',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
options: ['info', 'warning', 'success', 'error', 'neutral'],
|
||||
defaultValue: 'neutral',
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({}),
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,240 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { HeroVideo } from '@/components/blocks/HeroVideo';
|
||||
import { SubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
|
||||
import { CompetenceBentoGrid } from '@/components/blocks/CompetenceBentoGrid';
|
||||
import { ReferencesSlider } from '@/components/blocks/ReferencesSlider';
|
||||
import Hero from '@/components/home/Hero';
|
||||
import ProductCategories from '@/components/home/ProductCategories';
|
||||
import WhatWeDo from '@/components/home/WhatWeDo';
|
||||
import RecentPosts from '@/components/home/RecentPosts';
|
||||
import Experience from '@/components/home/Experience';
|
||||
import WhyChooseUs from '@/components/home/WhyChooseUs';
|
||||
import MeetTheTeam from '@/components/home/MeetTheTeam';
|
||||
import GallerySection from '@/components/home/GallerySection';
|
||||
import VideoSection from '@/components/home/VideoSection';
|
||||
import CTA from '@/components/home/CTA';
|
||||
|
||||
export const HomeHero: EtibBlock = {
|
||||
slug: 'homeHero',
|
||||
interfaceName: 'HomeHeroBlock',
|
||||
render: HeroVideo,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
{ name: 'videoUrl', type: 'text', admin: { description: 'URL to the background mp4 video' } },
|
||||
{ name: 'posterImage', type: 'upload', relationTo: 'media' },
|
||||
{ name: 'ctaLabel', type: 'text', localized: true },
|
||||
{ name: 'ctaHref', type: 'text' },
|
||||
{ name: 'secondaryCtaLabel', type: 'text', localized: true },
|
||||
{ name: 'secondaryCtaHref', type: 'text' },
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeSubCompanyTiles: EtibBlock = {
|
||||
slug: 'homeSubCompanyTiles',
|
||||
interfaceName: 'HomeSubCompanyTilesBlock',
|
||||
render: SubCompanyTiles,
|
||||
fields: [
|
||||
{ name: 'badge', type: 'text', localized: true },
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{
|
||||
name: 'companies',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'icon', type: 'text', admin: { description: 'SVG path for the icon' } },
|
||||
{ name: 'backgroundImage', type: 'upload', relationTo: 'media' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeCompetenceBentoGrid: EtibBlock = {
|
||||
slug: 'homeCompetenceBentoGrid',
|
||||
interfaceName: 'HomeCompetenceBentoGridBlock',
|
||||
render: CompetenceBentoGrid,
|
||||
fields: [
|
||||
{ name: 'badge', type: 'text', localized: true },
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'ctaLabel', type: 'text', localized: true },
|
||||
{ name: 'ctaHref', type: 'text' },
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'tag', type: 'text' },
|
||||
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||||
{
|
||||
name: 'size',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Large (2x2)', value: 'large' },
|
||||
{ label: 'Medium (1x2)', value: 'medium' },
|
||||
{ label: 'Small (1x1)', value: 'small' },
|
||||
{ label: 'Accent (Text only)', value: 'accent' },
|
||||
],
|
||||
defaultValue: 'small',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeReferencesSlider: EtibBlock = {
|
||||
slug: 'homeReferencesSlider',
|
||||
interfaceName: 'HomeReferencesSliderBlock',
|
||||
render: ReferencesSlider,
|
||||
fields: [
|
||||
{ name: 'badge', type: 'text', localized: true },
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'description', type: 'textarea', localized: true },
|
||||
{ name: 'ctaLabel', type: 'text', localized: true },
|
||||
{ name: 'ctaHref', type: 'text' },
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeProductCategories: EtibBlock = {
|
||||
slug: 'homeProductCategories',
|
||||
interfaceName: 'HomeProductCategoriesBlock',
|
||||
render: ProductCategories,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeWhatWeDo: EtibBlock = {
|
||||
slug: 'homeWhatWeDo',
|
||||
interfaceName: 'HomeWhatWeDoBlock',
|
||||
render: WhatWeDo,
|
||||
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: EtibBlock = {
|
||||
slug: 'homeRecentPosts',
|
||||
interfaceName: 'HomeRecentPostsBlock',
|
||||
render: RecentPosts,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeExperience: EtibBlock = {
|
||||
slug: 'homeExperience',
|
||||
interfaceName: 'HomeExperienceBlock',
|
||||
render: Experience,
|
||||
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: EtibBlock = {
|
||||
slug: 'homeWhyChooseUs',
|
||||
interfaceName: 'HomeWhyChooseUsBlock',
|
||||
render: WhyChooseUs,
|
||||
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: EtibBlock = {
|
||||
slug: 'homeMeetTheTeam',
|
||||
interfaceName: 'HomeMeetTheTeamBlock',
|
||||
render: MeetTheTeam,
|
||||
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: EtibBlock = {
|
||||
slug: 'homeGallery',
|
||||
interfaceName: 'HomeGalleryBlock',
|
||||
render: GallerySection,
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
],
|
||||
};
|
||||
|
||||
export const HomeVideo: EtibBlock = {
|
||||
slug: 'homeVideo',
|
||||
interfaceName: 'HomeVideoBlock',
|
||||
render: VideoSection,
|
||||
fields: [{ name: 'title', type: 'text', localized: true }],
|
||||
};
|
||||
|
||||
export const HomeCTA: EtibBlock = {
|
||||
slug: 'homeCTA',
|
||||
interfaceName: 'HomeCTABlock',
|
||||
render: CTA,
|
||||
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,
|
||||
HomeSubCompanyTiles,
|
||||
HomeCompetenceBentoGrid,
|
||||
HomeReferencesSlider,
|
||||
HomeProductCategories,
|
||||
HomeWhatWeDo,
|
||||
HomeRecentPosts,
|
||||
HomeExperience,
|
||||
HomeWhyChooseUs,
|
||||
HomeMeetTheTeam,
|
||||
HomeGallery,
|
||||
HomeVideo,
|
||||
HomeCTA,
|
||||
];
|
||||
@@ -1,27 +0,0 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { JobListingBlock as JobListingComponent } from '@/components/blocks/JobListingBlock';
|
||||
|
||||
export const JobListingBlock: EtibBlock = {
|
||||
slug: 'jobListing',
|
||||
interfaceName: 'JobListingBlock',
|
||||
render: JobListingComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
defaultValue: 'Aktuelle Stellenangebote',
|
||||
},
|
||||
{
|
||||
name: 'showFairs',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Nächste Messetermine anzeigen',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { PDFDownloadBlock } from '@/components/PDFDownloadBlock';
|
||||
|
||||
export const PDFDownload: EtibBlock = {
|
||||
slug: 'pdfDownload',
|
||||
render: PDFDownloadBlock,
|
||||
labels: {
|
||||
singular: 'PDF Download',
|
||||
plural: 'PDF Downloads',
|
||||
},
|
||||
admin: {},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Button Beschriftung',
|
||||
required: true,
|
||||
localized: true,
|
||||
defaultValue: 'Als PDF herunterladen',
|
||||
},
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'primary',
|
||||
options: [
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Secondary', value: 'secondary' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Block } from 'payload';
|
||||
|
||||
export const PowerCTA: Block = {
|
||||
slug: 'powerCTA',
|
||||
fields: [
|
||||
{
|
||||
name: 'locale',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
import ProductTabsComponent from '@/components/ProductTabs';
|
||||
|
||||
export const ProductTabs: EtibBlock = {
|
||||
slug: 'productTabs',
|
||||
interfaceName: 'ProductTabsBlock',
|
||||
render: ProductTabsComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'technicalItems',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'unit',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'voltageTables',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'voltageLabel',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'metaItems',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'unit',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'rows',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'configuration',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'cells',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
// We don't need blocks inside ProductTabs for now to avoid complexity/recursion
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import SplitHeadingComponent from '@/components/blog/SplitHeading';
|
||||
|
||||
export const SplitHeading: EtibBlock = {
|
||||
slug: 'splitHeading',
|
||||
interfaceName: 'SplitHeadingBlock',
|
||||
render: SplitHeadingComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
type: 'select',
|
||||
options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
||||
defaultValue: 'h2',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import StatsComponent from '@/components/blog/Stats';
|
||||
|
||||
export const Stats: EtibBlock = {
|
||||
slug: 'stats',
|
||||
interfaceName: 'StatsBlock',
|
||||
render: StatsComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'stats',
|
||||
type: 'array',
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import StickyNarrativeComponent from '@/components/blog/StickyNarrative';
|
||||
|
||||
export const StickyNarrative: EtibBlock = {
|
||||
slug: 'stickyNarrative',
|
||||
render: StickyNarrativeComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Main Heading',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { SupportCTA as SupportCTAComponent } from '@/components/blocks/SupportCTA';
|
||||
|
||||
export const SupportCTA: EtibBlock = {
|
||||
slug: 'supportCTA',
|
||||
interfaceName: 'SupportCTABlock',
|
||||
render: SupportCTAComponent,
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { TeamGridBlock as TeamGridComponent } from '@/components/blocks/TeamGridBlock';
|
||||
|
||||
export const TeamGrid: EtibBlock = {
|
||||
slug: 'teamGrid',
|
||||
interfaceName: 'TeamGridBlock',
|
||||
render: TeamGridComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
defaultValue: 'Ihre Ansprechpartner',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
defaultValue: 'Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.',
|
||||
},
|
||||
{
|
||||
name: 'filterBranch',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'E-TIB GmbH', value: 'e-tib' },
|
||||
{ label: 'Bohrtechnik', value: 'bohr' },
|
||||
{ label: 'Ingenieurgesellschaft', value: 'ing' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import { TeamProfile as TeamProfileComponent } from '@/components/blocks/TeamProfile';
|
||||
|
||||
export const TeamProfile: EtibBlock = {
|
||||
slug: 'teamProfile',
|
||||
interfaceName: 'TeamProfileBlock',
|
||||
render: TeamProfileComponent,
|
||||
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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import TechnicalGridComponent from '@/components/blog/TechnicalGrid';
|
||||
|
||||
export const TechnicalGrid: EtibBlock = {
|
||||
slug: 'technicalGrid',
|
||||
render: TechnicalGridComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Main Heading',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { EtibBlock } from './types';
|
||||
import VisualLinkPreviewComponent from '@/components/blog/VisualLinkPreview';
|
||||
|
||||
export const VisualLinkPreview: EtibBlock = {
|
||||
slug: 'visualLinkPreview',
|
||||
render: VisualLinkPreviewComponent,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'href',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
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 { PDFDownload } from './PDFDownload';
|
||||
import { TeamGrid } from './TeamGrid';
|
||||
import { JobListingBlock } from './JobListing';
|
||||
import { CompanyTimelineBlock } from './CompanyTimeline';
|
||||
import { SupportCTA } from './SupportCTA';
|
||||
import { homeBlocksArray } from './HomeBlocks';
|
||||
import { EtibBlock } from './types';
|
||||
|
||||
/**
|
||||
* All blocks including their React 'render' property.
|
||||
* Used by the frontend renderer (PayloadRichText.tsx).
|
||||
*/
|
||||
export const allBlocks: EtibBlock[] = [
|
||||
...homeBlocksArray,
|
||||
AnimatedImage,
|
||||
Callout,
|
||||
CategoryGrid,
|
||||
ChatBubble,
|
||||
ComparisonGrid,
|
||||
ContactSection,
|
||||
HeroSection,
|
||||
HighlightBox,
|
||||
ImageGallery,
|
||||
ManifestoGrid,
|
||||
PowerCTA,
|
||||
ProductTabs,
|
||||
SplitHeading,
|
||||
Stats,
|
||||
StickyNarrative,
|
||||
TeamProfile,
|
||||
TechnicalGrid,
|
||||
VisualLinkPreview,
|
||||
PDFDownload,
|
||||
TeamGrid,
|
||||
JobListingBlock,
|
||||
CompanyTimelineBlock,
|
||||
SupportCTA,
|
||||
];
|
||||
|
||||
/**
|
||||
* Blocks stripped of the 'render' property.
|
||||
* Used by Payload CMS configuration to avoid importing React components into the server build.
|
||||
*/
|
||||
export const payloadBlocks = allBlocks.map(({ render, ...block }) => block);
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { Block } from 'payload';
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
export type EtibBlock = Block & {
|
||||
render?: ComponentType<any>;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
import { authenticated } from '../access/authenticated';
|
||||
import { anyone } from '../access/anyone';
|
||||
|
||||
export const Competences: CollectionConfig = {
|
||||
slug: 'competences',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'updatedAt'],
|
||||
},
|
||||
access: {
|
||||
read: anyone,
|
||||
create: authenticated,
|
||||
update: authenticated,
|
||||
delete: authenticated,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
|
||||
export const FormSubmissions: CollectionConfig = {
|
||||
slug: 'form-submissions',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
defaultColumns: ['name', 'email', 'type', 'createdAt'],
|
||||
description: 'Captured leads from Contact and Product Quote forms.',
|
||||
},
|
||||
access: {
|
||||
// Only Admins can view and delete leads via dashboard.
|
||||
read: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development',
|
||||
update: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development',
|
||||
delete: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development',
|
||||
// Next.js server actions handle secure inserts natively. No public client create access.
|
||||
create: () => false,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'General Contact', value: 'contact' },
|
||||
{ label: 'Product Quote', value: 'product_quote' },
|
||||
{ label: 'Brochure Download', value: 'brochure_download' },
|
||||
],
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'productName',
|
||||
type: 'text',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
readOnly: true,
|
||||
condition: (data) => data.type === 'product_quote',
|
||||
description: 'The specific KLZ product the user requested a quote for.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
import { authenticated } from '../access/authenticated';
|
||||
import { anyone } from '../access/anyone';
|
||||
|
||||
export const Jobs: CollectionConfig = {
|
||||
slug: 'jobs',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'department', 'type'],
|
||||
},
|
||||
access: {
|
||||
read: anyone,
|
||||
create: authenticated,
|
||||
update: authenticated,
|
||||
delete: authenticated,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'department',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'location',
|
||||
type: 'text',
|
||||
defaultValue: 'Guben / Bülstedt',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Vollzeit', value: 'vollzeit' },
|
||||
{ label: 'Teilzeit', value: 'teilzeit' },
|
||||
{ label: 'Ausbildung', value: 'ausbildung' },
|
||||
{ label: 'Praktikum', value: 'praktikum' },
|
||||
],
|
||||
required: true,
|
||||
defaultValue: 'vollzeit',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'filename',
|
||||
defaultColumns: ['filename', 'alt', 'updatedAt'],
|
||||
},
|
||||
upload: {
|
||||
staticDir: 'public/media',
|
||||
adminThumbnail: 'thumbnail',
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'thumbnail',
|
||||
width: 600,
|
||||
// height: undefined allows wide 5:1 aspect ratios to be preserved without cropping
|
||||
height: undefined,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'card',
|
||||
width: 768,
|
||||
height: undefined,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
width: 1024,
|
||||
height: undefined,
|
||||
position: 'centre',
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
import { CollectionConfig } from 'payload';
|
||||
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical';
|
||||
import { payloadBlocks } from '../blocks/allBlocks';
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'layout', '_status', 'updatedAt'],
|
||||
},
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (process.env.NODE_ENV === 'development' || process.env.TARGET === 'staging') {
|
||||
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: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
options: [
|
||||
{ label: 'Default (Article)', value: 'default' },
|
||||
{ label: 'Full Bleed (Blocks Only)', value: 'fullBleed' },
|
||||
],
|
||||
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',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featuredImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: payloadBlocks,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,139 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
import { StickyNarrative } from '../blocks/StickyNarrative';
|
||||
import { ComparisonGrid } from '../blocks/ComparisonGrid';
|
||||
import { VisualLinkPreview } from '../blocks/VisualLinkPreview';
|
||||
import { TechnicalGrid } from '../blocks/TechnicalGrid';
|
||||
import { HighlightBox } from '../blocks/HighlightBox';
|
||||
import { AnimatedImage } from '../blocks/AnimatedImage';
|
||||
import { ChatBubble } from '../blocks/ChatBubble';
|
||||
import { PowerCTA } from '../blocks/PowerCTA';
|
||||
import { Callout } from '../blocks/Callout';
|
||||
import { Stats } from '../blocks/Stats';
|
||||
import { SplitHeading } from '../blocks/SplitHeading';
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
admin: {
|
||||
defaultColumns: ['featuredImage', 'title', 'date', 'updatedAt', '_status'],
|
||||
},
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (process.env.NODE_ENV === 'development' || process.env.TARGET === 'staging') {
|
||||
return true;
|
||||
}
|
||||
if (user) {
|
||||
return true;
|
||||
}
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
},
|
||||
{
|
||||
date: {
|
||||
less_than_equal: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: 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 }) => {
|
||||
if (value || !data?.title) return value;
|
||||
return data.title
|
||||
.toLowerCase()
|
||||
.replace(/ /g, '-')
|
||||
.replace(/[^\w-]+/g, '');
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'A short summary for blog feed cards and SEO.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
type: 'date',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Future dates will schedule the post to publish automatically.',
|
||||
},
|
||||
defaultValue: () => new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
name: 'featuredImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'The primary Hero image used for headers and OpenGraph previews.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Used for tag bucketing (e.g. "Kabel Technologie").',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
StickyNarrative,
|
||||
ComparisonGrid,
|
||||
VisualLinkPreview,
|
||||
TechnicalGrid,
|
||||
HighlightBox,
|
||||
AnimatedImage,
|
||||
ChatBubble,
|
||||
PowerCTA,
|
||||
Callout,
|
||||
Stats,
|
||||
SplitHeading,
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
import { authenticated } from '../access/authenticated';
|
||||
import { anyone } from '../access/anyone';
|
||||
|
||||
export const References: CollectionConfig = {
|
||||
slug: 'references',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'category', 'updatedAt'],
|
||||
},
|
||||
access: {
|
||||
read: anyone,
|
||||
create: authenticated,
|
||||
update: authenticated,
|
||||
delete: authenticated,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Kabelbau', value: 'kabelbau' },
|
||||
{ label: 'Bohrtechnik', value: 'bohrtechnik' },
|
||||
{ label: 'Vermessung', value: 'vermessung' },
|
||||
{ label: 'Planung', value: 'planung' },
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
import { authenticated } from '../access/authenticated';
|
||||
import { anyone } from '../access/anyone';
|
||||
|
||||
export const Team: CollectionConfig = {
|
||||
slug: 'team',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
defaultColumns: ['name', 'position', 'email'],
|
||||
},
|
||||
access: {
|
||||
read: anyone,
|
||||
create: authenticated,
|
||||
update: authenticated,
|
||||
delete: authenticated,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'E-TIB GmbH', value: 'e-tib' },
|
||||
{ label: 'Ingenieurgesellschaft', value: 'ing' },
|
||||
{ label: 'Bohrtechnik', value: 'bohr' },
|
||||
{ label: 'Verwaltung', value: 'verw' },
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload';
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
// Email added by default
|
||||
],
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { GlobalConfig } from 'payload';
|
||||
import { authenticated } from '../access/authenticated';
|
||||
import { anyone } from '../access/anyone';
|
||||
|
||||
export const Settings: GlobalConfig = {
|
||||
slug: 'settings',
|
||||
access: {
|
||||
read: anyone,
|
||||
update: authenticated,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Company Information',
|
||||
fields: [
|
||||
{
|
||||
name: 'contactEmail',
|
||||
type: 'email',
|
||||
required: true,
|
||||
defaultValue: 'info@e-tib.com',
|
||||
},
|
||||
{
|
||||
name: 'contactPhone',
|
||||
type: 'text',
|
||||
defaultValue: '+49 15207230518',
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'textarea',
|
||||
defaultValue: 'Gewerbestraße 22\\n03172 Guben',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Navigation',
|
||||
fields: [
|
||||
{
|
||||
name: 'mainNav',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,836 +0,0 @@
|
||||
import { Payload } from 'payload';
|
||||
|
||||
export async function seedDatabase(payload: Payload) {
|
||||
// Check if any users exist
|
||||
const { totalDocs: totalUsers } = await payload.find({
|
||||
collection: 'users',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalUsers === 0) {
|
||||
payload.logger.info('👤 No users found. Creating default admin user...');
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'admin@mintel.me',
|
||||
password: 'klz-admin-setup',
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Default admin user created successfully.');
|
||||
}
|
||||
|
||||
// Seed Team Members
|
||||
const { totalDocs: totalTeam } = await payload.find({
|
||||
collection: 'team',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalTeam === 0) {
|
||||
payload.logger.info('👥 Seeding team members...');
|
||||
await payload.create({
|
||||
collection: 'team',
|
||||
data: {
|
||||
name: 'Danny Joseph',
|
||||
position: 'Geschäftsführer',
|
||||
email: 'd.joseph@e-tib.com',
|
||||
phone: '+49 15207230518',
|
||||
branch: 'e-tib',
|
||||
},
|
||||
});
|
||||
await payload.create({
|
||||
collection: 'team',
|
||||
data: {
|
||||
name: 'Technischer Leiter',
|
||||
position: 'Projektierung & Planung',
|
||||
email: 'planung@e-tib.com',
|
||||
phone: '+49 4283 6979923',
|
||||
branch: 'ing',
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Team members seeded.');
|
||||
}
|
||||
|
||||
// Seed References
|
||||
const { totalDocs: totalRefs } = await payload.find({
|
||||
collection: 'references',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalRefs === 0) {
|
||||
payload.logger.info('🏗️ Seeding references...');
|
||||
await payload.create({
|
||||
collection: 'references',
|
||||
data: {
|
||||
title: 'Breitbandausbau FTTC',
|
||||
slug: 'breitbandausbau-fttc-spreewald',
|
||||
category: 'kabelbau',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Ausbau der Breitband-Infrastruktur (FTTC) in der Planungsregion Spreewald.',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await payload.create({
|
||||
collection: 'references',
|
||||
data: {
|
||||
title: 'Breitbandausbau FTTH',
|
||||
slug: 'breitbandausbau-ftth-bomlitz',
|
||||
category: 'kabelbau',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Umfangreicher FTTH-Ausbau in 29699 Bomlitz und 29690 Schwarmstedt.',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await payload.create({
|
||||
collection: 'references',
|
||||
data: {
|
||||
title: 'Neubau Kabeltrasse Forst',
|
||||
slug: 'neubau-kabeltrasse-forst',
|
||||
category: 'kabelbau',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Errichtung einer neuen Kabeltrasse in 03149 Forst.',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await payload.create({
|
||||
collection: 'references',
|
||||
data: {
|
||||
title: 'Kabelverbindung U30 - T10',
|
||||
slug: 'kabelverbindung-eisenhuettenstadt',
|
||||
category: 'kabelbau',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Realisierung der Kabelverbindung U30 - T10 in 15890 Eisenhüttenstadt.',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await payload.create({
|
||||
collection: 'references',
|
||||
data: {
|
||||
title: 'Einspeisung PV-Anlage',
|
||||
slug: 'einspeisung-pv-goerne',
|
||||
category: 'kabelbau',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Netzanschluss und Einspeisung für eine PV-Anlage in 14728 Görne.',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ References seeded.');
|
||||
}
|
||||
|
||||
// Seed Jobs
|
||||
const { totalDocs: totalJobs } = await payload.find({
|
||||
collection: 'jobs',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalJobs === 0) {
|
||||
payload.logger.info('💼 Seeding jobs...');
|
||||
await payload.create({
|
||||
collection: 'jobs',
|
||||
data: {
|
||||
title: 'Initiativbewerbung (m/w/d)',
|
||||
slug: 'initiativbewerbung',
|
||||
department: 'Alle Bereiche',
|
||||
location: 'Guben / Bundesweit',
|
||||
type: 'vollzeit',
|
||||
description: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Sie können sich vorstellen, eine langfristige Herausforderung in unserem Unternehmen anzunehmen, finden aber kein passendes Stellenangebot? Bewerben Sie sich initiativ!',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Wir bieten unseren Mitarbeitern:',
|
||||
version: 1,
|
||||
format: 1, // bold
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
listType: 'bullet',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{ type: 'listitem', value: 1, version: 1, children: [{ type: 'text', text: 'Einsatz bei regionalen und überregionalen Bauvorhaben', version: 1 }] },
|
||||
{ type: 'listitem', value: 2, version: 1, children: [{ type: 'text', text: 'Sicherer, unbefristeter Arbeitsvertrag in Vollzeit', version: 1 }] },
|
||||
{ type: 'listitem', value: 3, version: 1, children: [{ type: 'text', text: 'Entsprechende Einarbeitung in einem engagierten Team', version: 1 }] },
|
||||
{ type: 'listitem', value: 4, version: 1, children: [{ type: 'text', text: 'Leistungsbezogene und faire Vergütung', version: 1 }] },
|
||||
{ type: 'listitem', value: 5, version: 1, children: [{ type: 'text', text: 'Auslöse bis 30,00 EUR pro Arbeitstag', version: 1 }] },
|
||||
{ type: 'listitem', value: 6, version: 1, children: [{ type: 'text', text: '31 Tage Urlaub sowie Urlaubsgeld', version: 1 }] },
|
||||
{ type: 'listitem', value: 7, version: 1, children: [{ type: 'text', text: 'Arbeitskleidung (Engelbert-Strauss)', version: 1 }] },
|
||||
{ type: 'listitem', value: 8, version: 1, children: [{ type: 'text', text: 'Monatliche Zusatzzahlungen (persönliche MasterCard)', version: 1 }] },
|
||||
{ type: 'listitem', value: 9, version: 1, children: [{ type: 'text', text: 'Zuzahlung im Rahmen der betrieblichen Altersvorsorge', version: 1 }] },
|
||||
{ type: 'listitem', value: 10, version: 1, children: [{ type: 'text', text: 'Individuelle Weiterbildungs- und Entwicklungsmöglichkeiten', version: 1 }] },
|
||||
{ type: 'listitem', value: 11, version: 1, children: [{ type: 'text', text: 'Stellung Fahrzeug zur betrieblichen Nutzung und Fahrten zur Baustelle', version: 1 }] },
|
||||
{ type: 'listitem', value: 12, version: 1, children: [{ type: 'text', text: 'Familiäres Betriebsklima mit betrieblich organisierten Veranstaltungen', version: 1 }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Jobs seeded.');
|
||||
}
|
||||
|
||||
// 1. Seed Home Page for both locales
|
||||
for (const loc of ['de', 'en']) {
|
||||
const { docs } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'home' } },
|
||||
locale: loc as any,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (docs.length === 0) {
|
||||
payload.logger.info(`📄 Seeding [${loc}] home page (not found for this locale)...`);
|
||||
// Try to find the document by slug in ANY locale first to avoid duplicate IDs
|
||||
const { docs: anyLocaleDocs } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'home' } },
|
||||
locale: 'all',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (anyLocaleDocs.length > 0) {
|
||||
payload.logger.info(`📝 Updating existing Home page with [${loc}] content...`);
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
id: anyLocaleDocs[0].id,
|
||||
locale: loc as any,
|
||||
data: {
|
||||
title: loc === 'de' ? 'Startseite' : 'Home',
|
||||
slug: 'home',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeHero',
|
||||
badge: loc === 'de' ? 'Zukunftssichere Infrastruktur' : 'Future-Proof Infrastructure',
|
||||
title: loc === 'de' ? 'Energie & Kommunikation vernetzen' : 'Networking Energy & Communication',
|
||||
description: loc === 'de'
|
||||
? 'Wir realisieren komplexe Versorgungsleitungen für eine moderne Gesellschaft. Verlässlich, innovativ und mit höchster Präzision im Kabelbau und der Bohrtechnik.'
|
||||
: 'We realize complex supply lines for a modern society. Reliable, innovative and with the highest precision in cable construction and drilling technology.',
|
||||
videoUrl: 'https://cdn.mintel.me/video/etib-hero.mp4',
|
||||
linkText: loc === 'de' ? 'Unsere Leistungen' : 'Our Services',
|
||||
linkHref: loc === 'de' ? '/kompetenzen' : '/en/competencies',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeSubCompanyTiles',
|
||||
badge: loc === 'de' ? 'Unsere Unternehmen' : 'Our Companies',
|
||||
title: loc === 'de' ? 'Die E-TIB Gruppe' : 'The E-TIB Group',
|
||||
companies: [
|
||||
{
|
||||
title: 'E-TIB GmbH',
|
||||
description: loc === 'de' ? 'Ausführung elektrischer Infrastrukturprojekte' : 'Execution of electrical infrastructure projects',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Bohrtechnik GmbH',
|
||||
description: loc === 'de' ? 'Präzise Horizontalbohrungen in allen Bodenklassen' : 'Precise horizontal drilling in all soil classes',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Verwaltung GmbH',
|
||||
description: loc === 'de' ? 'Zentrale Dienste, Einkauf, Finanzen' : 'Central services, purchasing, finance',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Ingenieurgesellschaft mbH',
|
||||
description: loc === 'de' ? 'Planung, Projektierung, Dokumentation' : 'Planning, project management, documentation',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeCompetenceBentoGrid',
|
||||
badge: loc === 'de' ? 'Leistungsspektrum' : 'Range of Services',
|
||||
title: loc === 'de' ? 'Umfassende Lösungen für komplexe Netzwerke' : 'Comprehensive solutions for complex networks',
|
||||
ctaLabel: loc === 'de' ? 'Alle Kompetenzen ansehen' : 'View all competencies',
|
||||
ctaHref: loc === 'de' ? '/kompetenzen' : '/en/competencies',
|
||||
items: [
|
||||
{
|
||||
title: loc === 'de' ? 'Kabelleitungstiefbau MS/NS' : 'Cable Trenching MV/LV',
|
||||
description: loc === 'de' ? 'Kabel- und Rohrgräben, Verlegung bis 110 kV, Montage bis 30 kV.' : 'Cable and pipe trenches, laying up to 110 kV, assembly up to 30 kV.',
|
||||
tag: 'ENERGIE',
|
||||
size: 'large',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Breitband & Glasfaser' : 'Broadband & Fiber Optics',
|
||||
description: loc === 'de' ? 'Leerrohrtrassen, Einziehen und Einblasen von LWL-Kabeln.' : 'Empty conduit routes, pulling in and blowing in fiber optic cables.',
|
||||
tag: 'TELEKOMMUNIKATION',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Grabenlose Verlegung' : 'Trenchless Laying',
|
||||
description: loc === 'de' ? 'Horizontalspülbohrungen (bis 250m) und Erdraketen (bis 15m).' : 'Horizontal directional drilling (up to 250m) and soil rockets (up to 15m).',
|
||||
tag: 'BOHRTECHNIK',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Planung & Beratung' : 'Planning & Consulting',
|
||||
description: loc === 'de' ? 'Struktur-, Genehmigungs- und Ausführungsplanung.' : 'Structural, approval and execution planning.',
|
||||
tag: 'ENGINEERING',
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Vermessung & Doku' : 'Surveying & Docu',
|
||||
description: loc === 'de' ? 'Absteckung, Einmessung und 360° Erfassung.' : 'Staking out, measurement and 360° recording.',
|
||||
tag: 'DOKUMENTATION',
|
||||
size: 'accent',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeReferencesSlider',
|
||||
badge: loc === 'de' ? 'Referenzen' : 'References',
|
||||
title: loc === 'de' ? 'Erfolgreich realisierte Projekte' : 'Successfully completed projects',
|
||||
description: loc === 'de' ? 'Ein Auszug unserer aktuellen Bauvorhaben aus den Bereichen Kabeltiefbau, Bohrtechnik und Montage.' : 'An excerpt of our current construction projects in the areas of cable trenching, drilling technology and assembly.',
|
||||
ctaLabel: loc === 'de' ? 'Alle Projekte ansehen' : 'View all projects',
|
||||
ctaHref: loc === 'de' ? '/referenzen' : '/en/references',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
payload.logger.info(`✨ Creating brand new Home page for [${loc}]...`);
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
locale: loc as any,
|
||||
data: {
|
||||
title: loc === 'de' ? 'Startseite' : 'Home',
|
||||
slug: 'home',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeHero',
|
||||
badge: loc === 'de' ? 'Zukunftssichere Infrastruktur' : 'Future-Proof Infrastructure',
|
||||
title: loc === 'de' ? 'Energie & Kommunikation vernetzen' : 'Networking Energy & Communication',
|
||||
description: loc === 'de'
|
||||
? 'Wir realisieren komplexe Versorgungsleitungen für eine moderne Gesellschaft. Verlässlich, innovativ und mit höchster Präzision im Kabelbau und der Bohrtechnik.'
|
||||
: 'We realize complex supply lines for a modern society. Reliable, innovative and with the highest precision in cable construction and drilling technology.',
|
||||
videoUrl: 'https://cdn.mintel.me/video/etib-hero.mp4',
|
||||
linkText: loc === 'de' ? 'Unsere Leistungen' : 'Our Services',
|
||||
linkHref: loc === 'de' ? '/kompetenzen' : '/en/competencies',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeSubCompanyTiles',
|
||||
badge: loc === 'de' ? 'Unsere Unternehmen' : 'Our Companies',
|
||||
title: loc === 'de' ? 'Die E-TIB Gruppe' : 'The E-TIB Group',
|
||||
companies: [
|
||||
{
|
||||
title: 'E-TIB GmbH',
|
||||
description: loc === 'de' ? 'Ausführung elektrischer Infrastrukturprojekte' : 'Execution of electrical infrastructure projects',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Bohrtechnik GmbH',
|
||||
description: loc === 'de' ? 'Präzise Horizontalbohrungen in allen Bodenklassen' : 'Precise horizontal drilling in all soil classes',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Verwaltung GmbH',
|
||||
description: loc === 'de' ? 'Zentrale Dienste, Einkauf, Finanzen' : 'Central services, purchasing, finance',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
},
|
||||
{
|
||||
title: 'E-TIB Ingenieurgesellschaft mbH',
|
||||
description: loc === 'de' ? 'Planung, Projektierung, Dokumentation' : 'Planning, project management, documentation',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeCompetenceBentoGrid',
|
||||
badge: loc === 'de' ? 'Leistungsspektrum' : 'Range of Services',
|
||||
title: loc === 'de' ? 'Umfassende Lösungen für komplexe Netzwerke' : 'Comprehensive solutions for complex networks',
|
||||
ctaLabel: loc === 'de' ? 'Alle Kompetenzen ansehen' : 'View all competencies',
|
||||
ctaHref: loc === 'de' ? '/kompetenzen' : '/en/competencies',
|
||||
items: [
|
||||
{
|
||||
title: loc === 'de' ? 'Kabelleitungstiefbau MS/NS' : 'Cable Trenching MV/LV',
|
||||
description: loc === 'de' ? 'Kabel- und Rohrgräben, Verlegung bis 110 kV, Montage bis 30 kV.' : 'Cable and pipe trenches, laying up to 110 kV, assembly up to 30 kV.',
|
||||
tag: 'ENERGIE',
|
||||
size: 'large',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Breitband & Glasfaser' : 'Broadband & Fiber Optics',
|
||||
description: loc === 'de' ? 'Leerrohrtrassen, Einziehen und Einblasen von LWL-Kabeln.' : 'Empty conduit routes, pulling in and blowing in fiber optic cables.',
|
||||
tag: 'TELEKOMMUNIKATION',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Grabenlose Verlegung' : 'Trenchless Laying',
|
||||
description: loc === 'de' ? 'Horizontalspülbohrungen (bis 250m) und Erdraketen (bis 15m).' : 'Horizontal directional drilling (up to 250m) and soil rockets (up to 15m).',
|
||||
tag: 'BOHRTECHNIK',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Planung & Beratung' : 'Planning & Consulting',
|
||||
description: loc === 'de' ? 'Struktur-, Genehmigungs- und Ausführungsplanung.' : 'Structural, approval and execution planning.',
|
||||
tag: 'ENGINEERING',
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
title: loc === 'de' ? 'Vermessung & Doku' : 'Surveying & Docu',
|
||||
description: loc === 'de' ? 'Absteckung, Einmessung und 360° Erfassung.' : 'Staking out, measurement and 360° recording.',
|
||||
tag: 'DOKUMENTATION',
|
||||
size: 'accent',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeReferencesSlider',
|
||||
badge: loc === 'de' ? 'Referenzen' : 'References',
|
||||
title: loc === 'de' ? 'Erfolgreich realisierte Projekte' : 'Successfully completed projects',
|
||||
description: loc === 'de' ? 'Ein Auszug unserer aktuellen Bauvorhaben aus den Bereichen Kabeltiefbau, Bohrtechnik und Montage.' : 'An excerpt of our current construction projects in the areas of cable trenching, drilling technology and assembly.',
|
||||
ctaLabel: loc === 'de' ? 'Alle Projekte ansehen' : 'View all projects',
|
||||
ctaHref: loc === 'de' ? '/referenzen' : '/en/references',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
payload.logger.info(`✅ [${loc}] Home page seeded.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Seed "Über uns"
|
||||
const { totalDocs: totalUeberUns } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'ueber-uns' } },
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalUeberUns === 0) {
|
||||
payload.logger.info('📄 Seeding Über uns page...');
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Über uns',
|
||||
slug: 'ueber-uns',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'heroSection',
|
||||
title: 'Über uns',
|
||||
description: 'Seit unserer Gründung im Jahr 2015 wachsen wir stetig und entwickeln unsere Spezialkompetenzen weiter. Heute sind wir als E-TIB Gruppe ein ganzheitlicher Anbieter für Infrastrukturprojekte in ganz Deutschland.',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'companyTimeline',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Über uns page seeded.');
|
||||
}
|
||||
|
||||
// Seed "Kompetenzen"
|
||||
const { totalDocs: totalKompetenzen } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'kompetenzen' } },
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalKompetenzen === 0) {
|
||||
payload.logger.info('📄 Seeding Kompetenzen page...');
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Unsere Kompetenzen',
|
||||
slug: 'kompetenzen',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'heroSection',
|
||||
title: 'Unsere Kompetenzen',
|
||||
description: 'Das Leistungsspektrum der E-TIB Gruppe umfasst alle Aspekte des modernen Infrastrukturbaus. Wir bündeln Expertenwissen und schweres Gerät, um selbst die anspruchsvollsten Projekte termingerecht zu realisieren.',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'homeCompetenceBentoGrid',
|
||||
badge: 'Leistungsspektrum',
|
||||
title: 'Umfassende Lösungen für komplexe Netzwerke',
|
||||
items: [
|
||||
{
|
||||
title: 'Kabelleitungstiefbau MS/NS',
|
||||
description: 'Kabel- und Rohrgräben, Verlegung bis 110 kV, Montage bis 30 kV, Trafostationen und Strassenbeleuchtung.',
|
||||
tag: 'ENERGIE',
|
||||
size: 'large',
|
||||
},
|
||||
{
|
||||
title: 'Breitband & Glasfaser',
|
||||
description: 'Leerrohrtrassen, Einziehen und Einblasen von LWL-Kabeln, Stellung von Multifunktionsgehäusen.',
|
||||
tag: 'TELEKOMMUNIKATION',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: 'Grabenlose Verlegung',
|
||||
description: 'Horizontalspülbohrungen (bis 250m) und Erdraketen (bis 15m).',
|
||||
tag: 'BOHRTECHNIK',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
title: 'Planung & Beratung',
|
||||
description: 'Struktur-, Genehmigungs- und Ausführungsplanung. Vergabe und Bauüberwachung.',
|
||||
tag: 'ENGINEERING',
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
title: 'Vermessung & Doku',
|
||||
description: 'Absteckung, Einmessung und 360° Foto-/Video-Erfassung zur Projektabrechnung.',
|
||||
tag: 'DOKUMENTATION',
|
||||
size: 'accent',
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'supportCTA',
|
||||
title: 'Haben Sie ein konkretes Projekt?',
|
||||
description: 'Unsere Experten beraten Sie gerne bei der Planung und Umsetzung Ihrer Infrastrukturvorhaben.',
|
||||
buttonLabel: 'Kontakt aufnehmen',
|
||||
buttonHref: '/kontakt',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Kompetenzen page seeded.');
|
||||
}
|
||||
|
||||
// Seed "Karriere"
|
||||
const { totalDocs: totalKarriere } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'karriere' } },
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalKarriere === 0) {
|
||||
payload.logger.info('📄 Seeding Karriere page...');
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Karriere & Messen',
|
||||
slug: 'karriere',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'heroSection',
|
||||
title: 'Karriere & Messen',
|
||||
description: 'Werden Sie Teil eines dynamischen Teams. Entdecken Sie offene Positionen und treffen Sie uns auf kommenden Messen.',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'jobListing',
|
||||
title: 'Aktuelle Stellenangebote',
|
||||
showFairs: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Karriere page seeded.');
|
||||
}
|
||||
|
||||
// Seed "Kontakt"
|
||||
const { totalDocs: totalKontakt } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: { slug: { equals: 'kontakt' } },
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (totalKontakt === 0) {
|
||||
payload.logger.info('📄 Seeding Kontakt page...');
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Kontaktieren Sie uns',
|
||||
slug: 'kontakt',
|
||||
layout: 'fullBleed',
|
||||
_status: 'published',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'contactSection',
|
||||
showForm: true,
|
||||
showMap: true,
|
||||
showHours: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'block',
|
||||
format: '',
|
||||
version: 2,
|
||||
fields: {
|
||||
blockType: 'teamGrid',
|
||||
title: 'Ihre Ansprechpartner',
|
||||
filterBranch: 'all',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
payload.logger.info('✅ Kontakt page seeded.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
/**
|
||||
* Converts a Markdown+JSX string into a Lexical AST node array.
|
||||
* Specifically adapted for e-tib.com custom Component Blocks.
|
||||
*/
|
||||
|
||||
function propValue(chunk: string, prop: string): string {
|
||||
// Match prop="value" or prop='value' or prop={value}
|
||||
// and also multiline props like prop={\n [\n {...}\n ]\n}
|
||||
// For arrays or complex objects passed as props, basic regex might fail,
|
||||
// but the Markdown in e-tib usually uses simpler props or children.
|
||||
const match =
|
||||
chunk.match(new RegExp(`${prop}=["']([^"']+)["']`)) ||
|
||||
chunk.match(new RegExp(`${prop}=\\{([^}]+)\\}`));
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
function extractItemsProp(chunk: string, startTag: string): any[] {
|
||||
// Match items={ [ ... ] } robustly without stopping at inner object braces
|
||||
const itemsMatch = chunk.match(/items=\{\s*(\[[\s\S]*?\])\s*\}/);
|
||||
if (itemsMatch) {
|
||||
try {
|
||||
const arrayString = itemsMatch[1].trim();
|
||||
// Since e-tib Markdown passes pure JS object arrays like `items={[{title: 'A', content: 'B'}]}`,
|
||||
// parsing it via Regex to JSON is extremely brittle due to unquoted keys and trailing commas.
|
||||
// Using `new Function` safely evaluates the array AST directly in this Node script environment.
|
||||
const fn = new Function(`return ${arrayString};`);
|
||||
return fn();
|
||||
} catch (_e: any) {
|
||||
console.warn(`Could not parse items array for block ${startTag}:`, _e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function blockNode(blockType: string, fields: Record<string, any>) {
|
||||
return { type: 'block', format: '', version: 2, fields: { blockType, ...fields } };
|
||||
}
|
||||
|
||||
function ensureChildren(parsedNodes: any[]): any[] {
|
||||
// Lexical root nodes require at least one child node, or validation fails
|
||||
if (parsedNodes.length === 0) {
|
||||
return [
|
||||
{
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [{ mode: 'normal', type: 'text', text: ' ', version: 1 }],
|
||||
},
|
||||
];
|
||||
}
|
||||
return parsedNodes;
|
||||
}
|
||||
|
||||
function parseInlineMarkdown(text: string): any[] {
|
||||
// Simple regex-based inline parser for bold and italic
|
||||
// Matches **bold**, __bold__, *italic*, _italic_
|
||||
const nodes: any[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
const createTextNode = (content: string, format = 0) => ({
|
||||
detail: 0,
|
||||
format,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: content,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
});
|
||||
|
||||
const rawMatch = text.matchAll(
|
||||
/(\*\*(.*?)\*\*|__(.*?)__|(?<!\*)\*(?!\*)(.*?)\*|(?<!_)_(?!_)(.*?)_)/g,
|
||||
);
|
||||
|
||||
for (const m of rawMatch) {
|
||||
const offset = m.index!;
|
||||
// Leading plain text
|
||||
if (offset > lastIndex) {
|
||||
nodes.push(createTextNode(text.slice(lastIndex, offset)));
|
||||
}
|
||||
|
||||
const boldContent = m[2] || m[3];
|
||||
const italicContent = m[4] || m[5];
|
||||
|
||||
if (boldContent) {
|
||||
nodes.push(createTextNode(boldContent, 1)); // 1 = Bold
|
||||
} else if (italicContent) {
|
||||
nodes.push(createTextNode(italicContent, 2)); // 2 = Italic
|
||||
}
|
||||
|
||||
lastIndex = offset + m[0].length;
|
||||
}
|
||||
|
||||
// Trailing plain text
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
|
||||
return nodes.length > 0 ? nodes : [createTextNode(text)];
|
||||
}
|
||||
|
||||
export function parseMarkdownToLexical(markdown: string): any[] {
|
||||
const paragraphNode = (text: string) => ({
|
||||
type: 'paragraph',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: parseInlineMarkdown(text),
|
||||
});
|
||||
|
||||
const nodes: any[] = [];
|
||||
let content = markdown;
|
||||
|
||||
// Strip frontmatter
|
||||
const fm = content.match(/^---\s*\n[\s\S]*?\n---/);
|
||||
if (fm) content = content.replace(fm[0], '').trim();
|
||||
|
||||
// 1. EXTRACT MULTILINE WRAPPERS BEFORE CHUNKING
|
||||
const extractBlocks = [
|
||||
{
|
||||
tag: 'HighlightBox',
|
||||
regex: /<HighlightBox([^>]*)>([\s\S]*?)<\/HighlightBox>/g,
|
||||
build: (props: string, inner: string) =>
|
||||
blockNode('highlightBox', {
|
||||
title: propValue(`<Tag ${props}>`, 'title'),
|
||||
color: propValue(`<Tag ${props}>`, 'color') || 'primary',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: ensureChildren(parseMarkdownToLexical(inner.trim())),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
tag: 'ChatBubble',
|
||||
regex: /<ChatBubble([^>]*)>([\s\S]*?)<\/ChatBubble>/g,
|
||||
build: (props: string, inner: string) =>
|
||||
blockNode('chatBubble', {
|
||||
author: propValue(`<Tag ${props}>`, 'author') || 'E-TIB Team',
|
||||
avatar: propValue(`<Tag ${props}>`, 'avatar'),
|
||||
role: propValue(`<Tag ${props}>`, 'role') || 'Assistant',
|
||||
align: propValue(`<Tag ${props}>`, 'align') || 'left',
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: ensureChildren(parseMarkdownToLexical(inner.trim())),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
tag: 'Callout',
|
||||
regex: /<Callout([^>]*)>([\s\S]*?)<\/Callout>/g,
|
||||
build: (props: string, inner: string) =>
|
||||
blockNode('callout', {
|
||||
type: propValue(`<Tag ${props}>`, 'type') || 'info',
|
||||
title: propValue(`<Tag ${props}>`, 'title'),
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: ensureChildren(parseMarkdownToLexical(inner.trim())),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
tag: 'ProductTabs',
|
||||
regex: /<ProductTabs([^>]*)>([\s\S]*?)<\/ProductTabs>/g,
|
||||
build: (props: string, inner: string) => {
|
||||
const fullTag = `<ProductTabs ${props}>`;
|
||||
const dataMatch = fullTag.match(/data=\{({[\s\S]*?})\}\s*\/>/);
|
||||
let technicalItems = [];
|
||||
let voltageTables = [];
|
||||
|
||||
if (dataMatch) {
|
||||
try {
|
||||
const parsedData = JSON.parse(dataMatch[1]);
|
||||
technicalItems = parsedData.technicalItems || [];
|
||||
voltageTables = parsedData.voltageTables || [];
|
||||
|
||||
voltageTables.forEach((vt: any) => {
|
||||
vt.rows?.forEach((row: any) => {
|
||||
if (row.cells) {
|
||||
row.cells = row.cells.map((c: any) =>
|
||||
typeof c !== 'object' ? { value: String(c) } : c,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse ProductTabs JSON data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return blockNode('productTabs', {
|
||||
technicalItems,
|
||||
voltageTables,
|
||||
content: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: ensureChildren(parseMarkdownToLexical(inner.trim())),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function cleanMarkdownContent(text: string): string {
|
||||
return text
|
||||
.replace(/<section[^>]*>/g, '')
|
||||
.replace(/<\/section>/g, '')
|
||||
.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/g, '### $1\n\n')
|
||||
.replace(/<p[^>]*>([\s\S]*?)<\/p>/g, '$1\n\n')
|
||||
.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/g, '**$1**')
|
||||
.replace(/<em[^>]*>([\s\S]*?)<\/em>/g, '_$1_')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/^(#{1,6}\s+.*)$/gm, '\n\n$1\n\n') // MAKE HEADINGS THEIR OWN CHUNK
|
||||
.trim();
|
||||
}
|
||||
|
||||
content = cleanMarkdownContent(content);
|
||||
|
||||
const placeholders = new Map<string, any>();
|
||||
let placeholderIdx = 0;
|
||||
|
||||
for (const block of extractBlocks) {
|
||||
content = content.replace(block.regex, (match, propsMatch, innerMatch) => {
|
||||
const id = `__BLOCK_PLACEHOLDER_${placeholderIdx++}__`;
|
||||
placeholders.set(id, block.build(propsMatch, innerMatch));
|
||||
return `\n\n${id}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. CHUNK THE REST
|
||||
const rawChunks = content.split(/\n\s*\n/);
|
||||
|
||||
for (let chunk of rawChunks) {
|
||||
chunk = chunk.trim();
|
||||
if (!chunk) continue;
|
||||
|
||||
if (chunk.startsWith('__BLOCK_PLACEHOLDER_')) {
|
||||
nodes.push(placeholders.get(chunk));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<StickyNarrative')) {
|
||||
nodes.push(
|
||||
blockNode('stickyNarrative', {
|
||||
title: propValue(chunk, 'title'),
|
||||
items: extractItemsProp(chunk, 'StickyNarrative'),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<ComparisonGrid')) {
|
||||
nodes.push(
|
||||
blockNode('comparisonGrid', {
|
||||
title: propValue(chunk, 'title'),
|
||||
leftLabel: propValue(chunk, 'leftLabel'),
|
||||
rightLabel: propValue(chunk, 'rightLabel'),
|
||||
items: extractItemsProp(chunk, 'ComparisonGrid'),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<VisualLinkPreview')) {
|
||||
nodes.push(
|
||||
blockNode('visualLinkPreview', {
|
||||
url: propValue(chunk, 'url'),
|
||||
title: propValue(chunk, 'title'),
|
||||
summary: propValue(chunk, 'summary'),
|
||||
image: propValue(chunk, 'image'),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<TechnicalGrid')) {
|
||||
nodes.push(
|
||||
blockNode('technicalGrid', {
|
||||
title: propValue(chunk, 'title'),
|
||||
items: extractItemsProp(chunk, 'TechnicalGrid'),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<AnimatedImage')) {
|
||||
const widthMatch = chunk.match(/width=\{?(\d+)\}?/);
|
||||
const heightMatch = chunk.match(/height=\{?(\d+)\}?/);
|
||||
nodes.push(
|
||||
blockNode('animatedImage', {
|
||||
src: propValue(chunk, 'src'),
|
||||
alt: propValue(chunk, 'alt'),
|
||||
width: widthMatch ? parseInt(widthMatch[1], 10) : undefined,
|
||||
height: heightMatch ? parseInt(heightMatch[1], 10) : undefined,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.includes('<PowerCTA')) {
|
||||
nodes.push(
|
||||
blockNode('powerCTA', {
|
||||
locale: propValue(chunk, 'locale') || 'de',
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip horizontal rules (---)
|
||||
if (/^-{3,}$/.test(chunk)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const headingMatch = chunk.match(/^(#{1,6})\s+(.*)/);
|
||||
if (headingMatch) {
|
||||
const level = Math.min(headingMatch[1].length + 1, 6);
|
||||
nodes.push({
|
||||
type: 'heading',
|
||||
tag: `h${level}`,
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: parseInlineMarkdown(headingMatch[2]),
|
||||
});
|
||||
// If there's more text after the heading line in this chunk, emit as paragraph(s)
|
||||
const rest = chunk.slice(chunk.indexOf('\n') + 1).trim();
|
||||
if (rest && chunk.includes('\n')) {
|
||||
// Split remaining lines by single newlines for multi-paragraph support
|
||||
const subParagraphs = rest.split(/\n\s*\n/);
|
||||
for (const sub of subParagraphs) {
|
||||
const trimmed = sub.trim();
|
||||
if (trimmed && !/^-{3,}$/.test(trimmed)) {
|
||||
nodes.push(paragraphNode(trimmed));
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const imageMatch = chunk.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
||||
if (imageMatch) {
|
||||
nodes.push(paragraphNode(chunk));
|
||||
continue;
|
||||
}
|
||||
|
||||
nodes.push(paragraphNode(chunk));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
Reference in New Issue
Block a user