@prenta/admin 1.6.0 → 1.6.1

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 (51) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/__tests__/lib/page-audit-input.test.js +10 -0
  3. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -1
  4. package/dist/__tests__/lib/post-editor-service-api.test.js +1 -0
  5. package/dist/__tests__/lib/post-editor-service-api.test.js.map +1 -1
  6. package/dist/__tests__/lib/post-editor-service.test.js +25 -6
  7. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  8. package/dist/__tests__/lib/version-history.test.js +27 -0
  9. package/dist/__tests__/lib/version-history.test.js.map +1 -1
  10. package/dist/__tests__/views/post-fields-modal.render.test.js +19 -0
  11. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  12. package/dist/__tests__/views/post-section-editor.test.js +1 -0
  13. package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
  14. package/dist/__tests__/views/post-tab.render.test.js +1 -0
  15. package/dist/__tests__/views/post-tab.render.test.js.map +1 -1
  16. package/dist/lib/page-audit-input.js +1 -1
  17. package/dist/lib/page-audit-input.js.map +1 -1
  18. package/dist/lib/post-editor-service.d.ts +18 -3
  19. package/dist/lib/post-editor-service.d.ts.map +1 -1
  20. package/dist/lib/post-editor-service.js +33 -13
  21. package/dist/lib/post-editor-service.js.map +1 -1
  22. package/dist/lib/version-history.d.ts.map +1 -1
  23. package/dist/lib/version-history.js +3 -9
  24. package/dist/lib/version-history.js.map +1 -1
  25. package/dist/views/post-editor/PostFieldsForm.d.ts +3 -0
  26. package/dist/views/post-editor/PostFieldsForm.d.ts.map +1 -1
  27. package/dist/views/post-editor/PostFieldsForm.js +7 -2
  28. package/dist/views/post-editor/PostFieldsForm.js.map +1 -1
  29. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  30. package/dist/views/post-editor/PostFieldsModal.js +15 -2
  31. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  32. package/dist/views/post-editor/PostSectionEditor.js +3 -3
  33. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  34. package/dist/views/post-editor/PostTab.d.ts.map +1 -1
  35. package/dist/views/post-editor/PostTab.js +1 -0
  36. package/dist/views/post-editor/PostTab.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/__tests__/lib/page-audit-input.test.ts +11 -0
  39. package/src/__tests__/lib/post-editor-service-api.test.ts +1 -0
  40. package/src/__tests__/lib/post-editor-service.test.ts +26 -9
  41. package/src/__tests__/lib/version-history.test.ts +29 -0
  42. package/src/__tests__/views/post-fields-modal.render.test.tsx +39 -0
  43. package/src/__tests__/views/post-section-editor.test.tsx +1 -0
  44. package/src/__tests__/views/post-tab.render.test.tsx +1 -0
  45. package/src/lib/page-audit-input.ts +1 -1
  46. package/src/lib/post-editor-service.ts +45 -10
  47. package/src/lib/version-history.ts +3 -7
  48. package/src/views/post-editor/PostFieldsForm.tsx +14 -5
  49. package/src/views/post-editor/PostFieldsModal.tsx +15 -2
  50. package/src/views/post-editor/PostSectionEditor.tsx +3 -3
  51. package/src/views/post-editor/PostTab.tsx +1 -0
@@ -244,6 +244,17 @@ describe('post audit extras', () => {
244
244
  expect(imageSlots(withImage.sections)).toHaveLength(1)
245
245
  expect(imageSlots(withImage.sections)[0]?.sectionId).toBe('__post-featured-image')
246
246
  })
247
+
248
+ it('does not invent an image slot from a bare media id', () => {
249
+ const withId = buildPageAuditInput({
250
+ sections: textOnlySections,
251
+ meta: postMeta,
252
+ slug: 'a-post',
253
+ record: null,
254
+ post: { featuredImage: 'cmssbdfxo000004jrzphzt2bj' },
255
+ })
256
+ expect(imageSlots(withId.sections)).toHaveLength(0)
257
+ })
247
258
  })
248
259
 
249
260
  function builderTree(heroData: Record<string, unknown>) {
@@ -23,6 +23,7 @@ function draft(id: string | null) {
23
23
  slug: 'hello',
24
24
  excerpt: '',
25
25
  featuredImage: '',
26
+ featuredImageUrl: '',
26
27
  body: '',
27
28
  category: '',
28
29
  tags: [],
@@ -17,6 +17,7 @@ const {
17
17
  missingPostFields,
18
18
  savePostDraft,
19
19
  parseFeaturedImage,
20
+ parseFeaturedImageRef,
20
21
  } = await import('../../lib/post-editor-service.js')
21
22
 
22
23
  type EditorPostT = Awaited<ReturnType<typeof emptyPostFromTemplate>>
@@ -29,6 +30,7 @@ function basePost(over: Partial<EditorPostT> = {}): EditorPostT {
29
30
  slug: 'hello',
30
31
  excerpt: '',
31
32
  featuredImage: '',
33
+ featuredImageUrl: '',
32
34
  body: '',
33
35
  category: '',
34
36
  tags: [],
@@ -494,21 +496,34 @@ describe('tags round-trip', () => {
494
496
  expect(post.tags).toEqual(['AI', 'Nextjs'])
495
497
  })
496
498
 
499
+ it('parseFeaturedImageRef splits persist id from display url', () => {
500
+ const mediaId = 'cmry1z9yr000004l832th7bf2'
501
+ const url = 'https://cdn.example.com/hero.webp'
502
+ expect(parseFeaturedImageRef({ id: mediaId, url, alt: 'Hero' })).toEqual({
503
+ id: mediaId,
504
+ url,
505
+ })
506
+ expect(parseFeaturedImageRef(mediaId)).toEqual({ id: mediaId, url: '' })
507
+ expect(parseFeaturedImageRef(url)).toEqual({ id: url, url })
508
+ expect(parseFeaturedImageRef({ publicUrl: url })).toEqual({ id: url, url })
509
+ expect(parseFeaturedImageRef({ src: '/uploads/hero.webp' })).toEqual({
510
+ id: '/uploads/hero.webp',
511
+ url: '/uploads/hero.webp',
512
+ })
513
+ })
514
+
497
515
  it('parseFeaturedImage / fetchPostForEditor keep denormalized media objects (#671)', async () => {
498
516
  const mediaId = 'cmry1z9yr000004l832th7bf2'
517
+ const url = 'https://cdn.example.com/hero.webp'
499
518
  expect(
500
519
  parseFeaturedImage({
501
520
  id: mediaId,
502
- url: 'https://cdn.example.com/hero.webp',
521
+ url,
503
522
  alt: 'Hero',
504
523
  }),
505
524
  ).toBe(mediaId)
506
- expect(parseFeaturedImage('https://cdn.example.com/hero.webp')).toBe(
507
- 'https://cdn.example.com/hero.webp',
508
- )
509
- expect(parseFeaturedImage({ url: 'https://cdn.example.com/hero.webp' })).toBe(
510
- 'https://cdn.example.com/hero.webp',
511
- )
525
+ expect(parseFeaturedImage(url)).toBe(url)
526
+ expect(parseFeaturedImage({ url })).toBe(url)
512
527
 
513
528
  cmsApiMock.mockResolvedValueOnce({
514
529
  data: {
@@ -519,7 +534,7 @@ describe('tags round-trip', () => {
519
534
  data: {
520
535
  featuredImage: {
521
536
  id: mediaId,
522
- url: 'https://cdn.example.com/hero.webp',
537
+ url,
523
538
  alt: 'Hero',
524
539
  },
525
540
  },
@@ -527,16 +542,18 @@ describe('tags round-trip', () => {
527
542
  })
528
543
  const post = await fetchPostForEditor('blog', 'p1')
529
544
  expect(post.featuredImage).toBe(mediaId)
545
+ expect(post.featuredImageUrl).toBe(url)
530
546
 
531
547
  cmsApiMock.mockResolvedValueOnce({
532
548
  data: { id: 'p1', title: 'Hello', slug: 'hello', status: 'DRAFT', data: {} },
533
549
  })
534
- await savePostDraft(basePost({ id: 'p1', featuredImage: mediaId }))
550
+ await savePostDraft(basePost({ id: 'p1', featuredImage: mediaId, featuredImageUrl: url }))
535
551
  const body = JSON.parse(cmsApiMock.mock.calls[1]![1].body as string) as {
536
552
  featuredImage: string
537
553
  }
538
554
  expect(body.featuredImage).toBe(mediaId)
539
555
  expect(body.featuredImage).not.toBe('')
556
+ expect(body).not.toHaveProperty('featuredImageUrl')
540
557
  })
541
558
  })
542
559
 
@@ -60,6 +60,35 @@ describe('toPageVersion', () => {
60
60
  expect(mapped.body).toBe('<p>Snapshot body</p>')
61
61
  })
62
62
 
63
+ it('does not treat a bare media id as a canvas preview URL', () => {
64
+ const mapped = toPageVersion({
65
+ id: 'v1',
66
+ changeType: 'UPDATE',
67
+ createdAt: '2026-08-02T12:00:00Z',
68
+ data: {
69
+ featuredImage: 'cmssbdfxo000004jrzphzt2bj',
70
+ sections: [],
71
+ },
72
+ })
73
+ expect(mapped.featuredImage).toBeUndefined()
74
+ })
75
+
76
+ it('prefers the public URL over the media id on a denormalized snapshot', () => {
77
+ const mapped = toPageVersion({
78
+ id: 'v1',
79
+ changeType: 'UPDATE',
80
+ createdAt: '2026-08-02T12:00:00Z',
81
+ data: {
82
+ featuredImage: {
83
+ id: 'cmssbdfxo000004jrzphzt2bj',
84
+ url: 'https://cdn.example.com/hero.webp',
85
+ },
86
+ sections: [],
87
+ },
88
+ })
89
+ expect(mapped.featuredImage).toBe('https://cdn.example.com/hero.webp')
90
+ })
91
+
63
92
  it('preserves an unmanaged section rather than dropping it from the snapshot', () => {
64
93
  // Coercing a snapshot more strictly than the live document reads would
65
94
  // make an externally-managed section look deleted in every version.
@@ -39,6 +39,7 @@ function basePost(over: Partial<ModalProps['post']> = {}): ModalProps['post'] {
39
39
  slug: 'hello',
40
40
  excerpt: '',
41
41
  featuredImage: '',
42
+ featuredImageUrl: '',
42
43
  body: '',
43
44
  category: '',
44
45
  tags: [],
@@ -175,4 +176,42 @@ describe('PostFieldsModal', () => {
175
176
  expect(onClose).toHaveBeenCalledTimes(1)
176
177
  expect(screen.queryByRole('dialog', { name: 'Discard unsaved changes?' })).toBeNull()
177
178
  })
179
+
180
+ it('previews the featured image URL, not the stored media id', () => {
181
+ const mediaId = 'cmssbdfxo000004jrzphzt2bj'
182
+ const url = 'https://cdn.example.com/hero.webp'
183
+ render(
184
+ <PostFieldsModal
185
+ open
186
+ post={basePost({ featuredImage: mediaId, featuredImageUrl: url })}
187
+ canEdit
188
+ onClose={() => {}}
189
+ onSave={() => {}}
190
+ />,
191
+ )
192
+ const input = screen.getByLabelText('Featured image') as HTMLInputElement
193
+ expect(input.value).toBe(url)
194
+ expect(input.value).not.toBe(mediaId)
195
+ const preview = document.querySelector('img')
196
+ expect(preview?.getAttribute('src')).toBe(url)
197
+ })
198
+
199
+ it('saves the media id when the input is showing the public URL', () => {
200
+ const onSave = vi.fn()
201
+ const mediaId = 'cmssbdfxo000004jrzphzt2bj'
202
+ const url = 'https://cdn.example.com/hero.webp'
203
+ render(
204
+ <PostFieldsModal
205
+ open
206
+ post={basePost({ featuredImage: mediaId, featuredImageUrl: url })}
207
+ canEdit
208
+ onClose={() => {}}
209
+ onSave={onSave}
210
+ />,
211
+ )
212
+ fireEvent.click(screen.getByRole('button', { name: 'Done' }))
213
+ expect(onSave).toHaveBeenCalledWith(
214
+ expect.objectContaining({ featuredImage: mediaId, featuredImageUrl: url }),
215
+ )
216
+ })
178
217
  })
@@ -49,6 +49,7 @@ function loadedPost(
49
49
  slug: 'logan',
50
50
  excerpt: '',
51
51
  featuredImage: '',
52
+ featuredImageUrl: '',
52
53
  body: '',
53
54
  category: '',
54
55
  tags: [],
@@ -27,6 +27,7 @@ function basePost(over: Partial<EditorPost> = {}): EditorPost {
27
27
  slug: 'hello',
28
28
  excerpt: 'A short summary.',
29
29
  featuredImage: '',
30
+ featuredImageUrl: '',
30
31
  body: '',
31
32
  category: '',
32
33
  tags: [],
@@ -95,7 +95,7 @@ export function withPostAuditSlots(sections: PageSection[], post?: PostAuditExtr
95
95
  })
96
96
  }
97
97
  const image = post.featuredImage?.trim() ?? ''
98
- if (image) {
98
+ if (image && (image.startsWith('/') || /^https?:\/\//i.test(image))) {
99
99
  extras.push({
100
100
  id: POST_IMAGE_SECTION_ID,
101
101
  sectionType: 'feature',
@@ -74,7 +74,10 @@ export interface EditorPost {
74
74
  title: string
75
75
  slug: string
76
76
  excerpt: string
77
+ /** Persist identity — media id when present so usage tracking survives (#671). */
77
78
  featuredImage: string
79
+ /** Public URL for `<img src>` and the details input. Empty when only an id is known. */
80
+ featuredImageUrl: string
78
81
  body: string
79
82
  category: string
80
83
  /** Editorial tags as plain strings (stored as `[{ tag }]` rows, see {@link parseTags}). */
@@ -216,20 +219,49 @@ function dataStr(data: Record<string, unknown>, key: string): string {
216
219
  return typeof v === 'string' ? v : ''
217
220
  }
218
221
 
222
+ export interface FeaturedImageRef {
223
+ /** Persist this — media id when present so usage tracking survives (#671). */
224
+ id: string
225
+ /** Use this for `<img src>` and the details input. Empty when only an id is known. */
226
+ url: string
227
+ }
228
+
229
+ /** True for paths and http(s) URLs — never a bare media cuid. */
230
+ export function isDisplayableImageSrc(value: string): boolean {
231
+ const v = value.trim()
232
+ return v.startsWith('/') || /^https?:\/\//i.test(v)
233
+ }
234
+
235
+ function asDisplayUrl(value: unknown): string {
236
+ if (typeof value !== 'string') return ''
237
+ const v = value.trim()
238
+ return isDisplayableImageSrc(v) ? v : ''
239
+ }
240
+
219
241
  /**
220
- * Media fields are stored denormalized as `{ id, url, alt, }` after headless
221
- * writes / admin picker. The editor state is a string (id or URL). Never coerce
222
- * an object to `""` that wiped featured images on the next save (#671).
242
+ * Split a stored featured-image value into persist identity and display URL.
243
+ *
244
+ * Media fields are denormalized as `{ id, url, alt, }` after headless writes
245
+ * / the admin picker. Never coerce an object to `""` — that wiped featured
246
+ * images on the next save (#671).
223
247
  */
224
- export function parseFeaturedImage(raw: unknown): string {
225
- if (typeof raw === 'string') return raw.trim()
248
+ export function parseFeaturedImageRef(raw: unknown): FeaturedImageRef {
249
+ if (typeof raw === 'string') {
250
+ const value = raw.trim()
251
+ return { id: value, url: asDisplayUrl(value) }
252
+ }
226
253
  if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
227
254
  const obj = raw as Record<string, unknown>
228
- if (typeof obj.id === 'string' && obj.id.trim()) return obj.id.trim()
229
- const url = obj.url ?? obj.publicUrl ?? obj.src
230
- if (typeof url === 'string' && url.trim()) return url.trim()
255
+ const id = typeof obj.id === 'string' ? obj.id.trim() : ''
256
+ const url = asDisplayUrl(obj.url ?? obj.publicUrl ?? obj.src)
257
+ return { id: id || url, url }
231
258
  }
232
- return ''
259
+ return { id: '', url: '' }
260
+ }
261
+
262
+ /** Persist identity only — prefer `id` so a save does not drop the media link. */
263
+ export function parseFeaturedImage(raw: unknown): string {
264
+ return parseFeaturedImageRef(raw).id
233
265
  }
234
266
 
235
267
  /**
@@ -255,6 +287,7 @@ export function parseTags(raw: unknown): string[] {
255
287
  function rowToEditorPost(postType: string, doc: DocumentApiRow): EditorPost {
256
288
  const data = doc.data ?? {}
257
289
  const dataTitle = dataStr(data, 'title')
290
+ const featured = parseFeaturedImageRef(data.featuredImage)
258
291
  return {
259
292
  id: doc.id,
260
293
  postType,
@@ -263,7 +296,8 @@ function rowToEditorPost(postType: string, doc: DocumentApiRow): EditorPost {
263
296
  title: dataTitle || (doc.title ?? ''),
264
297
  slug: doc.slug ?? dataStr(data, 'slug'),
265
298
  excerpt: dataStr(data, 'excerpt'),
266
- featuredImage: parseFeaturedImage(data.featuredImage),
299
+ featuredImage: featured.id,
300
+ featuredImageUrl: featured.url,
267
301
  body: dataStr(data, 'body'),
268
302
  category: dataStr(data, 'category'),
269
303
  tags: parseTags(data.tags),
@@ -457,6 +491,7 @@ export async function emptyPostFromTemplate(postType: string): Promise<EditorPos
457
491
  slug: '',
458
492
  excerpt: '',
459
493
  featuredImage: '',
494
+ featuredImageUrl: '',
460
495
  body: '',
461
496
  category: '',
462
497
  tags: [],
@@ -9,6 +9,7 @@
9
9
  * response and no second request per row is needed.
10
10
  */
11
11
  import { cmsApi } from './api.js'
12
+ import { parseFeaturedImageRef } from './post-editor-service.js'
12
13
  import { coerceSections, type PageSection } from '@prenta/core/page-sections'
13
14
 
14
15
  /**
@@ -97,13 +98,8 @@ function optionalString(value: unknown): string | undefined {
97
98
  }
98
99
 
99
100
  function optionalFeaturedImage(value: unknown): string | undefined {
100
- if (typeof value === 'string') return value
101
- if (value && typeof value === 'object' && !Array.isArray(value)) {
102
- const obj = value as Record<string, unknown>
103
- const url = obj.url ?? obj.publicUrl ?? obj.src
104
- if (typeof url === 'string') return url
105
- }
106
- return undefined
101
+ const url = parseFeaturedImageRef(value).url
102
+ return url || undefined
107
103
  }
108
104
 
109
105
  export function toPageVersion(row: VersionApiRow): PageVersion {
@@ -23,7 +23,10 @@ export interface PostFieldsFormValues {
23
23
  title: string
24
24
  slug: string
25
25
  excerpt: string
26
+ /** Persist identity (media id when known). */
26
27
  featuredImage: string
28
+ /** Public URL shown in the input and used as `<img src>`. */
29
+ featuredImageUrl: string
27
30
  category: string
28
31
  tags: string[]
29
32
  }
@@ -286,10 +289,13 @@ export function PostFieldsForm({
286
289
  <input
287
290
  id={imageId}
288
291
  className={`${INPUT} min-w-0 flex-1`}
289
- value={values.featuredImage}
292
+ value={values.featuredImageUrl}
290
293
  disabled={!canEdit}
291
294
  placeholder="https://… or pick from the media library"
292
- onChange={(e) => onChange({ featuredImage: e.target.value })}
295
+ onChange={(e) => {
296
+ const next = e.target.value
297
+ onChange({ featuredImage: next, featuredImageUrl: next })
298
+ }}
293
299
  />
294
300
  <button
295
301
  type="button"
@@ -300,9 +306,9 @@ export function PostFieldsForm({
300
306
  Browse
301
307
  </button>
302
308
  </div>
303
- {values.featuredImage && (
309
+ {values.featuredImageUrl && (
304
310
  <div className="border-border bg-muted mt-2 overflow-hidden rounded-md border">
305
- <img src={values.featuredImage} alt="" className="h-28 w-full object-cover" />
311
+ <img src={values.featuredImageUrl} alt="" className="h-28 w-full object-cover" />
306
312
  </div>
307
313
  )}
308
314
  </div>
@@ -311,8 +317,11 @@ export function PostFieldsForm({
311
317
  <MediaPickerModal
312
318
  open={mediaOpen}
313
319
  onClose={() => setMediaOpen(false)}
320
+ onSelectItem={(item) => {
321
+ onChange({ featuredImage: item.id || item.url, featuredImageUrl: item.url })
322
+ }}
314
323
  onSelect={(url) => {
315
- onChange({ featuredImage: url })
324
+ onChange({ featuredImageUrl: url })
316
325
  setMediaOpen(false)
317
326
  }}
318
327
  accept="image/*"
@@ -29,6 +29,7 @@ type FormSnapshot = {
29
29
  slug: string
30
30
  excerpt: string
31
31
  featuredImage: string
32
+ featuredImageUrl: string
32
33
  category: string
33
34
  tags: string
34
35
  }
@@ -39,6 +40,7 @@ function snapshotFromPost(post: EditorPost): FormSnapshot {
39
40
  slug: post.slug,
40
41
  excerpt: post.excerpt,
41
42
  featuredImage: post.featuredImage,
43
+ featuredImageUrl: post.featuredImageUrl,
42
44
  category: post.category,
43
45
  tags: [...post.tags].sort().join('\0'),
44
46
  }
@@ -64,6 +66,7 @@ export function PostFieldsModal({
64
66
  const [slug, setSlug] = useState(post.slug)
65
67
  const [excerpt, setExcerpt] = useState(post.excerpt)
66
68
  const [featuredImage, setFeaturedImage] = useState(post.featuredImage)
69
+ const [featuredImageUrl, setFeaturedImageUrl] = useState(post.featuredImageUrl)
67
70
  const [category, setCategory] = useState(post.category)
68
71
  const [tags, setTags] = useState<string[]>(post.tags)
69
72
  const [slugTouched, setSlugTouched] = useState(Boolean(post.slug))
@@ -82,13 +85,22 @@ export function PostFieldsModal({
82
85
  setSlug(post.slug)
83
86
  setExcerpt(post.excerpt)
84
87
  setFeaturedImage(post.featuredImage)
88
+ setFeaturedImageUrl(post.featuredImageUrl)
85
89
  setCategory(post.category)
86
90
  setTags(post.tags)
87
91
  setSlugTouched(Boolean(post.slug))
88
92
  baselineRef.current = snapshotFromPost(post)
89
93
  }, [open, post])
90
94
 
91
- const values: PostFieldsFormValues = { title, slug, excerpt, featuredImage, category, tags }
95
+ const values: PostFieldsFormValues = {
96
+ title,
97
+ slug,
98
+ excerpt,
99
+ featuredImage,
100
+ featuredImageUrl,
101
+ category,
102
+ tags,
103
+ }
92
104
 
93
105
  const isDirty = useMemo(() => {
94
106
  if (!open || !baselineRef.current) return false
@@ -108,6 +120,7 @@ export function PostFieldsModal({
108
120
  if (patch.slug !== undefined) setSlug(patch.slug)
109
121
  if (patch.excerpt !== undefined) setExcerpt(patch.excerpt)
110
122
  if (patch.featuredImage !== undefined) setFeaturedImage(patch.featuredImage)
123
+ if (patch.featuredImageUrl !== undefined) setFeaturedImageUrl(patch.featuredImageUrl)
111
124
  if (patch.category !== undefined) setCategory(patch.category)
112
125
  if (patch.tags !== undefined) setTags(patch.tags)
113
126
  }
@@ -116,7 +129,7 @@ export function PostFieldsModal({
116
129
 
117
130
  function handleSave() {
118
131
  if (slugError) return
119
- onSave({ title, slug, excerpt, featuredImage, category, tags })
132
+ onSave({ title, slug, excerpt, featuredImage, featuredImageUrl, category, tags })
120
133
  onClose()
121
134
  }
122
135
 
@@ -226,7 +226,7 @@ export function PostSectionEditor({
226
226
  post: {
227
227
  title: post?.title,
228
228
  bodyHtml: post?.body,
229
- featuredImage: post?.featuredImage,
229
+ featuredImage: post?.featuredImageUrl,
230
230
  },
231
231
  })
232
232
  const audit = liveAudit ?? savedAudit
@@ -338,7 +338,7 @@ export function PostSectionEditor({
338
338
  title: post?.title,
339
339
  excerpt: post?.excerpt,
340
340
  body: post?.body,
341
- featuredImageUrl: post?.featuredImage,
341
+ featuredImageUrl: post?.featuredImageUrl,
342
342
  publishDate: post?.publishDate ?? post?.updatedAt ?? null,
343
343
  category: post?.category,
344
344
  author: { name: session?.user?.name || session?.user?.email },
@@ -841,7 +841,7 @@ export function PostSectionEditor({
841
841
  contentHtml={seoContentHtml}
842
842
  title={post.title}
843
843
  bodyHtml={post.body}
844
- featuredImage={post.featuredImage}
844
+ featuredImage={post.featuredImageUrl}
845
845
  draftMeta={{
846
846
  seoTitle: post.seoTitle,
847
847
  seoDescription: post.seoDescription,
@@ -30,6 +30,7 @@ export function PostTab({ post, canEdit, fields, onChange }: PostTabProps) {
30
30
  slug: post.slug,
31
31
  excerpt: post.excerpt,
32
32
  featuredImage: post.featuredImage,
33
+ featuredImageUrl: post.featuredImageUrl,
33
34
  category: post.category,
34
35
  tags: post.tags,
35
36
  }}