@actuate-media/cms-admin 0.18.0 → 0.20.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 (167) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +9 -5
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/lib/post-editor-service.test.js +75 -23
  5. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  6. package/dist/__tests__/lib/posts-service.test.js +132 -142
  7. package/dist/__tests__/lib/posts-service.test.js.map +1 -1
  8. package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
  9. package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
  10. package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
  11. package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
  12. package/dist/actuate-admin.css +1 -1
  13. package/dist/components/seo/Drawer.d.ts +11 -0
  14. package/dist/components/seo/Drawer.d.ts.map +1 -0
  15. package/dist/components/seo/Drawer.js +13 -0
  16. package/dist/components/seo/Drawer.js.map +1 -0
  17. package/dist/components/seo/RedirectEditorDrawer.d.ts +9 -0
  18. package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -0
  19. package/dist/components/seo/RedirectEditorDrawer.js +94 -0
  20. package/dist/components/seo/RedirectEditorDrawer.js.map +1 -0
  21. package/dist/components/seo/SeoCopilotDrawer.d.ts +10 -0
  22. package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -0
  23. package/dist/components/seo/SeoCopilotDrawer.js +34 -0
  24. package/dist/components/seo/SeoCopilotDrawer.js.map +1 -0
  25. package/dist/components/seo/SeoEditorDrawer.d.ts +8 -0
  26. package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -0
  27. package/dist/components/seo/SeoEditorDrawer.js +111 -0
  28. package/dist/components/seo/SeoEditorDrawer.js.map +1 -0
  29. package/dist/components/seo/SeoIssueFixDrawer.d.ts +8 -0
  30. package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -0
  31. package/dist/components/seo/SeoIssueFixDrawer.js +111 -0
  32. package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -0
  33. package/dist/components/seo/primitives.d.ts +53 -0
  34. package/dist/components/seo/primitives.d.ts.map +1 -0
  35. package/dist/components/seo/primitives.js +100 -0
  36. package/dist/components/seo/primitives.js.map +1 -0
  37. package/dist/index.d.ts +0 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +0 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/layout/Sidebar.d.ts.map +1 -1
  42. package/dist/layout/Sidebar.js +3 -0
  43. package/dist/layout/Sidebar.js.map +1 -1
  44. package/dist/lib/post-editor-service.d.ts +6 -4
  45. package/dist/lib/post-editor-service.d.ts.map +1 -1
  46. package/dist/lib/post-editor-service.js +32 -37
  47. package/dist/lib/post-editor-service.js.map +1 -1
  48. package/dist/lib/posts-service.d.ts.map +1 -1
  49. package/dist/lib/posts-service.js +91 -160
  50. package/dist/lib/posts-service.js.map +1 -1
  51. package/dist/lib/seo-service.d.ts +322 -0
  52. package/dist/lib/seo-service.d.ts.map +1 -0
  53. package/dist/lib/seo-service.js +295 -0
  54. package/dist/lib/seo-service.js.map +1 -0
  55. package/dist/router/index.d.ts.map +1 -1
  56. package/dist/router/index.js +4 -1
  57. package/dist/router/index.js.map +1 -1
  58. package/dist/views/MediaBrowser.d.ts.map +1 -1
  59. package/dist/views/MediaBrowser.js +36 -25
  60. package/dist/views/MediaBrowser.js.map +1 -1
  61. package/dist/views/Posts/NewPostPicker.d.ts +5 -4
  62. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
  63. package/dist/views/Posts/NewPostPicker.js +5 -4
  64. package/dist/views/Posts/NewPostPicker.js.map +1 -1
  65. package/dist/views/Posts/NewPostTypeInstructions.js +2 -2
  66. package/dist/views/Posts/PostTypesView.d.ts +5 -4
  67. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  68. package/dist/views/Posts/PostTypesView.js.map +1 -1
  69. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  70. package/dist/views/Posts/PostsListView.js +8 -0
  71. package/dist/views/Posts/PostsListView.js.map +1 -1
  72. package/dist/views/Posts/post-type-presets.d.ts +5 -0
  73. package/dist/views/Posts/post-type-presets.d.ts.map +1 -1
  74. package/dist/views/Posts/post-type-presets.js +10 -64
  75. package/dist/views/Posts/post-type-presets.js.map +1 -1
  76. package/dist/views/SEO.d.ts.map +1 -1
  77. package/dist/views/SEO.js +89 -132
  78. package/dist/views/SEO.js.map +1 -1
  79. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
  80. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
  81. package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
  82. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
  83. package/dist/views/post-editor/PostEditorCanvas.js +7 -1
  84. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  85. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  86. package/dist/views/post-editor/PostFieldsModal.js +10 -4
  87. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  88. package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
  89. package/dist/views/post-editor/PostHeader.js +4 -3
  90. package/dist/views/post-editor/PostHeader.js.map +1 -1
  91. package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
  92. package/dist/views/post-editor/PostPreview.js +11 -1
  93. package/dist/views/post-editor/PostPreview.js.map +1 -1
  94. package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
  95. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  96. package/dist/views/post-editor/PostSectionEditor.js +4 -1
  97. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  98. package/dist/views/seo/AuditTab.d.ts +6 -0
  99. package/dist/views/seo/AuditTab.d.ts.map +1 -0
  100. package/dist/views/seo/AuditTab.js +57 -0
  101. package/dist/views/seo/AuditTab.js.map +1 -0
  102. package/dist/views/seo/ContentTab.d.ts +5 -0
  103. package/dist/views/seo/ContentTab.d.ts.map +1 -0
  104. package/dist/views/seo/ContentTab.js +27 -0
  105. package/dist/views/seo/ContentTab.js.map +1 -0
  106. package/dist/views/seo/OverviewTab.d.ts +8 -0
  107. package/dist/views/seo/OverviewTab.d.ts.map +1 -0
  108. package/dist/views/seo/OverviewTab.js +35 -0
  109. package/dist/views/seo/OverviewTab.js.map +1 -0
  110. package/dist/views/seo/RedirectsTab.d.ts +2 -0
  111. package/dist/views/seo/RedirectsTab.d.ts.map +1 -0
  112. package/dist/views/seo/RedirectsTab.js +124 -0
  113. package/dist/views/seo/RedirectsTab.js.map +1 -0
  114. package/dist/views/seo/TechnicalTab.d.ts +2 -0
  115. package/dist/views/seo/TechnicalTab.d.ts.map +1 -0
  116. package/dist/views/seo/TechnicalTab.js +102 -0
  117. package/dist/views/seo/TechnicalTab.js.map +1 -0
  118. package/dist/views/seo/useSeoResource.d.ts +8 -0
  119. package/dist/views/seo/useSeoResource.d.ts.map +1 -0
  120. package/dist/views/seo/useSeoResource.js +42 -0
  121. package/dist/views/seo/useSeoResource.js.map +1 -0
  122. package/package.json +2 -2
  123. package/src/AdminRoot.tsx +9 -5
  124. package/src/__tests__/lib/post-editor-service.test.ts +78 -23
  125. package/src/__tests__/lib/posts-service.test.ts +134 -163
  126. package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
  127. package/src/components/seo/Drawer.tsx +61 -0
  128. package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
  129. package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
  130. package/src/components/seo/SeoEditorDrawer.tsx +347 -0
  131. package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
  132. package/src/components/seo/primitives.tsx +237 -0
  133. package/src/index.ts +0 -2
  134. package/src/layout/Sidebar.tsx +3 -0
  135. package/src/lib/post-editor-service.ts +41 -40
  136. package/src/lib/posts-service.ts +109 -180
  137. package/src/lib/seo-service.ts +623 -0
  138. package/src/router/index.ts +4 -1
  139. package/src/views/MediaBrowser.tsx +89 -67
  140. package/src/views/Posts/NewPostPicker.tsx +5 -4
  141. package/src/views/Posts/NewPostTypeInstructions.tsx +2 -2
  142. package/src/views/Posts/PostTypesView.tsx +5 -4
  143. package/src/views/Posts/PostsListView.tsx +9 -0
  144. package/src/views/Posts/post-type-presets.ts +15 -64
  145. package/src/views/SEO.tsx +186 -863
  146. package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
  147. package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
  148. package/src/views/post-editor/PostFieldsModal.tsx +36 -21
  149. package/src/views/post-editor/PostHeader.tsx +4 -4
  150. package/src/views/post-editor/PostPreview.tsx +12 -1
  151. package/src/views/post-editor/PostSectionEditor.tsx +22 -5
  152. package/src/views/seo/AuditTab.tsx +229 -0
  153. package/src/views/seo/ContentTab.tsx +164 -0
  154. package/src/views/seo/OverviewTab.tsx +257 -0
  155. package/src/views/seo/RedirectsTab.tsx +392 -0
  156. package/src/views/seo/TechnicalTab.tsx +303 -0
  157. package/src/views/seo/useSeoResource.ts +52 -0
  158. package/dist/views/PostEditor.d.ts +0 -6
  159. package/dist/views/PostEditor.d.ts.map +0 -1
  160. package/dist/views/PostEditor.js +0 -79
  161. package/dist/views/PostEditor.js.map +0 -1
  162. package/dist/views/Posts.d.ts +0 -5
  163. package/dist/views/Posts.d.ts.map +0 -1
  164. package/dist/views/Posts.js +0 -73
  165. package/dist/views/Posts.js.map +0 -1
  166. package/src/views/PostEditor.tsx +0 -257
  167. package/src/views/Posts.tsx +0 -375
@@ -0,0 +1,124 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef, useState } from 'react';
4
+ import { Plus, Pencil, Trash2, Upload, Download, Sparkles, ArrowRight, GitBranch, Check, X, Link2, } from 'lucide-react';
5
+ import { toast } from 'sonner';
6
+ import { fetchRedirectOverview, fetchRedirectRules, fetchRedirectChains, fetchRedirectSuggestions, deleteRedirectRule, flattenRedirectChain, generate404RecoverySuggestions, acceptRedirectSuggestion, dismissRedirectSuggestion, exportRedirectsCsv, importRedirectsCsv, } from '../../lib/seo-service.js';
7
+ import { SectionCard, SeoLoading, SeoEmptyState, SeoErrorState, SeoStatusBadge, btnPrimary, btnSecondary, btnGhostIcon, } from '../../components/seo/primitives.js';
8
+ import { RedirectEditorDrawer } from '../../components/seo/RedirectEditorDrawer.js';
9
+ import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
10
+ import { useSeoResource } from './useSeoResource.js';
11
+ function OverviewCards({ onRefetchKey }) {
12
+ const { data, loading, error, refetch } = useSeoResource(fetchRedirectOverview, [onRefetchKey]);
13
+ if (loading)
14
+ return _jsx(SeoLoading, { label: "Loading redirect overview\u2026" });
15
+ if (error)
16
+ return _jsx(SeoErrorState, { message: error, onRetry: refetch });
17
+ if (!data)
18
+ return null;
19
+ const cards = [
20
+ { label: 'Active redirects', value: data.activeRedirects },
21
+ { label: 'Stale redirects', value: data.staleRedirects },
22
+ { label: '404s tracked', value: data.notFoundCount },
23
+ { label: 'Chains & loops', value: data.chainsAndLoops },
24
+ ];
25
+ return (_jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: cards.map((c) => (_jsxs("div", { className: "border-border bg-card rounded-lg border p-4", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: c.label }), _jsx("p", { className: "text-foreground mt-1 text-2xl font-medium tabular-nums", children: c.value })] }, c.label))) }));
26
+ }
27
+ function Ai404RecoveryCard({ onApplied }) {
28
+ const { data, loading, error, refetch } = useSeoResource(fetchRedirectSuggestions, []);
29
+ const [generating, setGenerating] = useState(false);
30
+ async function handleGenerate() {
31
+ setGenerating(true);
32
+ const res = await generate404RecoverySuggestions();
33
+ setGenerating(false);
34
+ if (res.error) {
35
+ toast.error(res.error);
36
+ return;
37
+ }
38
+ toast.success('Generated 404 recovery suggestions.');
39
+ refetch();
40
+ }
41
+ async function handleAccept(id) {
42
+ const res = await acceptRedirectSuggestion(id);
43
+ if (res.error)
44
+ return toast.error(res.error);
45
+ toast.success('Redirect created from suggestion.');
46
+ refetch();
47
+ onApplied();
48
+ }
49
+ async function handleDismiss(id) {
50
+ const res = await dismissRedirectSuggestion(id);
51
+ if (res.error)
52
+ return toast.error(res.error);
53
+ refetch();
54
+ }
55
+ return (_jsx(SectionCard, { title: "AI 404 Recovery", description: "Suggested redirects for paths returning 404s", action: _jsxs("button", { className: btnSecondary, onClick: handleGenerate, disabled: generating, children: [_jsx(Sparkles, { className: `h-4 w-4 ${generating ? 'animate-pulse' : ''}`, "aria-hidden": true }), generating ? 'Analyzing…' : 'Generate suggestions'] }), children: loading ? (_jsx(SeoLoading, {})) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (data ?? []).length === 0 ? (_jsx(SeoEmptyState, { icon: _jsx(Sparkles, { size: 24 }), title: "No suggestions", description: "Generate suggestions to recover traffic from broken URLs." })) : (_jsx("ul", { className: "divide-border divide-y", children: data.map((s) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-3 first:pt-0", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "text-foreground flex items-center gap-2 text-sm", children: [_jsx("span", { className: "truncate font-medium", children: s.fromPath }), _jsx(ArrowRight, { className: "text-muted-foreground h-3.5 w-3.5 shrink-0", "aria-hidden": true }), _jsx("span", { className: "text-muted-foreground truncate", children: s.toPath })] }), s.reason && _jsx("p", { className: "text-muted-foreground mt-0.5 text-sm", children: s.reason })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(SeoStatusBadge, { label: `${Math.round(s.confidence * 100)}%`, tone: s.confidence >= 0.7 ? 'good' : s.confidence >= 0.4 ? 'fair' : 'poor' }), _jsx("button", { onClick: () => handleAccept(s.id), className: "text-success hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none", "aria-label": `Accept redirect from ${s.fromPath}`, children: _jsx(Check, { className: "h-4 w-4", "aria-hidden": true }) }), _jsx("button", { onClick: () => handleDismiss(s.id), className: "text-muted-foreground hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none", "aria-label": `Dismiss suggestion for ${s.fromPath}`, children: _jsx(X, { className: "h-4 w-4", "aria-hidden": true }) })] })] }, s.id))) })) }));
56
+ }
57
+ function ChainMapCard({ onFlattened }) {
58
+ const { data, loading, error, refetch } = useSeoResource(fetchRedirectChains, []);
59
+ async function handleFlatten(id) {
60
+ const res = await flattenRedirectChain(id);
61
+ if (res.error)
62
+ return toast.error(res.error);
63
+ toast.success('Chain flattened.');
64
+ refetch();
65
+ onFlattened();
66
+ }
67
+ if (loading)
68
+ return null;
69
+ if (error)
70
+ return null;
71
+ if (!data || data.length === 0)
72
+ return null;
73
+ return (_jsx(SectionCard, { title: "Redirect Chains & Loops", description: "Multi-hop redirects slow crawlers and lose link equity", children: _jsx("ul", { className: "space-y-3", children: data.map((chain) => (_jsxs("li", { className: "border-border flex items-center justify-between gap-3 rounded-md border p-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(GitBranch, { className: "text-muted-foreground h-4 w-4", "aria-hidden": true }), _jsx(SeoStatusBadge, { label: chain.kind === 'loop' ? 'Loop' : 'Chain', tone: chain.kind === 'loop' ? 'critical' : 'fair' })] }), _jsx("p", { className: "text-foreground flex flex-wrap items-center gap-1 text-sm", children: chain.hops.map((hop, i) => (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "truncate", children: hop }), i < chain.hops.length - 1 && (_jsx(ArrowRight, { className: "text-muted-foreground h-3 w-3", "aria-hidden": true }))] }, i))) })] }), chain.kind === 'chain' && (_jsx("button", { className: btnSecondary, onClick: () => handleFlatten(chain.id), children: "Flatten" }))] }, chain.id))) }) }));
74
+ }
75
+ export function RedirectsTab() {
76
+ const [refetchKey, setRefetchKey] = useState(0);
77
+ const { data, loading, error, refetch } = useSeoResource(fetchRedirectRules, [refetchKey]);
78
+ const [editor, setEditor] = useState(null);
79
+ const [confirmDelete, setConfirmDelete] = useState(null);
80
+ const fileRef = useRef(null);
81
+ const bump = () => setRefetchKey((k) => k + 1);
82
+ const refetchAll = () => {
83
+ refetch();
84
+ bump();
85
+ };
86
+ async function handleDelete(rule) {
87
+ const res = await deleteRedirectRule(rule.id);
88
+ if (res.error)
89
+ return toast.error(res.error);
90
+ toast.success('Redirect deleted.');
91
+ refetchAll();
92
+ }
93
+ async function handleExport() {
94
+ const res = await exportRedirectsCsv();
95
+ if (res.error || !res.csv)
96
+ return toast.error(res.error ?? 'Export failed.');
97
+ const blob = new Blob([res.csv], { type: 'text/csv' });
98
+ const link = document.createElement('a');
99
+ link.href = URL.createObjectURL(blob);
100
+ link.download = 'redirects.csv';
101
+ link.click();
102
+ URL.revokeObjectURL(link.href);
103
+ }
104
+ async function handleImportFile(file) {
105
+ const csv = await file.text();
106
+ const res = await importRedirectsCsv(csv);
107
+ if (res.error)
108
+ return toast.error(res.error);
109
+ if (res.errors && res.errors.length > 0) {
110
+ toast.error(`Imported ${res.imported ?? 0}, ${res.errors.length} row(s) skipped.`);
111
+ }
112
+ else {
113
+ toast.success(`Imported ${res.imported ?? 0} redirect(s).`);
114
+ }
115
+ refetchAll();
116
+ }
117
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(OverviewCards, { onRefetchKey: refetchKey }), _jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [_jsx(Ai404RecoveryCard, { onApplied: refetchAll }), _jsx(ChainMapCard, { onFlattened: refetchAll })] }), _jsx(SectionCard, { title: "Redirect Rules", action: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("input", { ref: fileRef, type: "file", accept: ".csv,text/csv", className: "sr-only", "aria-hidden": true, tabIndex: -1, onChange: (e) => {
118
+ const f = e.target.files?.[0];
119
+ if (f)
120
+ handleImportFile(f);
121
+ e.target.value = '';
122
+ } }), _jsxs("button", { className: btnSecondary, onClick: () => fileRef.current?.click(), children: [_jsx(Upload, { className: "h-4 w-4", "aria-hidden": true }), " Import"] }), _jsxs("button", { className: btnSecondary, onClick: handleExport, children: [_jsx(Download, { className: "h-4 w-4", "aria-hidden": true }), " Export"] }), _jsxs("button", { className: btnPrimary, onClick: () => setEditor({ rule: null }), children: [_jsx(Plus, { className: "h-4 w-4", "aria-hidden": true }), " New redirect"] })] }), children: loading ? (_jsx(SeoLoading, { label: "Loading redirects\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (data ?? []).length === 0 ? (_jsx(SeoEmptyState, { icon: _jsx(Link2, { size: 24 }), title: "No redirects yet", description: "Create a redirect or import a CSV to get started.", action: _jsxs("button", { className: btnPrimary, onClick: () => setEditor({ rule: null }), children: [_jsx(Plus, { className: "h-4 w-4", "aria-hidden": true }), " New redirect"] }) })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", "aria-label": "Redirect rules", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-muted-foreground border-border border-b text-left", children: [_jsx("th", { className: "py-2 pr-4 font-medium", children: "Source" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Destination" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Type" }), _jsx("th", { className: "py-2 pr-4 text-right font-medium", children: "Hits" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Status" }), _jsx("th", { className: "py-2 text-right font-medium", children: _jsx("span", { className: "sr-only", children: "Actions" }) })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: data.map((r) => (_jsxs("tr", { className: "hover:bg-accent/40", children: [_jsx("td", { className: "text-foreground py-2.5 pr-4 font-mono", children: r.fromPath }), _jsx("td", { className: "text-muted-foreground py-2.5 pr-4 font-mono", children: r.type === 410 ? _jsx("span", { className: "italic", children: "\u2014 (gone)" }) : r.toPath }), _jsx("td", { className: "text-foreground py-2.5 pr-4 tabular-nums", children: r.type }), _jsx("td", { className: "text-foreground py-2.5 pr-4 text-right tabular-nums", children: r.hits }), _jsx("td", { className: "py-2.5 pr-4", children: _jsxs("span", { className: "flex flex-wrap gap-1.5", children: [_jsx(SeoStatusBadge, { label: r.enabled ? 'Enabled' : 'Disabled', tone: r.enabled ? 'good' : 'neutral' }), r.stale && _jsx(SeoStatusBadge, { label: "Stale", tone: "fair" })] }) }), _jsx("td", { className: "py-2.5 text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx("button", { className: btnGhostIcon, onClick: () => setEditor({ rule: r }), "aria-label": `Edit redirect ${r.fromPath}`, children: _jsx(Pencil, { className: "h-4 w-4", "aria-hidden": true }) }), _jsx("button", { className: btnGhostIcon, onClick: () => setConfirmDelete(r), "aria-label": `Delete redirect ${r.fromPath}`, children: _jsx(Trash2, { className: "h-4 w-4", "aria-hidden": true }) })] }) })] }, r.id))) })] }) })) }), _jsx(RedirectEditorDrawer, { open: editor !== null, onOpenChange: (o) => !o && setEditor(null), rule: editor?.rule ?? null, onSaved: refetchAll }), _jsx(ConfirmDialog, { open: confirmDelete !== null, onClose: () => setConfirmDelete(null), onConfirm: () => confirmDelete && handleDelete(confirmDelete), title: "Delete redirect", description: `Delete the redirect from "${confirmDelete?.fromPath}"? This cannot be undone.`, confirmLabel: "Delete", destructive: true })] }));
123
+ }
124
+ //# sourceMappingURL=RedirectsTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedirectsTab.js","sourceRoot":"","sources":["../../../src/views/seo/RedirectsTab.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function TechnicalTab(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=TechnicalTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TechnicalTab.d.ts","sourceRoot":"","sources":["../../../src/views/seo/TechnicalTab.tsx"],"names":[],"mappings":"AAoSA,wBAAgB,YAAY,4CAU3B"}
@@ -0,0 +1,102 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { RefreshCw, Map, AlertTriangle, CheckCircle2, ExternalLink } from 'lucide-react';
5
+ import { toast } from 'sonner';
6
+ import { fetchSitemapStatus, regenerateSitemap, fetchCrawlSettings, updateCrawlSettings, fetchRobotsTxt, updateRobotsTxt, validateRobotsTxt, } from '../../lib/seo-service.js';
7
+ import { SectionCard, SeoLoading, SeoErrorState, btnPrimary, btnSecondary, } from '../../components/seo/primitives.js';
8
+ import { useSeoResource } from './useSeoResource.js';
9
+ const CRAWL_TOGGLES = [
10
+ {
11
+ key: 'indexEntireSite',
12
+ label: 'Index entire site',
13
+ help: 'Allow search engines to index all public pages.',
14
+ },
15
+ {
16
+ key: 'includeImagesInSitemap',
17
+ label: 'Include images in sitemap',
18
+ help: 'Add image entries to the XML sitemap.',
19
+ },
20
+ {
21
+ key: 'includePostsInSitemap',
22
+ label: 'Include posts in sitemap',
23
+ help: 'Add published posts to the sitemap.',
24
+ },
25
+ {
26
+ key: 'noindexPaginatedArchives',
27
+ label: 'No-index paginated archives',
28
+ help: 'Avoid duplicate-content penalties on page 2+.',
29
+ },
30
+ {
31
+ key: 'noindexTagPages',
32
+ label: 'No-index tag pages',
33
+ help: 'Keep thin tag archives out of the index.',
34
+ },
35
+ ];
36
+ function Toggle({ checked, onChange, label, help, }) {
37
+ return (_jsxs("label", { className: "flex items-center justify-between gap-3 py-2.5", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: label }), _jsx("span", { className: "text-muted-foreground block text-sm", children: help })] }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": checked, checked: checked, onChange: (e) => onChange(e.target.checked), className: "accent-primary h-4 w-4 shrink-0" })] }));
38
+ }
39
+ function SitemapCard() {
40
+ const { data, loading, error, refetch } = useSeoResource(fetchSitemapStatus, []);
41
+ const [busy, setBusy] = useState(false);
42
+ async function handleRegenerate() {
43
+ setBusy(true);
44
+ const res = await regenerateSitemap();
45
+ setBusy(false);
46
+ if (res.error) {
47
+ toast.error(res.error);
48
+ return;
49
+ }
50
+ toast.success('Sitemap regenerated.');
51
+ refetch();
52
+ }
53
+ return (_jsx(SectionCard, { title: "Sitemap", action: _jsxs("button", { className: btnSecondary, onClick: handleRegenerate, disabled: busy || loading, children: [_jsx(RefreshCw, { className: `h-4 w-4 ${busy ? 'animate-spin' : ''}`, "aria-hidden": true }), "Regenerate"] }), children: loading ? (_jsx(SeoLoading, { label: "Loading sitemap status\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : !data ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Sitemap status unavailable." })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("a", { href: data.sitemapUrl, target: "_blank", rel: "noopener noreferrer", className: "text-primary inline-flex items-center gap-1 text-sm hover:underline", children: [_jsx(Map, { className: "h-4 w-4", "aria-hidden": true }), data.sitemapUrl, _jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true })] }), _jsxs("dl", { className: "grid grid-cols-2 gap-3 text-sm", children: [_jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Pages included" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: data.pagesIncluded })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Pages excluded" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: data.pagesExcluded })] }), _jsxs("div", { className: "col-span-2", children: [_jsx("dt", { className: "text-muted-foreground", children: "Last generated" }), _jsx("dd", { className: "text-foreground", children: data.lastGeneratedAt ? new Date(data.lastGeneratedAt).toLocaleString() : 'Never' })] })] }), data.warnings.length > 0 && (_jsx("ul", { className: "space-y-1", children: data.warnings.map((w, i) => (_jsxs("li", { className: "text-warning flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", w] }, i))) }))] })) }));
54
+ }
55
+ function CrawlCard() {
56
+ const { data, loading, error, refetch } = useSeoResource(fetchCrawlSettings, []);
57
+ const [settings, setSettings] = useState(null);
58
+ const [saving, setSaving] = useState(false);
59
+ useEffect(() => {
60
+ if (data)
61
+ setSettings(data);
62
+ }, [data]);
63
+ async function save(next, changed) {
64
+ setSettings(next);
65
+ setSaving(true);
66
+ const res = await updateCrawlSettings({ [changed]: next[changed] });
67
+ setSaving(false);
68
+ if (res.error) {
69
+ toast.error(res.error);
70
+ refetch();
71
+ }
72
+ }
73
+ return (_jsx(SectionCard, { title: "Crawl Settings", children: loading ? (_jsx(SeoLoading, { label: "Loading crawl settings\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : !settings ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Crawl settings unavailable." })) : (_jsx("div", { className: "divide-border divide-y", "aria-busy": saving, children: CRAWL_TOGGLES.map((t) => (_jsx(Toggle, { label: t.label, help: t.help, checked: settings[t.key], onChange: (v) => save({ ...settings, [t.key]: v }, t.key) }, t.key))) })) }));
74
+ }
75
+ function RobotsCard() {
76
+ const { data, loading, error, refetch } = useSeoResource(fetchRobotsTxt, []);
77
+ const [body, setBody] = useState('');
78
+ const [saving, setSaving] = useState(false);
79
+ useEffect(() => {
80
+ if (data)
81
+ setBody(data.body);
82
+ }, [data]);
83
+ const validation = validateRobotsTxt(body);
84
+ async function handleSave() {
85
+ setSaving(true);
86
+ const res = await updateRobotsTxt(body);
87
+ setSaving(false);
88
+ if (res.error) {
89
+ toast.error(res.error);
90
+ return;
91
+ }
92
+ toast.success('robots.txt saved.');
93
+ refetch();
94
+ }
95
+ return (_jsx(SectionCard, { title: "robots.txt", description: "Controls which crawlers can access your site.", action: _jsx("button", { className: btnPrimary, onClick: handleSave, disabled: saving || loading || !validation.valid, children: saving ? 'Saving…' : 'Save' }), children: loading ? (_jsx(SeoLoading, { label: "Loading robots.txt\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (_jsxs("div", { className: "space-y-3", children: [validation.blocksEntireSite && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 flex items-start gap-2 rounded-md border p-3", role: "alert", children: [_jsx(AlertTriangle, { className: "text-destructive mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("p", { className: "text-destructive text-sm", children: "This configuration blocks your entire site from all search engines. Pages will be removed from search results." })] })), _jsx("label", { className: "sr-only", htmlFor: "robots-editor", children: "robots.txt contents" }), _jsx("textarea", { id: "robots-editor", value: body, onChange: (e) => setBody(e.target.value), spellCheck: false, className: "border-border bg-input-background text-foreground focus-visible:ring-ring min-h-48 w-full rounded-md border p-3 font-mono text-sm focus-visible:ring-2 focus-visible:outline-none" }), validation.errors.length > 0 ? (_jsx("ul", { className: "space-y-1", children: validation.errors.map((e, i) => (_jsxs("li", { className: "text-destructive flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", e] }, i))) })) : (_jsxs("p", { className: "text-success flex items-center gap-1.5 text-sm", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5", "aria-hidden": true }), " Valid robots.txt"] })), validation.warnings
96
+ .filter((w) => !validation.blocksEntireSite || !w.includes('blocks the entire site'))
97
+ .map((w, i) => (_jsxs("p", { className: "text-warning flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", w] }, i)))] })) }));
98
+ }
99
+ export function TechnicalTab() {
100
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-2", children: [_jsx(SitemapCard, {}), _jsx(CrawlCard, {})] }), _jsx(RobotsCard, {})] }));
101
+ }
102
+ //# sourceMappingURL=TechnicalTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TechnicalTab.js","sourceRoot":"","sources":["../../../src/views/seo/TechnicalTab.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface SeoResource<T> {
2
+ data: T | null;
3
+ loading: boolean;
4
+ error: string | null;
5
+ refetch: () => void;
6
+ }
7
+ export declare function useSeoResource<T>(fetcher: () => Promise<T>, deps?: ReadonlyArray<unknown>): SeoResource<T>;
8
+ //# sourceMappingURL=useSeoResource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSeoResource.d.ts","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,IAAI,GAAE,aAAa,CAAC,OAAO,CAAM,GAChC,WAAW,CAAC,CAAC,CAAC,CAgChB"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ /**
3
+ * Small async-resource hook for the SEO tabs: wraps a service fetcher with
4
+ * loading / error / data / refetch and abort-on-unmount safety. Keeps each
5
+ * tab declarative without re-implementing fetch plumbing.
6
+ */
7
+ import { useCallback, useEffect, useRef, useState } from 'react';
8
+ export function useSeoResource(fetcher, deps = []) {
9
+ const [data, setData] = useState(null);
10
+ const [loading, setLoading] = useState(true);
11
+ const [error, setError] = useState(null);
12
+ const aliveRef = useRef(true);
13
+ // eslint-disable-next-line react-hooks/exhaustive-deps
14
+ const stableFetcher = useCallback(fetcher, deps);
15
+ const load = useCallback(async () => {
16
+ setLoading(true);
17
+ setError(null);
18
+ try {
19
+ const result = await stableFetcher();
20
+ if (aliveRef.current)
21
+ setData(result);
22
+ }
23
+ catch (err) {
24
+ if (aliveRef.current) {
25
+ setError(err instanceof Error ? err.message : 'Failed to load data');
26
+ }
27
+ }
28
+ finally {
29
+ if (aliveRef.current)
30
+ setLoading(false);
31
+ }
32
+ }, [stableFetcher]);
33
+ useEffect(() => {
34
+ aliveRef.current = true;
35
+ load();
36
+ return () => {
37
+ aliveRef.current = false;
38
+ };
39
+ }, [load]);
40
+ return { data, loading, error, refetch: load };
41
+ }
42
+ //# sourceMappingURL=useSeoResource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSeoResource.js","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAShE,MAAM,UAAU,cAAc,CAC5B,OAAyB,EACzB,OAA+B,EAAE;IAEjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7B,uDAAuD;IACvD,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAEhD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAA;YACpC,IAAI,QAAQ,CAAC,OAAO;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAA;YACtE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,OAAO;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAA;QACzC,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACvB,IAAI,EAAE,CAAA;QACN,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;QAC1B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-admin",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/actuate-media/actuatecms.git",
@@ -87,7 +87,7 @@
87
87
  "tailwindcss": "^4.0.0",
88
88
  "typescript": "^5.7.0",
89
89
  "vitest": "^3.0.0",
90
- "@actuate-media/cms-core": "0.27.1",
90
+ "@actuate-media/cms-core": "0.29.0",
91
91
  "@actuate-media/component-blocks": "0.2.0"
92
92
  },
93
93
  "scripts": {
package/src/AdminRoot.tsx CHANGED
@@ -379,17 +379,21 @@ function AdminShell({
379
379
  return <Forms onNavigate={navigate} />
380
380
  }
381
381
 
382
+ // Legacy deep paths map onto the new five-tab IA.
382
383
  if (matchRoute('/seo/redirects')) {
383
384
  return <SEO onNavigate={navigate} initialTab="redirects" />
384
385
  }
385
- if (matchRoute('/seo/canonicals')) {
386
- return <SEO onNavigate={navigate} initialTab="canonicals" />
386
+ if (matchRoute('/seo/content') || matchRoute('/seo/canonicals')) {
387
+ return <SEO onNavigate={navigate} initialTab="content" />
387
388
  }
388
- if (matchRoute('/seo/links')) {
389
- return <SEO onNavigate={navigate} initialTab="links" />
389
+ if (matchRoute('/seo/technical') || matchRoute('/seo/links')) {
390
+ return <SEO onNavigate={navigate} initialTab="technical" />
391
+ }
392
+ if (matchRoute('/seo/audit')) {
393
+ return <SEO onNavigate={navigate} initialTab="audit" />
390
394
  }
391
395
  if (matchRoute('/seo')) {
392
- return <SEO onNavigate={navigate} initialTab="pages" />
396
+ return <SEO onNavigate={navigate} initialTab="overview" />
393
397
  }
394
398
 
395
399
  if (matchRoute('/script-tags/new')) {
@@ -135,26 +135,21 @@ describe('validateTemplate', () => {
135
135
 
136
136
  describe('emptyPostFromTemplate', () => {
137
137
  it('seeds sections from the type template with fresh ids', async () => {
138
- // fetchTemplateForEditor lists the post-templates collection.
138
+ // fetchTemplateForEditor reads the dedicated /post-templates/:type endpoint,
139
+ // whose `{ data }` envelope is unwrapped by cmsApi to { postType, docId, header, sections }.
139
140
  cmsApiMock.mockResolvedValueOnce({
140
141
  data: {
141
- docs: [
142
+ postType: 'blog',
143
+ docId: 'tmpl-1',
144
+ header: { layout: 'left' },
145
+ sections: [
142
146
  {
143
- id: 'tmpl-1',
144
- slug: 'blog',
145
- data: {
146
- header: { layout: 'left' },
147
- sections: [
148
- {
149
- id: 'orig',
150
- sectionType: 'article-body',
151
- name: 'Body',
152
- visible: true,
153
- content: {},
154
- settings: {},
155
- },
156
- ],
157
- },
147
+ id: 'orig',
148
+ sectionType: 'article-body',
149
+ name: 'Body',
150
+ visible: true,
151
+ content: {},
152
+ settings: {},
158
153
  },
159
154
  ],
160
155
  },
@@ -166,22 +161,82 @@ describe('emptyPostFromTemplate', () => {
166
161
  expect(post.sections[0]!.sectionType).toBe('article-body')
167
162
  // cloned: a fresh id, not the template's section id
168
163
  expect(post.sections[0]!.id).not.toBe('orig')
164
+ // the endpoint was addressed by post type, not the raw collection API
165
+ expect(cmsApiMock.mock.calls[0]![0]).toBe('/post-templates/blog')
169
166
  })
170
167
 
171
- it('starts empty when the type has no template', async () => {
172
- cmsApiMock.mockResolvedValueOnce({ data: { docs: [] } })
168
+ it('seeds the default starting template when the type has no saved template', async () => {
169
+ // Unsaved type endpoint returns docId null + empty sections; the service
170
+ // backfills the default (article-body + related-posts) so the post is never blank.
171
+ cmsApiMock.mockResolvedValueOnce({ data: { postType: 'news', docId: null, sections: [] } })
173
172
  const post = await emptyPostFromTemplate('news')
174
- expect(post.sections).toEqual([])
173
+ expect(post.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
174
+ // cloned with fresh, distinct ids
175
+ expect(post.sections[0]!.id).not.toBe(post.sections[1]!.id)
175
176
  })
176
177
  })
177
178
 
178
179
  describe('fetchTemplateForEditor', () => {
179
- it('returns an unsaved default when no template document matches', async () => {
180
- cmsApiMock.mockResolvedValueOnce({ data: { docs: [] } })
180
+ it('maps a saved template from the dedicated endpoint', async () => {
181
+ cmsApiMock.mockResolvedValueOnce({
182
+ data: {
183
+ postType: 'blog',
184
+ docId: 'tmpl-9',
185
+ header: { layout: 'overlay' },
186
+ sections: [
187
+ { id: 's1', sectionType: 'quote', name: 'Q', visible: true, content: {}, settings: {} },
188
+ ],
189
+ },
190
+ })
191
+ const t = await fetchTemplateForEditor('blog')
192
+ expect(t.docId).toBe('tmpl-9')
193
+ expect(t.header.layout).toBe('overlay')
194
+ expect(t.sections).toHaveLength(1)
195
+ })
196
+
197
+ it('seeds the default starting template when the type has no saved template', async () => {
198
+ cmsApiMock.mockResolvedValueOnce({ data: { postType: 'blog', docId: null, sections: [] } })
181
199
  const t = await fetchTemplateForEditor('blog')
182
200
  expect(t.docId).toBeNull()
183
201
  expect(t.header.layout).toBe('centered')
184
- expect(t.sections).toEqual([])
202
+ expect(t.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
203
+ })
204
+
205
+ it('falls back to the default template when the request fails', async () => {
206
+ cmsApiMock.mockResolvedValueOnce({ error: 'network down' })
207
+ const t = await fetchTemplateForEditor('blog')
208
+ expect(t.docId).toBeNull()
209
+ expect(t.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
210
+ })
211
+ })
212
+
213
+ describe('saveTemplate', () => {
214
+ it('PUTs header + sections to the dedicated endpoint', async () => {
215
+ cmsApiMock.mockResolvedValueOnce({
216
+ data: { postType: 'blog', docId: 'tmpl-1', header: { layout: 'left' }, sections: [] },
217
+ })
218
+ const { saveTemplate } = await import('../../lib/post-editor-service.js')
219
+ const out = await saveTemplate({
220
+ docId: null,
221
+ postType: 'blog',
222
+ header: {
223
+ layout: 'left',
224
+ showFeaturedImage: true,
225
+ showExcerpt: true,
226
+ showAuthor: true,
227
+ showDate: true,
228
+ showCategory: true,
229
+ showReadingTime: false,
230
+ },
231
+ sections: [],
232
+ })
233
+ const [endpoint, init] = cmsApiMock.mock.calls[0]!
234
+ expect(endpoint).toBe('/post-templates/blog')
235
+ expect(init.method).toBe('PUT')
236
+ const body = JSON.parse(init.body as string)
237
+ expect(body.header.layout).toBe('left')
238
+ expect(Array.isArray(body.sections)).toBe(true)
239
+ expect(out.docId).toBe('tmpl-1')
185
240
  })
186
241
  })
187
242