From f49eeb1a6372e09949c20a468c44d8f7a824899d Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Thu, 3 Jul 2025 17:47:16 -0400 Subject: [PATCH] fix(next): respect collection-level live preview config (#13036) Fixes #13035. We broke collection-level live preview configs in #12860. --- packages/next/src/views/Document/index.tsx | 8 +++- .../ui/src/providers/LivePreview/index.tsx | 15 +++---- test/_community/payload-types.ts | 14 +++++++ test/helpers.ts | 2 +- test/live-preview/collections/Categories.ts | 4 +- .../collections/CollectionLevelConfig.ts | 23 +++++++++++ test/live-preview/config.ts | 15 ++++++- test/live-preview/e2e.spec.ts | 17 ++++++++ test/live-preview/payload-types.ts | 41 +++++++++++++++++++ test/live-preview/shared.ts | 1 + 10 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 test/live-preview/collections/CollectionLevelConfig.ts diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx index c7533b789..894c5f720 100644 --- a/packages/next/src/views/Document/index.tsx +++ b/packages/next/src/views/Document/index.tsx @@ -6,6 +6,7 @@ import type { DocumentViewServerProps, DocumentViewServerPropsOnly, EditViewComponent, + LivePreviewConfig, PayloadComponent, RenderDocumentVersionsProperties, } from 'payload' @@ -91,7 +92,6 @@ export const renderDocument = async ({ payload: { config, config: { - admin: { livePreview: livePreviewConfig }, routes: { admin: adminRoute, api: apiRoute }, serverURL, }, @@ -329,6 +329,12 @@ export const renderDocument = async ({ viewType, } + const livePreviewConfig: LivePreviewConfig = { + ...(config.admin.livePreview || {}), + ...(collectionConfig?.admin?.livePreview || {}), + ...(globalConfig?.admin?.livePreview || {}), + } + const livePreviewURL = typeof livePreviewConfig?.url === 'function' ? await livePreviewConfig.url({ diff --git a/packages/ui/src/providers/LivePreview/index.tsx b/packages/ui/src/providers/LivePreview/index.tsx index 055274144..e2ff1bcd3 100644 --- a/packages/ui/src/providers/LivePreview/index.tsx +++ b/packages/ui/src/providers/LivePreview/index.tsx @@ -226,6 +226,13 @@ export const LivePreviewProvider: React.FC = ({ ) }, [isLivePreviewing, setPreference, collectionSlug, globalSlug]) + const isLivePreviewEnabled = Boolean( + operation !== 'create' && + ((collectionSlug && config?.admin?.livePreview?.collections?.includes(collectionSlug)) || + (globalSlug && config.admin?.livePreview?.globals?.includes(globalSlug)) || + entityConfig?.admin?.livePreview), + ) + return ( = ({ fieldSchemaJSON, iframeHasLoaded, iframeRef, - isLivePreviewEnabled: Boolean( - (operation !== 'create' && - collectionSlug && - config?.admin?.livePreview?.collections?.includes(collectionSlug)) || - (globalSlug && config.admin?.livePreview?.globals?.includes(globalSlug)) || - entityConfig?.admin?.livePreview, - ), + isLivePreviewEnabled, isLivePreviewing, isPopupOpen, listeningForMessages, diff --git a/test/_community/payload-types.ts b/test/_community/payload-types.ts index 4d2af3d49..599c9dec1 100644 --- a/test/_community/payload-types.ts +++ b/test/_community/payload-types.ts @@ -203,6 +203,13 @@ export interface User { hash?: string | null; loginAttempts?: number | null; lockUntil?: string | null; + sessions?: + | { + id: string; + createdAt?: string | null; + expiresAt: string; + }[] + | null; password?: string | null; } /** @@ -341,6 +348,13 @@ export interface UsersSelect { hash?: T; loginAttempts?: T; lockUntil?: T; + sessions?: + | T + | { + id?: T; + createdAt?: T; + expiresAt?: T; + }; } /** * This interface was referenced by `Config`'s JSON-Schema diff --git a/test/helpers.ts b/test/helpers.ts index f5a2f7f6f..0068b15b5 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -248,7 +248,7 @@ export async function saveDocHotkeyAndAssert(page: Page): Promise { export async function saveDocAndAssert( page: Page, - selector: '#access-save' | '#action-publish' | '#action-save-draft' | string = '#action-save', + selector: '#action-publish' | '#action-save' | '#action-save-draft' | string = '#action-save', expectation: 'error' | 'success' = 'success', ): Promise { await wait(500) // TODO: Fix this diff --git a/test/live-preview/collections/Categories.ts b/test/live-preview/collections/Categories.ts index 2bd8df638..f2acb3047 100644 --- a/test/live-preview/collections/Categories.ts +++ b/test/live-preview/collections/Categories.ts @@ -2,7 +2,7 @@ import type { CollectionConfig } from 'payload' import { categoriesSlug } from '../shared.js' -const Categories: CollectionConfig = { +export const Categories: CollectionConfig = { slug: categoriesSlug, admin: { useAsTitle: 'title', @@ -17,5 +17,3 @@ const Categories: CollectionConfig = { }, ], } - -export default Categories diff --git a/test/live-preview/collections/CollectionLevelConfig.ts b/test/live-preview/collections/CollectionLevelConfig.ts new file mode 100644 index 000000000..784a4886c --- /dev/null +++ b/test/live-preview/collections/CollectionLevelConfig.ts @@ -0,0 +1,23 @@ +import type { CollectionConfig } from 'payload' + +import { collectionLevelConfigSlug } from '../shared.js' + +export const CollectionLevelConfig: CollectionConfig = { + slug: collectionLevelConfigSlug, + admin: { + description: "Live Preview is enabled on this collection's own config, not the root config.", + useAsTitle: 'title', + livePreview: { + url: 'http://localhost:3000/live-preview', + }, + }, + access: { + read: () => true, + }, + fields: [ + { + name: 'title', + type: 'text', + }, + ], +} diff --git a/test/live-preview/config.ts b/test/live-preview/config.ts index c67f912b5..6bbf62012 100644 --- a/test/live-preview/config.ts +++ b/test/live-preview/config.ts @@ -3,7 +3,8 @@ import path from 'path' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' -import Categories from './collections/Categories.js' +import { Categories } from './collections/Categories.js' +import { CollectionLevelConfig } from './collections/CollectionLevelConfig.js' import { Media } from './collections/Media.js' import { Pages } from './collections/Pages.js' import { Posts } from './collections/Posts.js' @@ -44,7 +45,17 @@ export default buildConfigWithDefaults({ }, cors: ['http://localhost:3000', 'http://localhost:3001'], csrf: ['http://localhost:3000', 'http://localhost:3001'], - collections: [Users, Pages, Posts, SSR, SSRAutosave, Tenants, Categories, Media], + collections: [ + Users, + Pages, + Posts, + SSR, + SSRAutosave, + Tenants, + Categories, + Media, + CollectionLevelConfig, + ], globals: [Header, Footer], onInit: seed, typescript: { diff --git a/test/live-preview/e2e.spec.ts b/test/live-preview/e2e.spec.ts index 34cd766fd..3bf05af54 100644 --- a/test/live-preview/e2e.spec.ts +++ b/test/live-preview/e2e.spec.ts @@ -27,6 +27,7 @@ import { toggleLivePreview, } from './helpers.js' import { + collectionLevelConfigSlug, desktopBreakpoint, mobileBreakpoint, pagesSlug, @@ -101,6 +102,22 @@ describe('Live Preview', () => { await expect(page.locator('iframe.live-preview-iframe')).toBeHidden() }) + test('collection - does not enable live preview is collections that are not configured', async () => { + const usersURL = new AdminUrlUtil(serverURL, 'users') + await navigateToDoc(page, usersURL) + const toggler = page.locator('#live-preview-toggler') + await expect(toggler).toBeHidden() + }) + + test('collection - respect collection-level live preview config', async () => { + const collURL = new AdminUrlUtil(serverURL, collectionLevelConfigSlug) + await page.goto(collURL.create) + await page.locator('#field-title').fill('Collection Level Config') + await saveDocAndAssert(page) + await toggleLivePreview(page) + await expect(page.locator('iframe.live-preview-iframe')).toBeVisible() + }) + test('saves live preview state to preferences and loads it on next visit', async () => { await deletePreferences({ payload, diff --git a/test/live-preview/payload-types.ts b/test/live-preview/payload-types.ts index 024c28f9c..b11fe0671 100644 --- a/test/live-preview/payload-types.ts +++ b/test/live-preview/payload-types.ts @@ -75,6 +75,7 @@ export interface Config { tenants: Tenant; categories: Category; media: Media; + 'collection-level-config': CollectionLevelConfig; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -89,6 +90,7 @@ export interface Config { tenants: TenantsSelect | TenantsSelect; categories: CategoriesSelect | CategoriesSelect; media: MediaSelect | MediaSelect; + 'collection-level-config': CollectionLevelConfigSelect | CollectionLevelConfigSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; @@ -146,6 +148,13 @@ export interface User { hash?: string | null; loginAttempts?: number | null; lockUntil?: string | null; + sessions?: + | { + id: string; + createdAt?: string | null; + expiresAt: string; + }[] + | null; password?: string | null; } /** @@ -834,6 +843,18 @@ export interface SsrAutosave { createdAt: string; _status?: ('draft' | 'published') | null; } +/** + * Live Preview is enabled on this collection's own config, not the root config. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "collection-level-config". + */ +export interface CollectionLevelConfig { + id: string; + title?: string | null; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -872,6 +893,10 @@ export interface PayloadLockedDocument { | ({ relationTo: 'media'; value: string | Media; + } | null) + | ({ + relationTo: 'collection-level-config'; + value: string | CollectionLevelConfig; } | null); globalSlug?: string | null; user: { @@ -929,6 +954,13 @@ export interface UsersSelect { hash?: T; loginAttempts?: T; lockUntil?: T; + sessions?: + | T + | { + id?: T; + createdAt?: T; + expiresAt?: T; + }; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -1395,6 +1427,15 @@ export interface MediaSelect { focalX?: T; focalY?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "collection-level-config_select". + */ +export interface CollectionLevelConfigSelect { + title?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select". diff --git a/test/live-preview/shared.ts b/test/live-preview/shared.ts index 927ed7d46..fef101723 100644 --- a/test/live-preview/shared.ts +++ b/test/live-preview/shared.ts @@ -5,6 +5,7 @@ export const ssrAutosavePagesSlug = 'ssr-autosave' export const postsSlug = 'posts' export const mediaSlug = 'media' export const categoriesSlug = 'categories' +export const collectionLevelConfigSlug = 'collection-level-config' export const usersSlug = 'users' export const mobileBreakpoint = {