diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8ecbc33a5..ffde33393 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -82,8 +82,8 @@ jobs:
INFISICAL_TEST_SERVICE_TOKEN: ${{ secrets.INFISICAL_TEST_SERVICE_TOKEN }}
INFISICAL_TEST_SITE: ${{ vars.INFISICAL_SITE }}
- - name: Linux e2e smoke (pnpm)
- run: pnpm nx affected -t e2e nx-payload-e2e -c skip-docker
+ - name: Linux nx-payload e2e smoke (pnpm)
+ run: pnpm nx affected -t e2e --exclude '*,!tag:scope:nx-payload-e2e' -c quick
env:
CDWR_E2E_PACKAGE_MANAGER: pnpm
CDWR_E2E_VERDACCIO_HOST: localhost
diff --git a/apps/cms/src/app/(site)/[...slug]/page-preview.client.tsx b/apps/cms/src/app/(site)/[...slug]/page-preview.client.tsx
new file mode 100644
index 000000000..62186f0a7
--- /dev/null
+++ b/apps/cms/src/app/(site)/[...slug]/page-preview.client.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { RenderPage } from '@codeware/shared/ui/cms-renderer';
+import type { Page } from '@codeware/shared/util/payload-types';
+import type { BlocksData } from '@codeware/shared/util/payload-utils';
+
+import { LivePreview } from '../../../components/LivePreview.client';
+
+type Props = {
+ page: Page;
+ blocksData?: BlocksData;
+};
+
+export function PagePreview({ page, blocksData }: Props) {
+ return (
+
+ {(data) => }
+
+ );
+}
diff --git a/apps/cms/src/app/(site)/[...slug]/page.tsx b/apps/cms/src/app/(site)/[...slug]/page.tsx
index f403f4f8f..7ea43acda 100644
--- a/apps/cms/src/app/(site)/[...slug]/page.tsx
+++ b/apps/cms/src/app/(site)/[...slug]/page.tsx
@@ -1,10 +1,11 @@
import { notFound } from 'next/navigation';
import { getPageData } from '@codeware/app-cms/data-access';
-import { RenderPage } from '@codeware/shared/ui/cms-renderer';
import { payloadRuntime } from '../../../security/payload-runtime';
+import { PagePreview } from './page-preview.client';
+
interface Props {
params: Promise<{
slug: string[];
@@ -22,5 +23,5 @@ export default async function Page({ params }: Props) {
notFound();
}
- return ;
+ return ;
}
diff --git a/apps/cms/src/app/(site)/landing-page-preview.client.tsx b/apps/cms/src/app/(site)/landing-page-preview.client.tsx
new file mode 100644
index 000000000..881900f3c
--- /dev/null
+++ b/apps/cms/src/app/(site)/landing-page-preview.client.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { RenderLandingPage } from '@codeware/shared/ui/cms-renderer';
+import type { Page } from '@codeware/shared/util/payload-types';
+
+import { LivePreview } from '../../components/LivePreview.client';
+
+type Props = {
+ landingPage: Page;
+};
+
+export function LandingPagePreview({ landingPage }: Props) {
+ return (
+
+ {(data) => }
+
+ );
+}
diff --git a/apps/cms/src/app/(site)/page.tsx b/apps/cms/src/app/(site)/page.tsx
index 0270f3be6..2d8eae82a 100644
--- a/apps/cms/src/app/(site)/page.tsx
+++ b/apps/cms/src/app/(site)/page.tsx
@@ -1,10 +1,11 @@
import { notFound } from 'next/navigation';
import { getPage } from '@codeware/app-cms/data-access';
-import { RenderLandingPage } from '@codeware/shared/ui/cms-renderer';
import { payloadRuntime } from '../../security/payload-runtime';
+import { LandingPagePreview } from './landing-page-preview.client';
+
// TODO: metadata
export default async function SiteIndexPage() {
@@ -19,5 +20,5 @@ export default async function SiteIndexPage() {
notFound();
}
- return ;
+ return ;
}
diff --git a/apps/cms/src/app/(site)/posts/[...slug]/page.tsx b/apps/cms/src/app/(site)/posts/[...slug]/page.tsx
index 6678f5960..dc92e7efb 100644
--- a/apps/cms/src/app/(site)/posts/[...slug]/page.tsx
+++ b/apps/cms/src/app/(site)/posts/[...slug]/page.tsx
@@ -1,10 +1,11 @@
import { notFound } from 'next/navigation';
import { getPost } from '@codeware/app-cms/data-access';
-import { RenderPost } from '@codeware/shared/ui/cms-renderer';
import { payloadRuntime } from '../../../../security/payload-runtime';
+import { PostPreview } from './post-preview.client';
+
interface Props {
params: Promise<{
slug: string[];
@@ -16,12 +17,11 @@ export default async function Post({ params }: Props) {
const slugString = slug.join('/');
const runtime = await payloadRuntime();
-
const post = await getPost(runtime, slugString);
if (!post) {
notFound();
}
- return ;
+ return ;
}
diff --git a/apps/cms/src/app/(site)/posts/[...slug]/post-preview.client.tsx b/apps/cms/src/app/(site)/posts/[...slug]/post-preview.client.tsx
new file mode 100644
index 000000000..efb9b96c9
--- /dev/null
+++ b/apps/cms/src/app/(site)/posts/[...slug]/post-preview.client.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { RenderPost } from '@codeware/shared/ui/cms-renderer';
+import type { Post } from '@codeware/shared/util/payload-types';
+
+import { LivePreview } from '../../../../components/LivePreview.client';
+
+type Props = {
+ post: Post;
+};
+
+export function PostPreview({ post }: Props) {
+ return (
+
+ {(data) => }
+
+ );
+}
diff --git a/apps/cms/src/components/LivePreview.client.tsx b/apps/cms/src/components/LivePreview.client.tsx
new file mode 100644
index 000000000..a5413461b
--- /dev/null
+++ b/apps/cms/src/components/LivePreview.client.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { useLivePreview } from '@payloadcms/live-preview-react';
+
+import { usePayload } from '@codeware/shared/ui/cms-renderer';
+
+interface LivePreviewProps> {
+ initialData: T;
+ depth?: number;
+ children: (data: T) => React.ReactNode;
+}
+
+/**
+ * Generic client component for Payload live preview.
+ *
+ * Subscribes to live preview updates from the CMS admin panel and passes
+ * the current data (updated as-you-type) to the render function.
+ * Falls back to `initialData` when not in preview mode.
+ *
+ * @example
+ * ```tsx
+ *
+ * {(data) => }
+ *
+ * ```
+ */
+export function LivePreview>({
+ initialData,
+ depth = 2,
+ children
+}: LivePreviewProps) {
+ const { payloadUrl } = usePayload();
+ const { data } = useLivePreview({
+ initialData,
+ serverURL: payloadUrl,
+ depth
+ });
+
+ return <>{children(data)}>;
+}
diff --git a/apps/cms/src/components/RefreshRouteOnSave.tsx b/apps/cms/src/components/RefreshRouteOnSave.tsx
new file mode 100644
index 000000000..ee16c1f43
--- /dev/null
+++ b/apps/cms/src/components/RefreshRouteOnSave.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react';
+import { useRouter } from 'next/navigation';
+import React from 'react';
+
+import { usePayload } from '@codeware/shared/ui/cms-renderer';
+
+/**
+ * Client component for server-side live preview.
+ * Listens for save events from Payload CMS and triggers a route refresh.
+ *
+ * Add this component to your layout or page to enable live preview updates when content is saved in the CMS.
+ */
+export const RefreshRouteOnSave: React.FC = () => {
+ const { payloadUrl } = usePayload();
+ const router = useRouter();
+
+ return (
+ router.refresh()}
+ serverURL={payloadUrl}
+ />
+ );
+};
diff --git a/apps/cms/src/migrations/20260411_000000_enum_schema_cleanup.ts b/apps/cms/src/migrations/20260411_000000_enum_schema_cleanup.ts
index 57556116b..04738c5fe 100644
--- a/apps/cms/src/migrations/20260411_000000_enum_schema_cleanup.ts
+++ b/apps/cms/src/migrations/20260411_000000_enum_schema_cleanup.ts
@@ -1,296 +1,15 @@
import { MigrateDownArgs, MigrateUpArgs, sql } from '@payloadcms/db-postgres';
/**
- * Moves all enum types from the `public` schema into the `payload` schema
- * so that every type referenced by payload tables lives in the correct schema.
- *
- * Background: early migrations created enums without an explicit schema prefix,
- * which caused PostgreSQL to place them in `public` (the default search_path at
- * that time). Later migrations started using `"payload"."enum_*"` qualifiers, so
- * new types ended up in `payload` while old ones stayed in `public`.
- *
- * This migration:
- * 1. Creates each missing enum in `payload` (guarded – safe to run on databases
- * that were already patched manually).
- * 2. Migrates every affected column to the `payload`-qualified type.
- * 3. Drops the now-unused `public` copies.
+ * Originally moved all enum types from the `public` schema into the `payload`
+ * schema. Production was patched successfully so this is now a no-op, making
+ * it safe to run on fresh databases where the enums were always in `payload`.
*/
export async function up({ db }: MigrateUpArgs): Promise {
- await db.execute(sql`
- -- ----------------------------------------------------------------
- -- 1. Ensure all enums exist in the payload schema
- -- (DO/EXCEPTION guards make this idempotent for prod databases
- -- that were already patched manually)
- -- ----------------------------------------------------------------
- DO $$ BEGIN CREATE TYPE "payload"."_locales" AS ENUM('en', 'sv'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_code_language" AS ENUM('ts', 'plaintext', 'tsx', 'js', 'jsx'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_content_column_size" AS ENUM('one-third', 'half', 'two-thirds', 'full'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_forms_confirmation_type" AS ENUM('message', 'redirect'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_forms_redirect_type" AS ENUM('reference', 'custom'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_link_type" AS ENUM('reference', 'custom'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_nav_trigger" AS ENUM('card', 'link'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_navigation_label_source" AS ENUM('document', 'custom'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_social_media_direction" AS ENUM('horizontal', 'vertical'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_social_media_platform" AS ENUM('discord', 'email', 'facebook', 'github', 'instagram', 'linkedin', 'npm', 'phone', 'web', 'x', 'youtube'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_spacing_size" AS ENUM('tight', 'regular', 'loose'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_tenant_user_role" AS ENUM('user', 'admin'); EXCEPTION WHEN duplicate_object THEN null; END $$;
- DO $$ BEGIN CREATE TYPE "payload"."enum_user_role" AS ENUM('admin', 'user', 'system-user'); EXCEPTION WHEN duplicate_object THEN null; END $$;
-
- -- ----------------------------------------------------------------
- -- 2. Migrate _locale columns to payload._locales
- -- ----------------------------------------------------------------
- ALTER TABLE "payload"."categories_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_checkbox_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_country_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_date_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_email_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_message_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_number_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_radio_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_radio_options_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_select_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_select_options_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_text_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_blocks_textarea_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_emails_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."forms_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."media_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."pages_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."posts_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards_locales" ALTER COLUMN "_locale" TYPE "payload"."_locales" USING "_locale"::text::"payload"."_locales";
-
- -- ----------------------------------------------------------------
- -- 3. Migrate other enum columns
- -- For columns with a DEFAULT, drop it first (the stored expression
- -- is typed against the old type and cannot be auto-cast), change
- -- the type, then restore the default.
- -- ----------------------------------------------------------------
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" TYPE "payload"."enum_code_language" USING "language"::text::"payload"."enum_code_language";
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" SET DEFAULT 'ts';
-
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" TYPE "payload"."enum_code_language" USING "language"::text::"payload"."enum_code_language";
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" SET DEFAULT 'ts';
-
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" TYPE "payload"."enum_content_column_size" USING "size"::text::"payload"."enum_content_column_size";
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" SET DEFAULT 'full';
-
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" TYPE "payload"."enum_content_column_size" USING "size"::text::"payload"."enum_content_column_size";
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" SET DEFAULT 'full';
-
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" DROP DEFAULT;
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" TYPE "payload"."enum_forms_confirmation_type" USING "confirmation_type"::text::"payload"."enum_forms_confirmation_type";
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" SET DEFAULT 'message';
-
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" DROP DEFAULT;
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" TYPE "payload"."enum_forms_redirect_type" USING "redirect_type"::text::"payload"."enum_forms_redirect_type";
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" SET DEFAULT 'reference';
-
- ALTER TABLE "payload"."pages_blocks_card_cards" ALTER COLUMN "link_type" TYPE "payload"."enum_link_type" USING "link_type"::text::"payload"."enum_link_type";
-
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" TYPE "payload"."enum_link_type" USING "link_type"::text::"payload"."enum_link_type";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" SET DEFAULT 'reference';
-
- ALTER TABLE "payload"."pages_blocks_card_cards" ALTER COLUMN "link_nav_trigger" TYPE "payload"."enum_nav_trigger" USING "link_nav_trigger"::text::"payload"."enum_nav_trigger";
-
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" TYPE "payload"."enum_nav_trigger" USING "link_nav_trigger"::text::"payload"."enum_nav_trigger";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" SET DEFAULT 'card';
-
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" DROP DEFAULT;
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" TYPE "payload"."enum_navigation_label_source" USING "label_source"::text::"payload"."enum_navigation_label_source";
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" SET DEFAULT 'document';
-
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" TYPE "payload"."enum_social_media_direction" USING "direction"::text::"payload"."enum_social_media_direction";
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" SET DEFAULT 'horizontal';
-
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" TYPE "payload"."enum_social_media_direction" USING "direction"::text::"payload"."enum_social_media_direction";
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" SET DEFAULT 'horizontal';
-
- ALTER TABLE "payload"."pages_blocks_social_media_social" ALTER COLUMN "platform" TYPE "payload"."enum_social_media_platform" USING "platform"::text::"payload"."enum_social_media_platform";
- ALTER TABLE "payload"."reusable_content_blocks_social_media_social" ALTER COLUMN "platform" TYPE "payload"."enum_social_media_platform" USING "platform"::text::"payload"."enum_social_media_platform";
-
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" TYPE "payload"."enum_spacing_size" USING "size"::text::"payload"."enum_spacing_size";
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" SET DEFAULT 'regular';
-
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" TYPE "payload"."enum_spacing_size" USING "size"::text::"payload"."enum_spacing_size";
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" SET DEFAULT 'regular';
-
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" DROP DEFAULT;
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" TYPE "payload"."enum_tenant_user_role" USING "role"::text::"payload"."enum_tenant_user_role";
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" SET DEFAULT 'user';
-
- ALTER TABLE "payload"."users" ALTER COLUMN "role" DROP DEFAULT;
- ALTER TABLE "payload"."users" ALTER COLUMN "role" TYPE "payload"."enum_user_role" USING "role"::text::"payload"."enum_user_role";
- ALTER TABLE "payload"."users" ALTER COLUMN "role" SET DEFAULT 'user';
-
- -- ----------------------------------------------------------------
- -- 4. Drop the now-unused public schema copies
- -- (IF EXISTS guards make this safe even if already removed)
- -- ----------------------------------------------------------------
- DROP TYPE IF EXISTS "public"."_locales";
- DROP TYPE IF EXISTS "public"."enum_code_language";
- DROP TYPE IF EXISTS "public"."enum_content_column_size";
- DROP TYPE IF EXISTS "public"."enum_forms_confirmation_type";
- DROP TYPE IF EXISTS "public"."enum_forms_redirect_type";
- DROP TYPE IF EXISTS "public"."enum_link_type";
- DROP TYPE IF EXISTS "public"."enum_nav_trigger";
- DROP TYPE IF EXISTS "public"."enum_navigation_label_source";
- DROP TYPE IF EXISTS "public"."enum_social_media_direction";
- DROP TYPE IF EXISTS "public"."enum_social_media_platform";
- DROP TYPE IF EXISTS "public"."enum_spacing_size";
- -- enum_tenant_domain_page_type: orphaned public copy. The cod_290 migration
- -- already renamed the payload copy to "n" and the column was migrated away
- -- from this type, so there is no payload equivalent to create or cast to.
- DROP TYPE IF EXISTS "public"."enum_tenant_domain_page_type";
- DROP TYPE IF EXISTS "public"."enum_tenant_user_role";
- DROP TYPE IF EXISTS "public"."enum_user_role";
- `);
+ await db.execute(sql`SELECT 1`);
}
export async function down({ db }: MigrateDownArgs): Promise {
- await db.execute(sql`
- -- ----------------------------------------------------------------
- -- 1. Re-create public schema enums
- -- ----------------------------------------------------------------
- CREATE TYPE "public"."_locales" AS ENUM('en', 'sv');
- CREATE TYPE "public"."enum_code_language" AS ENUM('ts', 'plaintext', 'tsx', 'js', 'jsx');
- CREATE TYPE "public"."enum_content_column_size" AS ENUM('one-third', 'half', 'two-thirds', 'full');
- CREATE TYPE "public"."enum_forms_confirmation_type" AS ENUM('message', 'redirect');
- CREATE TYPE "public"."enum_forms_redirect_type" AS ENUM('reference', 'custom');
- CREATE TYPE "public"."enum_link_type" AS ENUM('reference', 'custom');
- CREATE TYPE "public"."enum_nav_trigger" AS ENUM('card', 'link');
- CREATE TYPE "public"."enum_navigation_label_source" AS ENUM('document', 'custom');
- CREATE TYPE "public"."enum_social_media_direction" AS ENUM('horizontal', 'vertical');
- CREATE TYPE "public"."enum_social_media_platform" AS ENUM('discord', 'email', 'facebook', 'github', 'instagram', 'linkedin', 'npm', 'phone', 'web', 'x', 'youtube');
- CREATE TYPE "public"."enum_spacing_size" AS ENUM('tight', 'regular', 'loose');
- CREATE TYPE "public"."enum_tenant_domain_page_type" AS ENUM('cms', 'client', 'disabled');
- CREATE TYPE "public"."enum_tenant_user_role" AS ENUM('user', 'admin');
- CREATE TYPE "public"."enum_user_role" AS ENUM('admin', 'user', 'system-user');
-
- -- ----------------------------------------------------------------
- -- 2. Revert _locale columns back to public._locales
- -- ----------------------------------------------------------------
- ALTER TABLE "payload"."categories_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_checkbox_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_country_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_date_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_email_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_message_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_number_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_radio_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_radio_options_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_select_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_select_options_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_text_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_blocks_textarea_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_emails_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."forms_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."media_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."pages_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."posts_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards_locales" ALTER COLUMN "_locale" TYPE "public"."_locales" USING "_locale"::text::"public"."_locales";
-
- -- ----------------------------------------------------------------
- -- 3. Revert other enum columns back to public schema types
- -- For columns with a DEFAULT, drop it first (the stored expression
- -- is typed and cannot be auto-cast), change the type, then restore.
- -- ----------------------------------------------------------------
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" TYPE "public"."enum_code_language" USING "language"::text::"public"."enum_code_language";
- ALTER TABLE "payload"."pages_blocks_code" ALTER COLUMN "language" SET DEFAULT 'ts';
-
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" TYPE "public"."enum_code_language" USING "language"::text::"public"."enum_code_language";
- ALTER TABLE "payload"."reusable_content_blocks_code" ALTER COLUMN "language" SET DEFAULT 'ts';
-
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" TYPE "public"."enum_content_column_size" USING "size"::text::"public"."enum_content_column_size";
- ALTER TABLE "payload"."pages_blocks_content_columns" ALTER COLUMN "size" SET DEFAULT 'full';
-
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" TYPE "public"."enum_content_column_size" USING "size"::text::"public"."enum_content_column_size";
- ALTER TABLE "payload"."reusable_content_blocks_content_columns" ALTER COLUMN "size" SET DEFAULT 'full';
-
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" DROP DEFAULT;
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" TYPE "public"."enum_forms_confirmation_type" USING "confirmation_type"::text::"public"."enum_forms_confirmation_type";
- ALTER TABLE "payload"."forms" ALTER COLUMN "confirmation_type" SET DEFAULT 'message';
-
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" DROP DEFAULT;
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" TYPE "public"."enum_forms_redirect_type" USING "redirect_type"::text::"public"."enum_forms_redirect_type";
- ALTER TABLE "payload"."forms" ALTER COLUMN "redirect_type" SET DEFAULT 'reference';
-
- ALTER TABLE "payload"."pages_blocks_card_cards" ALTER COLUMN "link_type" TYPE "public"."enum_link_type" USING "link_type"::text::"public"."enum_link_type";
-
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" TYPE "public"."enum_link_type" USING "link_type"::text::"public"."enum_link_type";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_type" SET DEFAULT 'reference';
-
- ALTER TABLE "payload"."pages_blocks_card_cards" ALTER COLUMN "link_nav_trigger" TYPE "public"."enum_nav_trigger" USING "link_nav_trigger"::text::"public"."enum_nav_trigger";
-
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" TYPE "public"."enum_nav_trigger" USING "link_nav_trigger"::text::"public"."enum_nav_trigger";
- ALTER TABLE "payload"."reusable_content_blocks_card_cards" ALTER COLUMN "link_nav_trigger" SET DEFAULT 'card';
-
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" DROP DEFAULT;
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" TYPE "public"."enum_navigation_label_source" USING "label_source"::text::"public"."enum_navigation_label_source";
- ALTER TABLE "payload"."navigation_items" ALTER COLUMN "label_source" SET DEFAULT 'document';
-
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" TYPE "public"."enum_social_media_direction" USING "direction"::text::"public"."enum_social_media_direction";
- ALTER TABLE "payload"."pages_blocks_social_media" ALTER COLUMN "direction" SET DEFAULT 'horizontal';
-
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" TYPE "public"."enum_social_media_direction" USING "direction"::text::"public"."enum_social_media_direction";
- ALTER TABLE "payload"."reusable_content_blocks_social_media" ALTER COLUMN "direction" SET DEFAULT 'horizontal';
-
- ALTER TABLE "payload"."pages_blocks_social_media_social" ALTER COLUMN "platform" TYPE "public"."enum_social_media_platform" USING "platform"::text::"public"."enum_social_media_platform";
- ALTER TABLE "payload"."reusable_content_blocks_social_media_social" ALTER COLUMN "platform" TYPE "public"."enum_social_media_platform" USING "platform"::text::"public"."enum_social_media_platform";
-
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" TYPE "public"."enum_spacing_size" USING "size"::text::"public"."enum_spacing_size";
- ALTER TABLE "payload"."pages_blocks_spacing" ALTER COLUMN "size" SET DEFAULT 'regular';
-
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" DROP DEFAULT;
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" TYPE "public"."enum_spacing_size" USING "size"::text::"public"."enum_spacing_size";
- ALTER TABLE "payload"."reusable_content_blocks_spacing" ALTER COLUMN "size" SET DEFAULT 'regular';
-
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" DROP DEFAULT;
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" TYPE "public"."enum_tenant_user_role" USING "role"::text::"public"."enum_tenant_user_role";
- ALTER TABLE "payload"."users_tenants" ALTER COLUMN "role" SET DEFAULT 'user';
-
- ALTER TABLE "payload"."users" ALTER COLUMN "role" DROP DEFAULT;
- ALTER TABLE "payload"."users" ALTER COLUMN "role" TYPE "public"."enum_user_role" USING "role"::text::"public"."enum_user_role";
- ALTER TABLE "payload"."users" ALTER COLUMN "role" SET DEFAULT 'user';
-
- -- ----------------------------------------------------------------
- -- 4. Drop the payload schema enum copies that were created in up()
- -- Do NOT drop payload._locales — it was created in the very first
- -- migration (cod-213) and is still referenced by locale tables
- -- introduced in cod-290 whose _locale columns are not reverted here.
- -- Also leave enum_site_settings_general_default_locale and
- -- enum_tenant_supported_locales — those belong to cod-290.
- -- ----------------------------------------------------------------
- DROP TYPE IF EXISTS "payload"."enum_code_language";
- DROP TYPE IF EXISTS "payload"."enum_content_column_size";
- DROP TYPE IF EXISTS "payload"."enum_forms_confirmation_type";
- DROP TYPE IF EXISTS "payload"."enum_forms_redirect_type";
- DROP TYPE IF EXISTS "payload"."enum_link_type";
- DROP TYPE IF EXISTS "payload"."enum_nav_trigger";
- DROP TYPE IF EXISTS "payload"."enum_navigation_label_source";
- DROP TYPE IF EXISTS "payload"."enum_social_media_direction";
- DROP TYPE IF EXISTS "payload"."enum_social_media_platform";
- DROP TYPE IF EXISTS "payload"."enum_spacing_size";
- DROP TYPE IF EXISTS "payload"."enum_tenant_user_role";
- DROP TYPE IF EXISTS "payload"."enum_user_role";
- `);
+ await db.execute(sql`SELECT 1`);
}
diff --git a/apps/cms/src/payload.config.ts b/apps/cms/src/payload.config.ts
index f1bd34fa3..2205a201d 100644
--- a/apps/cms/src/payload.config.ts
+++ b/apps/cms/src/payload.config.ts
@@ -58,6 +58,24 @@ export default buildConfig({
graphics: {
Logo: '@codeware/apps/cms/components/Logo.client'
}
+ },
+ livePreview: {
+ breakpoints: [
+ { label: 'Mobile', name: 'mobile', width: 375, height: 667 },
+ { label: 'Tablet', name: 'tablet', width: 768, height: 1024 },
+ { label: 'Desktop', name: 'desktop', width: 1440, height: 900 }
+ ],
+ collections: ['pages', 'posts'],
+ url: ({ data, collectionConfig, locale }) => {
+ // Live preview in not enabled in host mode
+ if (env.APP_MODE.type === 'host') {
+ return null;
+ }
+ if (collectionConfig?.slug === 'posts') {
+ return `posts/${data.slug}?locale=${locale.code}`;
+ }
+ return `${data.slug}?locale=${locale.code}`;
+ }
}
},
// Declare blocks globally and reference then by slug elsewhere
diff --git a/package.json b/package.json
index c7df9cd4d..4c9ab54c8 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@payloadcms/drizzle": "~3.69.0",
"@payloadcms/email-nodemailer": "~3.69.0",
"@payloadcms/next": "~3.69.0",
+ "@payloadcms/live-preview-react": "~3.69.0",
"@payloadcms/plugin-form-builder": "~3.69.0",
"@payloadcms/plugin-multi-tenant": "~3.69.0",
"@payloadcms/plugin-seo": "~3.69.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e273d401a..e0c47121c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,6 +62,9 @@ importers:
'@payloadcms/email-nodemailer':
specifier: ~3.69.0
version: 3.69.0(payload@3.69.0(graphql@16.12.0)(typescript@5.9.2))
+ '@payloadcms/live-preview-react':
+ specifier: ~3.69.0
+ version: 3.69.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@payloadcms/next':
specifier: ~3.69.0
version: 3.69.0(@types/react@19.0.0)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.1))(payload@3.69.0(graphql@16.12.0)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2)
@@ -3137,7 +3140,6 @@ packages:
'@evilmartians/lefthook@1.13.6':
resolution: {integrity: sha512-79vplrUBWL4Fkt59YkEBdSpqBVhNrY8t5+jEp+wX5QbsmbQLcSULqwS7FmbNRyECa2LWMrUWpe6ENIfNwB4jiw==}
- cpu: [x64, arm64, ia32]
os: [darwin, linux, win32]
hasBin: true
@@ -4960,6 +4962,15 @@ packages:
graphql: ^16.8.1
payload: 3.69.0
+ '@payloadcms/live-preview-react@3.69.0':
+ resolution: {integrity: sha512-Nxbvo5fXEVI7VdGicwCx/GaLNxxdlWzfV89DUzHvp8guzbSXOmtKRsuWwsT7BrsHVdud8z3B2bzt6aceih3qlw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.1 || ^19.1.2 || ^19.2.1
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.1 || ^19.1.2 || ^19.2.1
+
+ '@payloadcms/live-preview@3.69.0':
+ resolution: {integrity: sha512-TTylsSlQ+iXyGS3sOiah/jSJWKdNRLctgE9sTkFvJs1r/IwUPyE5j17kTIkooJc69gZ0JYNeL+juh0lvI7ZQBA==}
+
'@payloadcms/next@3.69.0':
resolution: {integrity: sha512-rkj/wvTDcbOkb8+v4jkZVdoJm2tvPUQL62CWoGnysy/ST+ZUwMr/70veLB7Ztr3uj6HKi6dU+31H+gG94UaMZg==}
engines: {node: ^18.20.2 || >=20.9.0}
@@ -21438,6 +21449,14 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@payloadcms/live-preview-react@3.69.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@payloadcms/live-preview': 3.69.0
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+
+ '@payloadcms/live-preview@3.69.0': {}
+
'@payloadcms/next@3.69.0(@types/react@19.0.0)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.1))(payload@3.69.0(graphql@16.12.0)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2)':
dependencies:
'@dnd-kit/core': 6.0.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)