@actuate-media/cms-admin 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/__tests__/lib/editor-preview.test.d.ts +2 -0
  3. package/dist/__tests__/lib/editor-preview.test.d.ts.map +1 -0
  4. package/dist/__tests__/lib/editor-preview.test.js +18 -0
  5. package/dist/__tests__/lib/editor-preview.test.js.map +1 -0
  6. package/dist/__tests__/lib/post-editor-service.test.js +16 -0
  7. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  8. package/dist/__tests__/views/dashboard.test.js +23 -0
  9. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  10. package/dist/__tests__/views/editor-top-bar.render.test.d.ts +2 -0
  11. package/dist/__tests__/views/editor-top-bar.render.test.d.ts.map +1 -0
  12. package/dist/__tests__/views/editor-top-bar.render.test.js +46 -0
  13. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -0
  14. package/dist/__tests__/views/use-site-preview.render.test.js +10 -1
  15. package/dist/__tests__/views/use-site-preview.render.test.js.map +1 -1
  16. package/dist/lib/editor-preview.d.ts +4 -0
  17. package/dist/lib/editor-preview.d.ts.map +1 -0
  18. package/dist/lib/editor-preview.js +10 -0
  19. package/dist/lib/editor-preview.js.map +1 -0
  20. package/dist/lib/post-editor-service.d.ts.map +1 -1
  21. package/dist/lib/post-editor-service.js +4 -1
  22. package/dist/lib/post-editor-service.js.map +1 -1
  23. package/dist/lib/seo-service.d.ts.map +1 -1
  24. package/dist/lib/seo-service.js +11 -3
  25. package/dist/lib/seo-service.js.map +1 -1
  26. package/dist/views/Users.js +1 -1
  27. package/dist/views/Users.js.map +1 -1
  28. package/dist/views/page-editor/EditorCanvas.d.ts +2 -1
  29. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  30. package/dist/views/page-editor/EditorCanvas.js +2 -2
  31. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  32. package/dist/views/page-editor/EditorTopBar.d.ts +5 -1
  33. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  34. package/dist/views/page-editor/EditorTopBar.js +12 -2
  35. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  36. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  37. package/dist/views/page-editor/PageSectionEditor.js +7 -1
  38. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  39. package/dist/views/page-editor/StructureNotice.d.ts +4 -3
  40. package/dist/views/page-editor/StructureNotice.d.ts.map +1 -1
  41. package/dist/views/page-editor/StructureNotice.js +9 -7
  42. package/dist/views/page-editor/StructureNotice.js.map +1 -1
  43. package/dist/views/page-editor/useSitePreview.d.ts +3 -1
  44. package/dist/views/page-editor/useSitePreview.d.ts.map +1 -1
  45. package/dist/views/page-editor/useSitePreview.js +44 -31
  46. package/dist/views/page-editor/useSitePreview.js.map +1 -1
  47. package/dist/views/post-editor/PostEditorCanvas.d.ts +3 -1
  48. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  49. package/dist/views/post-editor/PostEditorCanvas.js +2 -2
  50. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  51. package/dist/views/post-editor/PostSectionEditor.d.ts +8 -0
  52. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  53. package/dist/views/post-editor/PostSectionEditor.js +7 -1
  54. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/__tests__/lib/editor-preview.test.ts +21 -0
  57. package/src/__tests__/lib/post-editor-service.test.ts +17 -0
  58. package/src/__tests__/views/dashboard.test.tsx +26 -0
  59. package/src/__tests__/views/editor-top-bar.render.test.tsx +65 -0
  60. package/src/__tests__/views/use-site-preview.render.test.tsx +12 -0
  61. package/src/lib/editor-preview.ts +12 -0
  62. package/src/lib/post-editor-service.ts +4 -1
  63. package/src/lib/seo-service.ts +14 -3
  64. package/src/views/Users.tsx +1 -1
  65. package/src/views/page-editor/EditorCanvas.tsx +6 -1
  66. package/src/views/page-editor/EditorTopBar.tsx +20 -1
  67. package/src/views/page-editor/PageSectionEditor.tsx +14 -0
  68. package/src/views/page-editor/StructureNotice.tsx +42 -8
  69. package/src/views/page-editor/useSitePreview.ts +47 -31
  70. package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
  71. package/src/views/post-editor/PostSectionEditor.tsx +15 -0
@@ -0,0 +1,12 @@
1
+ import type { AdminEditorConfig } from '@actuate-media/cms-core'
2
+
3
+ /** Resolve whether a section editor should open with Site preview active. */
4
+ export function resolveDefaultSitePreview(
5
+ editor: AdminEditorConfig | undefined,
6
+ kind: 'pages' | 'posts',
7
+ ): boolean {
8
+ const setting = editor?.defaultSitePreview
9
+ if (setting === true) return true
10
+ if (setting == null || setting === false) return false
11
+ return kind === 'pages' ? setting.pages === true : setting.posts === true
12
+ }
@@ -220,10 +220,13 @@ export function parseTags(raw: unknown): string[] {
220
220
 
221
221
  function rowToEditorPost(postType: string, doc: DocumentApiRow): EditorPost {
222
222
  const data = doc.data ?? {}
223
+ const dataTitle = dataStr(data, 'title')
223
224
  return {
224
225
  id: doc.id,
225
226
  postType,
226
- title: doc.title ?? dataStr(data, 'title'),
227
+ // Prefer the JSON field — the denormalized `doc.title` column can lag or
228
+ // reflect slug-derived fallbacks on imported/legacy rows.
229
+ title: dataTitle || (doc.title ?? ''),
227
230
  slug: doc.slug ?? dataStr(data, 'slug'),
228
231
  excerpt: dataStr(data, 'excerpt'),
229
232
  featuredImage: dataStr(data, 'featuredImage'),
@@ -817,9 +817,20 @@ export async function fetchRedirectOverview(): Promise<RedirectOverview> {
817
817
  }
818
818
 
819
819
  export async function fetchRedirectRules(): Promise<RedirectRule[]> {
820
- const res = await cmsApi<{ rules: RedirectRule[] }>('/seo/redirects')
821
- throwIfError(res)
822
- return res.data?.rules ?? []
820
+ const rules: RedirectRule[] = []
821
+ let page = 1
822
+ let totalPages = 1
823
+ do {
824
+ const res = await cmsApi<{
825
+ rules: RedirectRule[]
826
+ totalPages?: number
827
+ }>(`/seo/redirects?page=${page}&pageSize=500`)
828
+ throwIfError(res)
829
+ rules.push(...(res.data?.rules ?? []))
830
+ totalPages = res.data?.totalPages ?? 1
831
+ page += 1
832
+ } while (page <= totalPages)
833
+ return rules
823
834
  }
824
835
 
825
836
  export interface RedirectRulePayload {
@@ -677,7 +677,7 @@ export interface UsersProps {
677
677
  }
678
678
 
679
679
  export function Users({ embedded = false }: UsersProps = {}) {
680
- const members = useApiData<ApiMember[]>('/users')
680
+ const members = useApiData<ApiMember[]>('/users?pageSize=200')
681
681
  const invites = useApiData<ApiInvite[]>('/invites')
682
682
  const stats = useApiData<ApiStats>('/users/stats')
683
683
  const review = useApiData<ApiRecommendation[]>('/access-review')
@@ -15,6 +15,7 @@ interface EditorCanvasProps {
15
15
  onAddSection: () => void
16
16
  /** Section types rendered as placeholders — shows the "Structure view" notice. */
17
17
  structuralTypes?: string[]
18
+ sitePreviewAvailable?: boolean
18
19
  }
19
20
 
20
21
  /**
@@ -30,6 +31,7 @@ export function EditorCanvas({
30
31
  onSelect,
31
32
  onAddSection,
32
33
  structuralTypes = [],
34
+ sitePreviewAvailable = false,
33
35
  }: EditorCanvasProps) {
34
36
  const vp = getViewport(viewport)
35
37
  const containerRef = useRef<HTMLDivElement>(null)
@@ -78,7 +80,10 @@ export function EditorCanvas({
78
80
  <EmptyCanvas onAddSection={onAddSection} />
79
81
  ) : (
80
82
  <>
81
- <StructureNotice structuralTypes={structuralTypes} />
83
+ <StructureNotice
84
+ structuralTypes={structuralTypes}
85
+ sitePreviewAvailable={sitePreviewAvailable}
86
+ />
82
87
  <div
83
88
  className="mx-auto"
84
89
  style={{ width: vp.width * scale, height: surfaceHeight ?? undefined }}
@@ -28,6 +28,10 @@ interface EditorTopBarProps {
28
28
  publishing: boolean
29
29
  canEdit: boolean
30
30
  canPublish: boolean
31
+ /** When false, Publish stays disabled even if the user has publish permission. */
32
+ publishReady?: boolean
33
+ /** Shown on hover/focus when publish is blocked by validation (not role). */
34
+ publishDisabledReason?: string
31
35
  viewport: ViewportId
32
36
  /** Breadcrumb root label + back-button text (e.g. "Pages" or "Posts"). */
33
37
  backLabel?: string
@@ -86,6 +90,8 @@ export function EditorTopBar({
86
90
  publishing,
87
91
  canEdit,
88
92
  canPublish,
93
+ publishReady = true,
94
+ publishDisabledReason,
89
95
  viewport,
90
96
  backLabel = 'Pages',
91
97
  onViewport,
@@ -109,6 +115,17 @@ export function EditorTopBar({
109
115
  ? 'Preview structure (custom sections show placeholders)'
110
116
  : 'Preview page'
111
117
 
118
+ const publishBlocked = !canPublish || !publishReady || publishing || saveState === 'saving'
119
+ const publishTitle = publishing
120
+ ? 'Publishing…'
121
+ : saveState === 'saving'
122
+ ? 'Wait for the current save to finish'
123
+ : canPublish && !publishReady && publishDisabledReason
124
+ ? publishDisabledReason
125
+ : !canPublish
126
+ ? 'You do not have permission to publish'
127
+ : undefined
128
+
112
129
  return (
113
130
  <header className="bg-card border-border flex h-14 shrink-0 items-center justify-between gap-4 border-b px-4">
114
131
  {/* Breadcrumb + status */}
@@ -253,7 +270,9 @@ export function EditorTopBar({
253
270
  <button
254
271
  type="button"
255
272
  onClick={onPublish}
256
- disabled={!canPublish || publishing || saveState === 'saving'}
273
+ disabled={publishBlocked}
274
+ title={publishTitle}
275
+ aria-disabled={publishBlocked}
257
276
  className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
258
277
  >
259
278
  {publishing && <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />}
@@ -43,6 +43,7 @@ import { SectionInspector } from './SectionInspector.js'
43
43
  import { AddSectionModal } from './AddSectionModal.js'
44
44
  import { PageSettingsModal } from './PageSettingsModal.js'
45
45
  import { ValidationSummary } from './ValidationSummary.js'
46
+ import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
46
47
 
47
48
  interface PageSectionEditorProps {
48
49
  /** undefined when creating a new page (`/pages/new`) */
@@ -113,6 +114,13 @@ export function PageSectionEditor({
113
114
  )
114
115
  }, [config])
115
116
 
117
+ const publishCheck = useMemo(
118
+ () => (page ? validatePage(page, true) : { errors: [''], warnings: [] }),
119
+ [page],
120
+ )
121
+ const publishReady = Boolean(page) && publishCheck.errors.length === 0
122
+ const publishDisabledReason = publishCheck.errors[0]
123
+
116
124
  // ─── Load ──────────────────────────────────────────────────────────
117
125
  useEffect(() => {
118
126
  let cancelled = false
@@ -275,8 +283,11 @@ export function PageSectionEditor({
275
283
  dirty,
276
284
  saveState,
277
285
  saveDraft: handleSaveDraft,
286
+ defaultActive: resolveDefaultSitePreview(config?.admin?.editor, 'pages'),
278
287
  })
279
288
 
289
+ const sitePreviewAvailable = Boolean(page?.id && page?.slug)
290
+
280
291
  const requestPublish = useCallback(() => {
281
292
  if (!page) return
282
293
  const result = validatePage(page, true)
@@ -459,6 +470,8 @@ export function PageSectionEditor({
459
470
  publishing={publishing}
460
471
  canEdit={effectiveCanEdit}
461
472
  canPublish={canPublish && effectiveCanEdit}
473
+ publishReady={publishReady}
474
+ publishDisabledReason={publishDisabledReason}
462
475
  viewport={viewport}
463
476
  onViewport={setViewport}
464
477
  onBack={() => guardedNavigate('/pages')}
@@ -550,6 +563,7 @@ export function PageSectionEditor({
550
563
  onSelect={setSelectedId}
551
564
  onAddSection={() => setAddOpen(true)}
552
565
  structuralTypes={structuralTypes}
566
+ sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
553
567
  />
554
568
  )}
555
569
  </div>
@@ -5,12 +5,47 @@ import { Puzzle } from 'lucide-react'
5
5
  /**
6
6
  * Banner shown above the canvas surface when the document contains section
7
7
  * types the canvas can only render as placeholders (custom / unmanaged
8
- * types). Tells authors the canvas is a structural view — the real design
9
- * lives on the public site behind "View on site".
8
+ * types), or when Site preview is available for accurate styling.
10
9
  */
11
- export function StructureNotice({ structuralTypes }: { structuralTypes: string[] }) {
12
- if (structuralTypes.length === 0) return null
13
- const count = structuralTypes.length
10
+ export function StructureNotice({
11
+ structuralTypes,
12
+ sitePreviewAvailable = false,
13
+ }: {
14
+ structuralTypes: string[]
15
+ /** When true, nudge editors toward the Site preview toggle for WYSIWYG. */
16
+ sitePreviewAvailable?: boolean
17
+ }) {
18
+ if (structuralTypes.length === 0 && !sitePreviewAvailable) return null
19
+
20
+ if (structuralTypes.length > 0) {
21
+ const count = structuralTypes.length
22
+ return (
23
+ <div
24
+ role="note"
25
+ className="border-border bg-card text-muted-foreground mx-auto mb-4 flex w-fit max-w-full items-center gap-2 rounded-lg border px-3 py-1.5 text-xs"
26
+ >
27
+ <Puzzle className="h-3.5 w-3.5 shrink-0" aria-hidden />
28
+ <span>
29
+ <span className="text-foreground font-medium">Structure view</span> — {count} custom
30
+ section type{count > 1 ? 's' : ''} shown as placeholders.
31
+ {sitePreviewAvailable ? (
32
+ <>
33
+ {' '}
34
+ Use <span className="text-foreground font-medium">Site preview</span> for accurate
35
+ styling.
36
+ </>
37
+ ) : (
38
+ <>
39
+ {' '}
40
+ Use <span className="text-foreground font-medium">View on site</span> for the real
41
+ design.
42
+ </>
43
+ )}
44
+ </span>
45
+ </div>
46
+ )
47
+ }
48
+
14
49
  return (
15
50
  <div
16
51
  role="note"
@@ -18,9 +53,8 @@ export function StructureNotice({ structuralTypes }: { structuralTypes: string[]
18
53
  >
19
54
  <Puzzle className="h-3.5 w-3.5 shrink-0" aria-hidden />
20
55
  <span>
21
- <span className="text-foreground font-medium">Structure view</span> — {count} custom section{' '}
22
- type{count > 1 ? 's' : ''} shown as placeholders. Use{' '}
23
- <span className="text-foreground font-medium">View on site</span> for the real design.
56
+ <span className="text-foreground font-medium">Structure preview</span> — use{' '}
57
+ <span className="text-foreground font-medium">Site preview</span> for accurate styling.
24
58
  </span>
25
59
  </div>
26
60
  )
@@ -21,6 +21,8 @@ interface UseSitePreviewOptions {
21
21
  saveState: SaveState
22
22
  /** Persist the draft; resolves `false` when the save failed. */
23
23
  saveDraft: () => Promise<boolean>
24
+ /** Open Site preview on load when the document is saved (has id + slug). */
25
+ defaultActive?: boolean
24
26
  }
25
27
 
26
28
  export interface SitePreviewState {
@@ -49,12 +51,55 @@ export function useSitePreview({
49
51
  dirty,
50
52
  saveState,
51
53
  saveDraft,
54
+ defaultActive = false,
52
55
  }: UseSitePreviewOptions): SitePreviewState {
53
56
  const [active, setActive] = useState(false)
54
57
  const [url, setUrl] = useState<string | null>(null)
55
58
  const [reloadKey, setReloadKey] = useState(0)
56
59
  const tokenRef = useRef<string | null>(null)
57
60
  const prevSaveState = useRef(saveState)
61
+ const autoOpenRef = useRef(false)
62
+
63
+ const activatePreview = useCallback(async () => {
64
+ if (!documentId || !slug) {
65
+ toast.error('Save the page before opening the site preview')
66
+ return false
67
+ }
68
+ setActive(true)
69
+ setUrl(null)
70
+ try {
71
+ if (dirty) {
72
+ const ok = await saveDraft()
73
+ if (!ok) {
74
+ setActive(false)
75
+ return false
76
+ }
77
+ }
78
+ const { token } = await createPreviewToken(collection, documentId)
79
+ tokenRef.current = token
80
+ setUrl(
81
+ buildPublicPreviewUrl({
82
+ siteUrl: resolveSiteUrl(siteUrl),
83
+ urlPrefix: urlPrefix ?? '',
84
+ slug,
85
+ token,
86
+ }),
87
+ )
88
+ return true
89
+ } catch (err) {
90
+ toast.error(err instanceof Error ? err.message : 'Failed to open site preview')
91
+ setActive(false)
92
+ return false
93
+ }
94
+ }, [collection, documentId, slug, urlPrefix, siteUrl, dirty, saveDraft])
95
+
96
+ // Opt-in default: open the iframe once a saved document is loaded.
97
+ useEffect(() => {
98
+ if (!defaultActive || autoOpenRef.current) return
99
+ if (!documentId || !slug) return
100
+ autoOpenRef.current = true
101
+ void activatePreview()
102
+ }, [defaultActive, documentId, slug, activatePreview])
58
103
 
59
104
  // After a save completes while the preview is open, rebuild the URL (the
60
105
  // slug may have changed in settings — the token is bound to the document
@@ -81,37 +126,8 @@ export function useSitePreview({
81
126
  setActive(false)
82
127
  return
83
128
  }
84
- if (!documentId || !slug) {
85
- toast.error('Save the page before opening the site preview')
86
- return
87
- }
88
- setActive(true)
89
- setUrl(null)
90
- void (async () => {
91
- try {
92
- if (dirty) {
93
- const ok = await saveDraft()
94
- if (!ok) {
95
- setActive(false)
96
- return
97
- }
98
- }
99
- const { token } = await createPreviewToken(collection, documentId)
100
- tokenRef.current = token
101
- setUrl(
102
- buildPublicPreviewUrl({
103
- siteUrl: resolveSiteUrl(siteUrl),
104
- urlPrefix: urlPrefix ?? '',
105
- slug,
106
- token,
107
- }),
108
- )
109
- } catch (err) {
110
- toast.error(err instanceof Error ? err.message : 'Failed to open site preview')
111
- setActive(false)
112
- }
113
- })()
114
- }, [active, collection, documentId, slug, urlPrefix, siteUrl, dirty, saveDraft])
129
+ void activatePreview()
130
+ }, [active, activatePreview])
115
131
 
116
132
  return { active, url, reloadKey, toggle }
117
133
  }
@@ -18,6 +18,8 @@ interface PostEditorCanvasProps {
18
18
  onAddSection: () => void
19
19
  /** Section types rendered as placeholders — shows the "Structure view" notice. */
20
20
  structuralTypes?: string[]
21
+ /** Site preview toggle is available — show WYSIWYG nudge on the canvas. */
22
+ sitePreviewAvailable?: boolean
21
23
  }
22
24
 
23
25
  /**
@@ -35,6 +37,7 @@ export function PostEditorCanvas({
35
37
  onSelect,
36
38
  onAddSection,
37
39
  structuralTypes = [],
40
+ sitePreviewAvailable = false,
38
41
  }: PostEditorCanvasProps) {
39
42
  const vp = getViewport(viewport)
40
43
  const containerRef = useRef<HTMLDivElement>(null)
@@ -74,7 +77,10 @@ export function PostEditorCanvas({
74
77
 
75
78
  return (
76
79
  <div ref={containerRef} className="bg-muted h-full overflow-auto p-6">
77
- <StructureNotice structuralTypes={structuralTypes} />
80
+ <StructureNotice
81
+ structuralTypes={structuralTypes}
82
+ sitePreviewAvailable={sitePreviewAvailable}
83
+ />
78
84
  <div
79
85
  className="mx-auto"
80
86
  style={{ width: vp.width * scale, height: surfaceHeight ?? undefined }}
@@ -48,6 +48,7 @@ import { SectionsPanel } from '../page-editor/SectionsPanel.js'
48
48
  import { SectionInspector } from '../page-editor/SectionInspector.js'
49
49
  import { AddSectionModal } from '../page-editor/AddSectionModal.js'
50
50
  import { ValidationSummary } from '../page-editor/ValidationSummary.js'
51
+ import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
51
52
  import { PostEditorCanvas } from './PostEditorCanvas.js'
52
53
  import { PostFieldsModal, type PostFieldConfig } from './PostFieldsModal.js'
53
54
 
@@ -63,6 +64,7 @@ interface EditorConfig {
63
64
  | undefined
64
65
  >
65
66
  seo?: { siteUrl?: string }
67
+ admin?: { editor?: { defaultSitePreview?: boolean | { pages?: boolean; posts?: boolean } } }
66
68
  }
67
69
 
68
70
  /** The slice of the admin session this editor reads. */
@@ -149,6 +151,13 @@ export function PostSectionEditor({
149
151
  )
150
152
  }, [config, postType])
151
153
 
154
+ const publishCheck = useMemo(
155
+ () => (post ? validatePost(post, true) : { errors: [''], warnings: [] }),
156
+ [post],
157
+ )
158
+ const publishReady = Boolean(post) && publishCheck.errors.length === 0
159
+ const publishDisabledReason = publishCheck.errors[0]
160
+
152
161
  // ─── Load ──────────────────────────────────────────────────────────
153
162
  useEffect(() => {
154
163
  let cancelled = false
@@ -324,8 +333,11 @@ export function PostSectionEditor({
324
333
  dirty,
325
334
  saveState,
326
335
  saveDraft: handleSaveDraft,
336
+ defaultActive: resolveDefaultSitePreview(config?.admin?.editor, 'posts'),
327
337
  })
328
338
 
339
+ const sitePreviewAvailable = Boolean(post?.id && post?.slug)
340
+
329
341
  const requestPublish = useCallback(() => {
330
342
  if (!post) return
331
343
  const result = validatePost(post, true)
@@ -499,6 +511,8 @@ export function PostSectionEditor({
499
511
  publishing={publishing}
500
512
  canEdit={canEdit}
501
513
  canPublish={canPublish && canEdit}
514
+ publishReady={publishReady}
515
+ publishDisabledReason={publishDisabledReason}
502
516
  viewport={viewport}
503
517
  backLabel="Posts"
504
518
  onViewport={setViewport}
@@ -570,6 +584,7 @@ export function PostSectionEditor({
570
584
  onSelect={setSelectedId}
571
585
  onAddSection={() => setAddOpen(true)}
572
586
  structuralTypes={structuralTypes}
587
+ sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
573
588
  />
574
589
  )}
575
590
  </div>