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)