@actuate-media/cms-admin 0.49.1 → 0.49.4

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 (113) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/AdminRoot.js +2 -2
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/actuate-admin.css +1 -1
  5. package/dist/components/CoauthorResultPopover.js +1 -1
  6. package/dist/components/CoauthorResultPopover.js.map +1 -1
  7. package/dist/components/CommentSidePanel.js +1 -1
  8. package/dist/components/CommentSidePanel.js.map +1 -1
  9. package/dist/components/ContentOverviewChart.js +1 -1
  10. package/dist/components/ContentOverviewChart.js.map +1 -1
  11. package/dist/components/FolderTree.js +19 -19
  12. package/dist/components/FolderTree.js.map +1 -1
  13. package/dist/components/NotificationBell.js +1 -1
  14. package/dist/components/NotificationBell.js.map +1 -1
  15. package/dist/components/PresenceChips.js +1 -1
  16. package/dist/components/PresenceChips.js.map +1 -1
  17. package/dist/components/SEOConfigPanel.js +2 -2
  18. package/dist/components/SEOConfigPanel.js.map +1 -1
  19. package/dist/components/SEOPanel.d.ts.map +1 -1
  20. package/dist/components/SEOPanel.js +2 -2
  21. package/dist/components/SEOPanel.js.map +1 -1
  22. package/dist/components/SEOPerformance.js +2 -2
  23. package/dist/components/SEOPerformance.js.map +1 -1
  24. package/dist/components/forms/EntriesTable.js +1 -1
  25. package/dist/components/forms/EntriesTable.js.map +1 -1
  26. package/dist/components/ui/Card.js +1 -1
  27. package/dist/components/ui/Card.js.map +1 -1
  28. package/dist/components/ui/EmptyState.js +1 -1
  29. package/dist/components/ui/EmptyState.js.map +1 -1
  30. package/dist/layout/Sidebar.js +1 -1
  31. package/dist/layout/Sidebar.js.map +1 -1
  32. package/dist/layout/primitives/PageContainer.js +1 -1
  33. package/dist/layout/primitives/PageContainer.js.map +1 -1
  34. package/dist/views/CollectionList.js +1 -1
  35. package/dist/views/CollectionList.js.map +1 -1
  36. package/dist/views/Dashboard.js +8 -8
  37. package/dist/views/Dashboard.js.map +1 -1
  38. package/dist/views/DocumentEdit.js +1 -1
  39. package/dist/views/DocumentEdit.js.map +1 -1
  40. package/dist/views/ForgotPassword.js +1 -1
  41. package/dist/views/ForgotPassword.js.map +1 -1
  42. package/dist/views/FormEditor.d.ts.map +1 -1
  43. package/dist/views/FormEditor.js +1 -1
  44. package/dist/views/FormEditor.js.map +1 -1
  45. package/dist/views/Login.js +1 -1
  46. package/dist/views/Login.js.map +1 -1
  47. package/dist/views/MediaBrowser.d.ts.map +1 -1
  48. package/dist/views/MediaBrowser.js +16 -16
  49. package/dist/views/MediaBrowser.js.map +1 -1
  50. package/dist/views/PageEditor.js +1 -1
  51. package/dist/views/PageEditor.js.map +1 -1
  52. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  53. package/dist/views/Pages/PagesListView.js +5 -5
  54. package/dist/views/Pages/PagesListView.js.map +1 -1
  55. package/dist/views/Pages.js +1 -1
  56. package/dist/views/Pages.js.map +1 -1
  57. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  58. package/dist/views/Posts/PostsListView.js +4 -4
  59. package/dist/views/Posts/PostsListView.js.map +1 -1
  60. package/dist/views/Redirects.d.ts.map +1 -1
  61. package/dist/views/Redirects.js +7 -7
  62. package/dist/views/Redirects.js.map +1 -1
  63. package/dist/views/ResetPassword.js +2 -2
  64. package/dist/views/ResetPassword.js.map +1 -1
  65. package/dist/views/ScriptTagEditor.js +4 -4
  66. package/dist/views/ScriptTagEditor.js.map +1 -1
  67. package/dist/views/ScriptTags.d.ts.map +1 -1
  68. package/dist/views/ScriptTags.js +5 -5
  69. package/dist/views/ScriptTags.js.map +1 -1
  70. package/dist/views/SetupWizard.js +2 -2
  71. package/dist/views/SetupWizard.js.map +1 -1
  72. package/dist/views/page-editor/SectionInspector.js +2 -2
  73. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  74. package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
  75. package/dist/views/post-editor/PostHeader.js +2 -2
  76. package/dist/views/post-editor/PostHeader.js.map +1 -1
  77. package/dist/views/post-editor/PostHeaderPanel.js +1 -1
  78. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/AdminRoot.tsx +2 -2
  81. package/src/components/CoauthorResultPopover.tsx +1 -1
  82. package/src/components/CommentSidePanel.tsx +1 -1
  83. package/src/components/ContentOverviewChart.tsx +1 -1
  84. package/src/components/FolderTree.tsx +32 -32
  85. package/src/components/NotificationBell.tsx +2 -2
  86. package/src/components/PresenceChips.tsx +1 -1
  87. package/src/components/SEOConfigPanel.tsx +3 -3
  88. package/src/components/SEOPanel.tsx +8 -6
  89. package/src/components/SEOPerformance.tsx +5 -5
  90. package/src/components/forms/EntriesTable.tsx +1 -1
  91. package/src/components/ui/Card.tsx +1 -1
  92. package/src/components/ui/EmptyState.tsx +1 -1
  93. package/src/layout/Sidebar.tsx +1 -1
  94. package/src/layout/primitives/PageContainer.tsx +1 -1
  95. package/src/views/CollectionList.tsx +1 -1
  96. package/src/views/Dashboard.tsx +12 -12
  97. package/src/views/DocumentEdit.tsx +4 -4
  98. package/src/views/ForgotPassword.tsx +1 -1
  99. package/src/views/FormEditor.tsx +1 -3
  100. package/src/views/Login.tsx +2 -2
  101. package/src/views/MediaBrowser.tsx +126 -120
  102. package/src/views/PageEditor.tsx +2 -2
  103. package/src/views/Pages/PagesListView.tsx +126 -118
  104. package/src/views/Pages.tsx +1 -1
  105. package/src/views/Posts/PostsListView.tsx +115 -111
  106. package/src/views/Redirects.tsx +48 -44
  107. package/src/views/ResetPassword.tsx +2 -2
  108. package/src/views/ScriptTagEditor.tsx +40 -40
  109. package/src/views/ScriptTags.tsx +34 -30
  110. package/src/views/SetupWizard.tsx +2 -2
  111. package/src/views/page-editor/SectionInspector.tsx +4 -4
  112. package/src/views/post-editor/PostHeader.tsx +2 -4
  113. package/src/views/post-editor/PostHeaderPanel.tsx +2 -2
@@ -122,7 +122,7 @@ export function PageEditor({ id, onNavigate }: PageEditorProps) {
122
122
  value={title}
123
123
  onChange={(e) => setTitle(e.target.value)}
124
124
  placeholder="Page title"
125
- className="mb-4 w-full border-none px-0 text-2xl font-bold placeholder:text-gray-300 focus:ring-0 focus:outline-none sm:text-3xl"
125
+ className="mb-4 w-full border-none px-0 text-2xl font-medium placeholder:text-gray-300 focus:ring-0 focus:outline-none sm:text-3xl"
126
126
  />
127
127
 
128
128
  <div className="mb-4">
@@ -149,7 +149,7 @@ export function PageEditor({ id, onNavigate }: PageEditorProps) {
149
149
  <div className="w-[30%] overflow-y-auto border-l border-gray-200 bg-gray-50">
150
150
  <div className="space-y-4 p-4">
151
151
  <div className="rounded-lg border border-gray-200 bg-white p-4">
152
- <h3 className="mb-3 text-sm font-semibold text-gray-900">Publish</h3>
152
+ <h3 className="mb-3 text-sm font-medium text-gray-900">Publish</h3>
153
153
  <div className="space-y-3">
154
154
  <div>
155
155
  <label className="mb-1 block text-xs font-medium text-gray-700">Status</label>
@@ -347,7 +347,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
347
347
 
348
348
  {/* Tag filter bar */}
349
349
  <div className="flex flex-wrap items-center gap-2">
350
- <span className="text-muted-foreground text-[10px] font-semibold tracking-wider uppercase">
350
+ <span className="text-muted-foreground text-[10px] font-medium tracking-wider uppercase">
351
351
  Tags
352
352
  </span>
353
353
  {tags.map((t) => (
@@ -398,123 +398,131 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
398
398
 
399
399
  {/* Table card */}
400
400
  <div className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
401
- <table className="w-full table-fixed" role="table" aria-label="Pages">
402
- <colgroup>
403
- <col style={{ width: '40px' }} />
404
- <col />
405
- <col style={{ width: '200px' }} />
406
- <col style={{ width: '170px' }} />
407
- <col style={{ width: '120px' }} />
408
- <col style={{ width: '130px' }} />
409
- <col style={{ width: '80px' }} />
410
- </colgroup>
411
- <thead className="border-border bg-muted/40 border-b">
412
- <tr className="text-muted-foreground text-xs">
413
- <th scope="col" className="px-3 py-2.5 text-left">
414
- <input
415
- type="checkbox"
416
- aria-label="Select all pages on this page"
417
- checked={allOnPageSelected}
418
- onChange={(e) => toggleAllOnPage(e.target.checked)}
419
- className="border-border accent-primary rounded"
420
- />
421
- </th>
422
- <th scope="col" aria-sort={ariaSortFor('title')} className="px-3 py-2.5 text-left">
423
- <SortHeader
424
- label="Title"
425
- field="title"
426
- current={sortBy}
427
- dir={dir}
428
- onSort={handleSort}
429
- />
430
- </th>
431
- <th scope="col" className="px-3 py-2.5 text-left">
432
- <span className="text-xs font-medium tracking-wider uppercase">Tags</span>
433
- </th>
434
- <th scope="col" aria-sort={ariaSortFor('author')} className="px-3 py-2.5 text-left">
435
- <SortHeader
436
- label="Author"
437
- field="author"
438
- current={sortBy}
439
- dir={dir}
440
- onSort={handleSort}
441
- />
442
- </th>
443
- <th scope="col" aria-sort={ariaSortFor('status')} className="px-3 py-2.5 text-left">
444
- <SortHeader
445
- label="Status"
446
- field="status"
447
- current={sortBy}
448
- dir={dir}
449
- onSort={handleSort}
450
- />
451
- </th>
452
- <th scope="col" aria-sort={ariaSortFor('modified')} className="px-3 py-2.5 text-left">
453
- <SortHeader
454
- label="Last Modified"
455
- field="modified"
456
- current={sortBy}
457
- dir={dir}
458
- onSort={handleSort}
459
- />
460
- </th>
461
- <th scope="col" aria-sort={ariaSortFor('seo')} className="px-3 py-2.5 text-left">
462
- <SortHeader
463
- label="SEO"
464
- field="seo"
465
- current={sortBy}
466
- dir={dir}
467
- onSort={handleSort}
401
+ {/* Horizontal scroll on narrow viewports: the fixed columns keep their
402
+ width and the table scrolls sideways instead of crushing content. */}
403
+ <div className="overflow-x-auto">
404
+ <table className="w-full min-w-[800px] table-fixed" role="table" aria-label="Pages">
405
+ <colgroup>
406
+ <col style={{ width: '40px' }} />
407
+ <col />
408
+ <col style={{ width: '200px' }} />
409
+ <col style={{ width: '170px' }} />
410
+ <col style={{ width: '120px' }} />
411
+ <col style={{ width: '130px' }} />
412
+ <col style={{ width: '80px' }} />
413
+ </colgroup>
414
+ <thead className="border-border bg-muted/40 border-b">
415
+ <tr className="text-muted-foreground text-xs">
416
+ <th scope="col" className="px-3 py-2.5 text-left">
417
+ <input
418
+ type="checkbox"
419
+ aria-label="Select all pages on this page"
420
+ checked={allOnPageSelected}
421
+ onChange={(e) => toggleAllOnPage(e.target.checked)}
422
+ className="border-border accent-primary rounded"
423
+ />
424
+ </th>
425
+ <th scope="col" aria-sort={ariaSortFor('title')} className="px-3 py-2.5 text-left">
426
+ <SortHeader
427
+ label="Title"
428
+ field="title"
429
+ current={sortBy}
430
+ dir={dir}
431
+ onSort={handleSort}
432
+ />
433
+ </th>
434
+ <th scope="col" className="px-3 py-2.5 text-left">
435
+ <span className="text-xs font-medium tracking-wider uppercase">Tags</span>
436
+ </th>
437
+ <th scope="col" aria-sort={ariaSortFor('author')} className="px-3 py-2.5 text-left">
438
+ <SortHeader
439
+ label="Author"
440
+ field="author"
441
+ current={sortBy}
442
+ dir={dir}
443
+ onSort={handleSort}
444
+ />
445
+ </th>
446
+ <th scope="col" aria-sort={ariaSortFor('status')} className="px-3 py-2.5 text-left">
447
+ <SortHeader
448
+ label="Status"
449
+ field="status"
450
+ current={sortBy}
451
+ dir={dir}
452
+ onSort={handleSort}
453
+ />
454
+ </th>
455
+ <th
456
+ scope="col"
457
+ aria-sort={ariaSortFor('modified')}
458
+ className="px-3 py-2.5 text-left"
459
+ >
460
+ <SortHeader
461
+ label="Last Modified"
462
+ field="modified"
463
+ current={sortBy}
464
+ dir={dir}
465
+ onSort={handleSort}
466
+ />
467
+ </th>
468
+ <th scope="col" aria-sort={ariaSortFor('seo')} className="px-3 py-2.5 text-left">
469
+ <SortHeader
470
+ label="SEO"
471
+ field="seo"
472
+ current={sortBy}
473
+ dir={dir}
474
+ onSort={handleSort}
475
+ />
476
+ </th>
477
+ </tr>
478
+ </thead>
479
+ <tbody>
480
+ {loading && pages.length === 0 && <LoadingRows />}
481
+ {!loading && error && (
482
+ <tr>
483
+ <td colSpan={7} className="px-3 py-12 text-center">
484
+ <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
485
+ <button
486
+ onClick={() => setRefreshToken((t) => t + 1)}
487
+ className="text-primary text-sm hover:underline"
488
+ >
489
+ Try again
490
+ </button>
491
+ </td>
492
+ </tr>
493
+ )}
494
+ {!loading && !error && pages.length === 0 && (
495
+ <EmptyRow
496
+ filtersActive={filtersAreActive}
497
+ onClear={clearFilters}
498
+ onNewPage={() => onNavigate('/pages/new')}
468
499
  />
469
- </th>
470
- </tr>
471
- </thead>
472
- <tbody>
473
- {loading && pages.length === 0 && <LoadingRows />}
474
- {!loading && error && (
475
- <tr>
476
- <td colSpan={7} className="px-3 py-12 text-center">
477
- <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
478
- <button
479
- onClick={() => setRefreshToken((t) => t + 1)}
480
- className="text-primary text-sm hover:underline"
500
+ )}
501
+ {!loading &&
502
+ !error &&
503
+ displayGroups.map((group) => (
504
+ <GroupBlock
505
+ key={group.key}
506
+ showHeader={grouped}
507
+ label={group.label}
508
+ color={group.color}
509
+ count={group.pages.length}
481
510
  >
482
- Try again
483
- </button>
484
- </td>
485
- </tr>
486
- )}
487
- {!loading && !error && pages.length === 0 && (
488
- <EmptyRow
489
- filtersActive={filtersAreActive}
490
- onClear={clearFilters}
491
- onNewPage={() => onNavigate('/pages/new')}
492
- />
493
- )}
494
- {!loading &&
495
- !error &&
496
- displayGroups.map((group) => (
497
- <GroupBlock
498
- key={group.key}
499
- showHeader={grouped}
500
- label={group.label}
501
- color={group.color}
502
- count={group.pages.length}
503
- >
504
- {group.pages.map((p) => (
505
- <PageRow
506
- key={p.id}
507
- page={p}
508
- selected={selected.has(p.id)}
509
- resolveTag={(slug) => tagBySlug.get(slug)}
510
- onSelect={() => toggleOne(p.id)}
511
- onOpen={() => onNavigate(`/pages/${p.id}/edit`)}
512
- />
513
- ))}
514
- </GroupBlock>
515
- ))}
516
- </tbody>
517
- </table>
511
+ {group.pages.map((p) => (
512
+ <PageRow
513
+ key={p.id}
514
+ page={p}
515
+ selected={selected.has(p.id)}
516
+ resolveTag={(slug) => tagBySlug.get(slug)}
517
+ onSelect={() => toggleOne(p.id)}
518
+ onOpen={() => onNavigate(`/pages/${p.id}/edit`)}
519
+ />
520
+ ))}
521
+ </GroupBlock>
522
+ ))}
523
+ </tbody>
524
+ </table>
525
+ </div>
518
526
 
519
527
  {/* Pagination only for the flat (ungrouped) view. */}
520
528
  {!grouped && (
@@ -629,7 +637,7 @@ function GroupBlock({
629
637
  {showHeader && (
630
638
  <tr className="bg-muted/30 border-border border-b">
631
639
  <td colSpan={7} className="px-3 py-2">
632
- <span className="text-foreground inline-flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
640
+ <span className="text-foreground inline-flex items-center gap-2 text-xs font-medium tracking-wider uppercase">
633
641
  {color && <span aria-hidden className={`h-2 w-2 rounded-full ${dotFor(color)}`} />}
634
642
  {label}
635
643
  <span className="text-muted-foreground tabular-nums">{count}</span>
@@ -811,7 +819,7 @@ function PageRow({
811
819
  <div className="flex items-center gap-2">
812
820
  <span
813
821
  aria-hidden
814
- className="bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold"
822
+ className="bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-medium"
815
823
  >
816
824
  {page.authorInitials}
817
825
  </span>
@@ -279,7 +279,7 @@ export function Pages({ onNavigate }: PagesProps) {
279
279
  <FolderInput className="h-5 w-5 text-gray-600" />
280
280
  </button>
281
281
  <div>
282
- <h1 className="text-xl font-semibold text-gray-900 sm:text-2xl">Pages</h1>
282
+ <h1 className="text-xl font-medium text-gray-900 sm:text-2xl">Pages</h1>
283
283
  <p className="text-sm text-gray-500">
284
284
  {totalCount} page{totalCount !== 1 ? 's' : ''}
285
285
  </p>
@@ -257,118 +257,122 @@ export function PostsListView({ onNavigate }: PostsListViewProps) {
257
257
 
258
258
  {/* Table card */}
259
259
  <div className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
260
- <table className="w-full table-fixed" role="table" aria-label="Posts">
261
- <colgroup>
262
- <col style={{ width: '40px' }} />
263
- <col />
264
- <col style={{ width: '140px' }} />
265
- <col style={{ width: '180px' }} />
266
- <col style={{ width: '130px' }} />
267
- <col style={{ width: '120px' }} />
268
- <col style={{ width: '90px' }} />
269
- </colgroup>
270
- <thead className="border-border bg-muted/40 border-b">
271
- <tr className="text-muted-foreground text-xs">
272
- <th scope="col" className="px-3 py-2.5 text-left">
273
- <input
274
- type="checkbox"
275
- aria-label="Select all posts on this page"
276
- checked={allOnPageSelected}
277
- onChange={(e) => toggleAllOnPage(e.target.checked)}
278
- className="border-border accent-primary rounded"
279
- />
280
- </th>
281
- <th scope="col" className="px-3 py-2.5 text-left">
282
- <SortHeader
283
- label="Title"
284
- field="title"
285
- current={sortBy}
286
- dir={dir}
287
- onSort={handleSort}
288
- />
289
- </th>
290
- <th scope="col" className="px-3 py-2.5 text-left">
291
- <SortHeader
292
- label="Type"
293
- field="type"
294
- current={sortBy}
295
- dir={dir}
296
- onSort={handleSort}
297
- />
298
- </th>
299
- <th scope="col" className="px-3 py-2.5 text-left">
300
- <SortHeader
301
- label="Author"
302
- field="author"
303
- current={sortBy}
304
- dir={dir}
305
- onSort={handleSort}
306
- />
307
- </th>
308
- <th scope="col" className="px-3 py-2.5 text-left">
309
- <SortHeader
310
- label="Status"
311
- field="status"
312
- current={sortBy}
313
- dir={dir}
314
- onSort={handleSort}
315
- />
316
- </th>
317
- <th scope="col" className="px-3 py-2.5 text-left">
318
- <SortHeader
319
- label="Date"
320
- field="date"
321
- current={sortBy}
322
- dir={dir}
323
- onSort={handleSort}
324
- />
325
- </th>
326
- <th scope="col" className="px-3 py-2.5 text-left">
327
- <SortHeader
328
- label="SEO"
329
- field="seo"
330
- current={sortBy}
331
- dir={dir}
332
- onSort={handleSort}
333
- />
334
- </th>
335
- </tr>
336
- </thead>
337
- <tbody>
338
- {loading && posts.length === 0 && <LoadingRows />}
339
- {!loading && error && (
340
- <tr>
341
- <td colSpan={7} className="px-3 py-12 text-center">
342
- <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
343
- <button
344
- onClick={() => setRefreshToken((t) => t + 1)}
345
- className="text-primary text-sm hover:underline"
346
- >
347
- Try again
348
- </button>
349
- </td>
260
+ {/* Horizontal scroll on narrow viewports: the fixed columns keep their
261
+ width and the table scrolls sideways instead of crushing content. */}
262
+ <div className="overflow-x-auto">
263
+ <table className="w-full min-w-[760px] table-fixed" role="table" aria-label="Posts">
264
+ <colgroup>
265
+ <col style={{ width: '40px' }} />
266
+ <col />
267
+ <col style={{ width: '140px' }} />
268
+ <col style={{ width: '180px' }} />
269
+ <col style={{ width: '130px' }} />
270
+ <col style={{ width: '120px' }} />
271
+ <col style={{ width: '90px' }} />
272
+ </colgroup>
273
+ <thead className="border-border bg-muted/40 border-b">
274
+ <tr className="text-muted-foreground text-xs">
275
+ <th scope="col" className="px-3 py-2.5 text-left">
276
+ <input
277
+ type="checkbox"
278
+ aria-label="Select all posts on this page"
279
+ checked={allOnPageSelected}
280
+ onChange={(e) => toggleAllOnPage(e.target.checked)}
281
+ className="border-border accent-primary rounded"
282
+ />
283
+ </th>
284
+ <th scope="col" className="px-3 py-2.5 text-left">
285
+ <SortHeader
286
+ label="Title"
287
+ field="title"
288
+ current={sortBy}
289
+ dir={dir}
290
+ onSort={handleSort}
291
+ />
292
+ </th>
293
+ <th scope="col" className="px-3 py-2.5 text-left">
294
+ <SortHeader
295
+ label="Type"
296
+ field="type"
297
+ current={sortBy}
298
+ dir={dir}
299
+ onSort={handleSort}
300
+ />
301
+ </th>
302
+ <th scope="col" className="px-3 py-2.5 text-left">
303
+ <SortHeader
304
+ label="Author"
305
+ field="author"
306
+ current={sortBy}
307
+ dir={dir}
308
+ onSort={handleSort}
309
+ />
310
+ </th>
311
+ <th scope="col" className="px-3 py-2.5 text-left">
312
+ <SortHeader
313
+ label="Status"
314
+ field="status"
315
+ current={sortBy}
316
+ dir={dir}
317
+ onSort={handleSort}
318
+ />
319
+ </th>
320
+ <th scope="col" className="px-3 py-2.5 text-left">
321
+ <SortHeader
322
+ label="Date"
323
+ field="date"
324
+ current={sortBy}
325
+ dir={dir}
326
+ onSort={handleSort}
327
+ />
328
+ </th>
329
+ <th scope="col" className="px-3 py-2.5 text-left">
330
+ <SortHeader
331
+ label="SEO"
332
+ field="seo"
333
+ current={sortBy}
334
+ dir={dir}
335
+ onSort={handleSort}
336
+ />
337
+ </th>
350
338
  </tr>
351
- )}
352
- {!loading && !error && posts.length === 0 && (
353
- <EmptyRow
354
- filtersActive={filtersAreActive}
355
- onClear={clearFilters}
356
- onNewPost={() => onNavigate('/posts/new')}
357
- />
358
- )}
359
- {!loading &&
360
- !error &&
361
- posts.map((p) => (
362
- <PostRow
363
- key={p.id}
364
- post={p}
365
- selected={selected.has(p.id)}
366
- onSelect={() => toggleOne(p.id)}
367
- onOpen={() => onNavigate(`/posts/${p.postTypeId}/${p.id}/edit`)}
339
+ </thead>
340
+ <tbody>
341
+ {loading && posts.length === 0 && <LoadingRows />}
342
+ {!loading && error && (
343
+ <tr>
344
+ <td colSpan={7} className="px-3 py-12 text-center">
345
+ <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
346
+ <button
347
+ onClick={() => setRefreshToken((t) => t + 1)}
348
+ className="text-primary text-sm hover:underline"
349
+ >
350
+ Try again
351
+ </button>
352
+ </td>
353
+ </tr>
354
+ )}
355
+ {!loading && !error && posts.length === 0 && (
356
+ <EmptyRow
357
+ filtersActive={filtersAreActive}
358
+ onClear={clearFilters}
359
+ onNewPost={() => onNavigate('/posts/new')}
368
360
  />
369
- ))}
370
- </tbody>
371
- </table>
361
+ )}
362
+ {!loading &&
363
+ !error &&
364
+ posts.map((p) => (
365
+ <PostRow
366
+ key={p.id}
367
+ post={p}
368
+ selected={selected.has(p.id)}
369
+ onSelect={() => toggleOne(p.id)}
370
+ onOpen={() => onNavigate(`/posts/${p.postTypeId}/${p.id}/edit`)}
371
+ />
372
+ ))}
373
+ </tbody>
374
+ </table>
375
+ </div>
372
376
 
373
377
  {/* Pagination footer */}
374
378
  <PaginationFooter
@@ -707,7 +711,7 @@ function PostRow({
707
711
  <div className="flex items-center gap-2">
708
712
  <span
709
713
  aria-hidden
710
- className="bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold"
714
+ className="bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-medium"
711
715
  >
712
716
  {post.authorInitials}
713
717
  </span>