@actuate-media/cms-admin 0.66.0 → 0.68.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 (244) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +36 -9
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/inspector-pane.test.d.ts +2 -0
  6. package/dist/__tests__/components/inspector-pane.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/inspector-pane.test.js +31 -0
  8. package/dist/__tests__/components/inspector-pane.test.js.map +1 -0
  9. package/dist/__tests__/hooks/useIdleLogout.test.d.ts +2 -0
  10. package/dist/__tests__/hooks/useIdleLogout.test.d.ts.map +1 -0
  11. package/dist/__tests__/hooks/useIdleLogout.test.js +64 -0
  12. package/dist/__tests__/hooks/useIdleLogout.test.js.map +1 -0
  13. package/dist/__tests__/lib/pages-tree.test.d.ts +2 -0
  14. package/dist/__tests__/lib/pages-tree.test.d.ts.map +1 -0
  15. package/dist/__tests__/lib/pages-tree.test.js +173 -0
  16. package/dist/__tests__/lib/pages-tree.test.js.map +1 -0
  17. package/dist/__tests__/lib/post-types-service.test.d.ts +2 -0
  18. package/dist/__tests__/lib/post-types-service.test.d.ts.map +1 -0
  19. package/dist/__tests__/lib/post-types-service.test.js +88 -0
  20. package/dist/__tests__/lib/post-types-service.test.js.map +1 -0
  21. package/dist/__tests__/views/collection-list-nav.render.test.js +43 -5
  22. package/dist/__tests__/views/collection-list-nav.render.test.js.map +1 -1
  23. package/dist/__tests__/views/dashboard.test.js +7 -3
  24. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  25. package/dist/__tests__/views/forms-list.render.test.js +25 -6
  26. package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
  27. package/dist/__tests__/views/media-seo-audit.render.test.d.ts +2 -0
  28. package/dist/__tests__/views/media-seo-audit.render.test.d.ts.map +1 -0
  29. package/dist/__tests__/views/media-seo-audit.render.test.js +102 -0
  30. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -0
  31. package/dist/__tests__/views/onboarding-block.render.test.d.ts +2 -0
  32. package/dist/__tests__/views/onboarding-block.render.test.d.ts.map +1 -0
  33. package/dist/__tests__/views/onboarding-block.render.test.js +146 -0
  34. package/dist/__tests__/views/onboarding-block.render.test.js.map +1 -0
  35. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +2 -0
  36. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +121 -0
  38. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +1 -0
  39. package/dist/__tests__/views/page-templates.render.test.d.ts +2 -0
  40. package/dist/__tests__/views/page-templates.render.test.d.ts.map +1 -0
  41. package/dist/__tests__/views/page-templates.render.test.js +105 -0
  42. package/dist/__tests__/views/page-templates.render.test.js.map +1 -0
  43. package/dist/__tests__/views/pages-list-view.test.js +77 -1
  44. package/dist/__tests__/views/pages-list-view.test.js.map +1 -1
  45. package/dist/__tests__/views/post-type-editor.render.test.d.ts +2 -0
  46. package/dist/__tests__/views/post-type-editor.render.test.d.ts.map +1 -0
  47. package/dist/__tests__/views/post-type-editor.render.test.js +102 -0
  48. package/dist/__tests__/views/post-type-editor.render.test.js.map +1 -0
  49. package/dist/__tests__/views/security-settings.render.test.js +85 -4
  50. package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
  51. package/dist/__tests__/views/seo-content-pane.render.test.d.ts +2 -0
  52. package/dist/__tests__/views/seo-content-pane.render.test.d.ts.map +1 -0
  53. package/dist/__tests__/views/seo-content-pane.render.test.js +117 -0
  54. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -0
  55. package/dist/actuate-admin.css +1 -1
  56. package/dist/components/collections/EntryDetailPane.d.ts +10 -0
  57. package/dist/components/collections/EntryDetailPane.d.ts.map +1 -0
  58. package/dist/components/collections/EntryDetailPane.js +92 -0
  59. package/dist/components/collections/EntryDetailPane.js.map +1 -0
  60. package/dist/components/forms/FieldsPanel.js +1 -1
  61. package/dist/components/forms/FieldsPanel.js.map +1 -1
  62. package/dist/components/forms/FormSchemaPane.d.ts +8 -0
  63. package/dist/components/forms/FormSchemaPane.d.ts.map +1 -0
  64. package/dist/components/forms/FormSchemaPane.js +95 -0
  65. package/dist/components/forms/FormSchemaPane.js.map +1 -0
  66. package/dist/components/script-tags/ScriptTagEditorPane.d.ts +26 -0
  67. package/dist/components/script-tags/ScriptTagEditorPane.d.ts.map +1 -0
  68. package/dist/components/script-tags/ScriptTagEditorPane.js +160 -0
  69. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -0
  70. package/dist/components/seo/SeoEditorPane.d.ts +9 -0
  71. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -0
  72. package/dist/components/seo/SeoEditorPane.js +175 -0
  73. package/dist/components/seo/SeoEditorPane.js.map +1 -0
  74. package/dist/components/ui/Badge.d.ts +1 -1
  75. package/dist/components/ui/Button.d.ts +1 -1
  76. package/dist/components/ui/InspectorPane.d.ts +51 -0
  77. package/dist/components/ui/InspectorPane.d.ts.map +1 -0
  78. package/dist/components/ui/InspectorPane.js +42 -0
  79. package/dist/components/ui/InspectorPane.js.map +1 -0
  80. package/dist/components/ui/StatusBadge.d.ts +1 -1
  81. package/dist/components/ui/index.d.ts +2 -0
  82. package/dist/components/ui/index.d.ts.map +1 -1
  83. package/dist/components/ui/index.js +1 -0
  84. package/dist/components/ui/index.js.map +1 -1
  85. package/dist/hooks/useIdleLogout.d.ts +18 -0
  86. package/dist/hooks/useIdleLogout.d.ts.map +1 -0
  87. package/dist/hooks/useIdleLogout.js +66 -0
  88. package/dist/hooks/useIdleLogout.js.map +1 -0
  89. package/dist/lib/api.d.ts +1 -0
  90. package/dist/lib/api.d.ts.map +1 -1
  91. package/dist/lib/api.js +7 -1
  92. package/dist/lib/api.js.map +1 -1
  93. package/dist/lib/page-editor-service.d.ts +11 -2
  94. package/dist/lib/page-editor-service.d.ts.map +1 -1
  95. package/dist/lib/page-editor-service.js +8 -2
  96. package/dist/lib/page-editor-service.js.map +1 -1
  97. package/dist/lib/pages-tree.d.ts +44 -0
  98. package/dist/lib/pages-tree.d.ts.map +1 -0
  99. package/dist/lib/pages-tree.js +106 -0
  100. package/dist/lib/pages-tree.js.map +1 -0
  101. package/dist/lib/post-types-service.d.ts +44 -0
  102. package/dist/lib/post-types-service.d.ts.map +1 -0
  103. package/dist/lib/post-types-service.js +83 -0
  104. package/dist/lib/post-types-service.js.map +1 -0
  105. package/dist/views/CollectionList.d.ts.map +1 -1
  106. package/dist/views/CollectionList.js +23 -4
  107. package/dist/views/CollectionList.js.map +1 -1
  108. package/dist/views/Dashboard.d.ts.map +1 -1
  109. package/dist/views/Dashboard.js +31 -2
  110. package/dist/views/Dashboard.js.map +1 -1
  111. package/dist/views/Forms.d.ts.map +1 -1
  112. package/dist/views/Forms.js +25 -21
  113. package/dist/views/Forms.js.map +1 -1
  114. package/dist/views/MediaBrowser.d.ts.map +1 -1
  115. package/dist/views/MediaBrowser.js +28 -2
  116. package/dist/views/MediaBrowser.js.map +1 -1
  117. package/dist/views/MediaSeoAudit.d.ts +25 -0
  118. package/dist/views/MediaSeoAudit.d.ts.map +1 -0
  119. package/dist/views/MediaSeoAudit.js +97 -0
  120. package/dist/views/MediaSeoAudit.js.map +1 -0
  121. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  122. package/dist/views/Pages/PagesListView.js +90 -10
  123. package/dist/views/Pages/PagesListView.js.map +1 -1
  124. package/dist/views/Pages/PagesTreeTable.d.ts +35 -0
  125. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -0
  126. package/dist/views/Pages/PagesTreeTable.js +28 -0
  127. package/dist/views/Pages/PagesTreeTable.js.map +1 -0
  128. package/dist/views/Posts/PostTypeEditor.d.ts +16 -0
  129. package/dist/views/Posts/PostTypeEditor.d.ts.map +1 -0
  130. package/dist/views/Posts/PostTypeEditor.js +159 -0
  131. package/dist/views/Posts/PostTypeEditor.js.map +1 -0
  132. package/dist/views/Posts/PostTypesView.d.ts +3 -2
  133. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  134. package/dist/views/Posts/PostTypesView.js +1 -1
  135. package/dist/views/Posts/PostTypesView.js.map +1 -1
  136. package/dist/views/ScriptTags.d.ts +5 -1
  137. package/dist/views/ScriptTags.d.ts.map +1 -1
  138. package/dist/views/ScriptTags.js +32 -7
  139. package/dist/views/ScriptTags.js.map +1 -1
  140. package/dist/views/dashboard/OnboardingBlock.d.ts +29 -0
  141. package/dist/views/dashboard/OnboardingBlock.d.ts.map +1 -0
  142. package/dist/views/dashboard/OnboardingBlock.js +71 -0
  143. package/dist/views/dashboard/OnboardingBlock.js.map +1 -0
  144. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +20 -0
  145. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +1 -0
  146. package/dist/views/page-builder/BuilderSectionsPanel.js +105 -0
  147. package/dist/views/page-builder/BuilderSectionsPanel.js.map +1 -0
  148. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  149. package/dist/views/page-builder/PageBuilder.js +51 -2
  150. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  151. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  152. package/dist/views/page-builder/PageTemplates.js +113 -6
  153. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  154. package/dist/views/page-editor/PageSectionEditor.d.ts +7 -1
  155. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  156. package/dist/views/page-editor/PageSectionEditor.js +3 -3
  157. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  158. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  159. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  160. package/dist/views/page-editor/PageSettingsModal.js +42 -3
  161. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  162. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  163. package/dist/views/seo/ContentTab.js +22 -6
  164. package/dist/views/seo/ContentTab.js.map +1 -1
  165. package/dist/views/settings/AccessProtectionCard.d.ts +5 -1
  166. package/dist/views/settings/AccessProtectionCard.d.ts.map +1 -1
  167. package/dist/views/settings/AccessProtectionCard.js +13 -10
  168. package/dist/views/settings/AccessProtectionCard.js.map +1 -1
  169. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  170. package/dist/views/settings/SecurityTab.js +3 -3
  171. package/dist/views/settings/SecurityTab.js.map +1 -1
  172. package/dist/views/settings/SessionPolicyCard.d.ts +1 -2
  173. package/dist/views/settings/SessionPolicyCard.d.ts.map +1 -1
  174. package/dist/views/settings/SessionPolicyCard.js +12 -5
  175. package/dist/views/settings/SessionPolicyCard.js.map +1 -1
  176. package/dist/views/settings/useSecuritySettings.d.ts +13 -1
  177. package/dist/views/settings/useSecuritySettings.d.ts.map +1 -1
  178. package/dist/views/settings/useSecuritySettings.js +47 -4
  179. package/dist/views/settings/useSecuritySettings.js.map +1 -1
  180. package/package.json +2 -2
  181. package/src/AdminRoot.tsx +43 -10
  182. package/src/__tests__/components/inspector-pane.test.tsx +64 -0
  183. package/src/__tests__/hooks/useIdleLogout.test.tsx +86 -0
  184. package/src/__tests__/lib/pages-tree.test.ts +197 -0
  185. package/src/__tests__/lib/post-types-service.test.ts +112 -0
  186. package/src/__tests__/views/collection-list-nav.render.test.tsx +51 -5
  187. package/src/__tests__/views/dashboard.test.tsx +7 -3
  188. package/src/__tests__/views/forms-list.render.test.tsx +34 -6
  189. package/src/__tests__/views/media-seo-audit.render.test.tsx +117 -0
  190. package/src/__tests__/views/onboarding-block.render.test.tsx +193 -0
  191. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +138 -0
  192. package/src/__tests__/views/page-templates.render.test.tsx +142 -0
  193. package/src/__tests__/views/pages-list-view.test.tsx +93 -1
  194. package/src/__tests__/views/post-type-editor.render.test.tsx +116 -0
  195. package/src/__tests__/views/security-settings.render.test.tsx +111 -4
  196. package/src/__tests__/views/seo-content-pane.render.test.tsx +142 -0
  197. package/src/components/collections/EntryDetailPane.tsx +182 -0
  198. package/src/components/forms/FieldsPanel.tsx +3 -1
  199. package/src/components/forms/FormSchemaPane.tsx +192 -0
  200. package/src/components/script-tags/ScriptTagEditorPane.tsx +474 -0
  201. package/src/components/seo/SeoEditorPane.tsx +533 -0
  202. package/src/components/ui/InspectorPane.tsx +129 -0
  203. package/src/components/ui/index.ts +10 -0
  204. package/src/hooks/useIdleLogout.ts +77 -0
  205. package/src/lib/api.ts +8 -2
  206. package/src/lib/page-editor-service.ts +16 -2
  207. package/src/lib/pages-tree.ts +145 -0
  208. package/src/lib/post-types-service.ts +129 -0
  209. package/src/views/CollectionList.tsx +158 -110
  210. package/src/views/Dashboard.tsx +238 -196
  211. package/src/views/Forms.tsx +154 -126
  212. package/src/views/MediaBrowser.tsx +641 -511
  213. package/src/views/MediaSeoAudit.tsx +320 -0
  214. package/src/views/Pages/PagesListView.tsx +352 -183
  215. package/src/views/Pages/PagesTreeTable.tsx +339 -0
  216. package/src/views/Posts/PostTypeEditor.tsx +568 -0
  217. package/src/views/Posts/PostTypesView.tsx +5 -4
  218. package/src/views/ScriptTags.tsx +204 -129
  219. package/src/views/dashboard/OnboardingBlock.tsx +233 -0
  220. package/src/views/page-builder/BuilderSectionsPanel.tsx +304 -0
  221. package/src/views/page-builder/PageBuilder.tsx +69 -2
  222. package/src/views/page-builder/PageTemplates.tsx +340 -57
  223. package/src/views/page-editor/PageSectionEditor.tsx +9 -2
  224. package/src/views/page-editor/PageSettingsModal.tsx +75 -2
  225. package/src/views/seo/ContentTab.tsx +113 -80
  226. package/src/views/settings/AccessProtectionCard.tsx +83 -53
  227. package/src/views/settings/SecurityTab.tsx +23 -2
  228. package/src/views/settings/SessionPolicyCard.tsx +45 -9
  229. package/src/views/settings/useSecuritySettings.ts +78 -22
  230. package/dist/components/forms/FormSchemaDrawer.d.ts +0 -9
  231. package/dist/components/forms/FormSchemaDrawer.d.ts.map +0 -1
  232. package/dist/components/forms/FormSchemaDrawer.js +0 -99
  233. package/dist/components/forms/FormSchemaDrawer.js.map +0 -1
  234. package/dist/components/seo/SeoEditorDrawer.d.ts +0 -8
  235. package/dist/components/seo/SeoEditorDrawer.d.ts.map +0 -1
  236. package/dist/components/seo/SeoEditorDrawer.js +0 -172
  237. package/dist/components/seo/SeoEditorDrawer.js.map +0 -1
  238. package/dist/views/ScriptTagEditor.d.ts +0 -6
  239. package/dist/views/ScriptTagEditor.d.ts.map +0 -1
  240. package/dist/views/ScriptTagEditor.js +0 -111
  241. package/dist/views/ScriptTagEditor.js.map +0 -1
  242. package/src/components/forms/FormSchemaDrawer.tsx +0 -192
  243. package/src/components/seo/SeoEditorDrawer.tsx +0 -529
  244. package/src/views/ScriptTagEditor.tsx +0 -353
@@ -6,18 +6,44 @@ const EMPTY_FORM = {
6
6
  mfaRequired: false,
7
7
  mfaGracePeriodDays: 7,
8
8
  maxConcurrentSessions: 5,
9
+ timeoutHours: 168,
10
+ idleLogoutEnabled: false,
11
+ ipAllowlistText: '',
12
+ auditLoggingEnabled: true,
9
13
  };
14
+ /** Split the textarea into trimmed, de-duplicated entries (one per line). */
15
+ export function parseAllowlistText(text) {
16
+ const out = [];
17
+ for (const line of text.split('\n')) {
18
+ const entry = line.trim();
19
+ if (entry && !out.includes(entry))
20
+ out.push(entry);
21
+ }
22
+ return out;
23
+ }
10
24
  function formFromSettings(s) {
11
25
  return {
12
26
  mfaRequired: s.mfa.required,
13
27
  mfaGracePeriodDays: s.mfa.gracePeriodDays,
14
28
  maxConcurrentSessions: s.session.maxConcurrentSessions,
29
+ timeoutHours: s.session.timeoutHours ?? 168,
30
+ idleLogoutEnabled: s.session.idleLogoutEnabled ?? false,
31
+ ipAllowlistText: (s.access?.ipAllowlist ?? []).join('\n'),
32
+ auditLoggingEnabled: s.access?.auditLoggingEnabled ?? true,
15
33
  };
16
34
  }
17
35
  function formToSettings(f) {
18
36
  return {
19
37
  mfa: { required: f.mfaRequired, gracePeriodDays: f.mfaGracePeriodDays },
20
- session: { maxConcurrentSessions: f.maxConcurrentSessions },
38
+ session: {
39
+ maxConcurrentSessions: f.maxConcurrentSessions,
40
+ timeoutHours: f.timeoutHours,
41
+ idleLogoutEnabled: f.idleLogoutEnabled,
42
+ },
43
+ access: {
44
+ ipAllowlist: parseAllowlistText(f.ipAllowlistText),
45
+ auditLoggingEnabled: f.auditLoggingEnabled,
46
+ },
21
47
  };
22
48
  }
23
49
  /**
@@ -35,6 +61,7 @@ export function useSecuritySettings(canEdit) {
35
61
  const [form, setForm] = useState(EMPTY_FORM);
36
62
  const [saveState, setSaveState] = useState('idle');
37
63
  const [saveError, setSaveError] = useState(null);
64
+ const [lockoutPrompt, setLockoutPrompt] = useState(null);
38
65
  const [revokeState, setRevokeState] = useState('idle');
39
66
  const [reloadKey, setReloadKey] = useState(0);
40
67
  useEffect(() => {
@@ -85,18 +112,30 @@ export function useSecuritySettings(canEdit) {
85
112
  return list;
86
113
  }, [form, baseline.mfaRequired, data]);
87
114
  const hasErrors = issues.some((i) => i.severity === 'error');
88
- const save = useCallback(async () => {
115
+ const save = useCallback(async (options) => {
89
116
  if (!canEdit || hasErrors || !dirty)
90
117
  return;
91
118
  setSaveState('saving');
92
119
  setSaveError(null);
120
+ setLockoutPrompt(null);
93
121
  try {
94
122
  const res = await cmsApi('/security', {
95
123
  method: 'PUT',
96
- body: JSON.stringify(formToSettings(form)),
124
+ body: JSON.stringify({
125
+ ...formToSettings(form),
126
+ ...(options?.confirmLockout ? { confirmLockout: true } : {}),
127
+ }),
97
128
  });
98
- if (res.error || !res.data)
129
+ // The server's lockout guard: surface a confirm dialog instead of a
130
+ // generic failure so the operator can retry with confirmLockout.
131
+ if (res.code === 'IP_LOCKOUT_RISK') {
132
+ setSaveState('idle');
133
+ setLockoutPrompt(res.error ?? 'This allowlist may lock you out. Save anyway?');
134
+ return;
135
+ }
136
+ if (res.error || !res.data) {
99
137
  throw new Error(res.error ?? 'Failed to save security settings.');
138
+ }
100
139
  setData(res.data);
101
140
  const f = formFromSettings(res.data.settings);
102
141
  setBaseline(f);
@@ -108,10 +147,12 @@ export function useSecuritySettings(canEdit) {
108
147
  setSaveError(err instanceof Error ? err.message : 'Failed to save security settings.');
109
148
  }
110
149
  }, [canEdit, hasErrors, dirty, form]);
150
+ const dismissLockoutPrompt = useCallback(() => setLockoutPrompt(null), []);
111
151
  const reset = useCallback(() => {
112
152
  setForm(baseline);
113
153
  setSaveState('idle');
114
154
  setSaveError(null);
155
+ setLockoutPrompt(null);
115
156
  }, [baseline]);
116
157
  const refetch = useCallback(() => setReloadKey((k) => k + 1), []);
117
158
  const revokeAllOtherSessions = useCallback(async () => {
@@ -147,6 +188,8 @@ export function useSecuritySettings(canEdit) {
147
188
  save,
148
189
  reset,
149
190
  refetch,
191
+ lockoutPrompt,
192
+ dismissLockoutPrompt,
150
193
  revokeState,
151
194
  revokeAllOtherSessions,
152
195
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useSecuritySettings.js","sourceRoot":"","sources":["../../../src/views/settings/useSecuritySettings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSecuritySettings.js","sourceRoot":"","sources":["../../../src/views/settings/useSecuritySettings.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-admin",
3
- "version": "0.66.0",
3
+ "version": "0.68.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/actuate-media/actuatecms.git",
@@ -95,7 +95,7 @@
95
95
  "tailwindcss": "^4.0.0",
96
96
  "typescript": "^5.7.0",
97
97
  "vitest": "^4.1.0",
98
- "@actuate-media/cms-core": "0.86.0",
98
+ "@actuate-media/cms-core": "0.88.0",
99
99
  "@actuate-media/component-blocks": "0.2.1"
100
100
  },
101
101
  "scripts": {
package/src/AdminRoot.tsx CHANGED
@@ -26,7 +26,6 @@ import { FormSubmissions } from './views/FormSubmissions.js'
26
26
  import { FormEntries } from './views/FormEntries.js'
27
27
  import { SEO } from './views/SEO.js'
28
28
  import { ScriptTags } from './views/ScriptTags.js'
29
- import { ScriptTagEditor } from './views/ScriptTagEditor.js'
30
29
  import { SetupWizard } from './views/SetupWizard.js'
31
30
  import { Login } from './views/Login.js'
32
31
  import { HiddenLoginNotFound } from './views/HiddenLoginNotFound.js'
@@ -39,6 +38,7 @@ import { ThemeProvider } from './components/ThemeProvider.js'
39
38
  import { LocaleProvider } from './components/LocaleProvider.js'
40
39
  import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts.js'
41
40
  import { useAppearanceBootstrap } from './hooks/useAppearanceBootstrap.js'
41
+ import { useIdleLogout } from './hooks/useIdleLogout.js'
42
42
  import { PageBuilder } from './views/page-builder/PageBuilder.js'
43
43
  import { PageSectionEditor } from './views/page-editor/PageSectionEditor.js'
44
44
  import { PagePreview } from './views/page-editor/PagePreview.js'
@@ -53,6 +53,7 @@ import { PagesListView } from './views/Pages/PagesListView.js'
53
53
  import { PageTagManager } from './views/Pages/PageTagManager.js'
54
54
  import { PostsListView } from './views/Posts/PostsListView.js'
55
55
  import { PostTypesView } from './views/Posts/PostTypesView.js'
56
+ import { PostTypeEditor } from './views/Posts/PostTypeEditor.js'
56
57
  import { NewPostPicker } from './views/Posts/NewPostPicker.js'
57
58
  import { NewPostTypeInstructions } from './views/Posts/NewPostTypeInstructions.js'
58
59
  import { PostSectionEditor } from './views/post-editor/PostSectionEditor.js'
@@ -180,6 +181,39 @@ function AdminShell({
180
181
  [buildHref],
181
182
  )
182
183
 
184
+ // Idle auto-logout (Settings → Security). The policy is read from the
185
+ // non-admin-gated /auth/session-policy endpoint so every member's client
186
+ // enforces it, not just admins. When enabled, `timeoutHours` of inactivity
187
+ // signs the user out through the normal logout flow (server revocation +
188
+ // cookie clear + local state reset).
189
+ const [sessionPolicy, setSessionPolicy] = useState<{
190
+ timeoutHours: number
191
+ idleLogoutEnabled: boolean
192
+ } | null>(null)
193
+ useEffect(() => {
194
+ if (!session) {
195
+ setSessionPolicy(null)
196
+ return
197
+ }
198
+ let cancelled = false
199
+ void cmsApi<{ timeoutHours: number; idleLogoutEnabled: boolean }>('/auth/session-policy').then(
200
+ (res) => {
201
+ if (!cancelled && res.data && typeof res.data.timeoutHours === 'number') {
202
+ setSessionPolicy(res.data)
203
+ }
204
+ },
205
+ )
206
+ return () => {
207
+ cancelled = true
208
+ }
209
+ }, [session])
210
+
211
+ useIdleLogout({
212
+ enabled: Boolean(session && sessionPolicy?.idleLogoutEnabled),
213
+ timeoutMs: (sessionPolicy?.timeoutHours ?? 0) * 3_600_000,
214
+ onIdle: handleLogout,
215
+ })
216
+
183
217
  useBranding(config)
184
218
  useAppearanceBootstrap(session)
185
219
  useAdminPageTitle(config, currentPath)
@@ -359,13 +393,7 @@ function AdminShell({
359
393
  }
360
394
  const postTypeEdit = matchRoute('/posts/types/:slug/edit')
361
395
  if (postTypeEdit?.slug) {
362
- // V1 falls through to the per-collection list view; future
363
- // PR introduces a dedicated PostTypeEditor with field-level
364
- // metadata. The list view is the natural per-type management
365
- // surface today (it already shows posts in that type).
366
- return (
367
- <CollectionList collectionSlug={postTypeEdit.slug} config={config} onNavigate={navigate} />
368
- )
396
+ return <PostTypeEditor slug={postTypeEdit.slug} onNavigate={navigate} />
369
397
  }
370
398
  if (matchRoute('/posts/types')) {
371
399
  return <PostTypesView onNavigate={navigate} />
@@ -432,8 +460,13 @@ function AdminShell({
432
460
  return <PageTagManager onNavigate={navigate} />
433
461
  }
434
462
  if (matchRoute('/pages/new')) {
463
+ // "Add sub-page" in the Pages tree view arrives with the parent id
464
+ // in the query string (`/pages/new?parent=<id>`); matchRoute ignores
465
+ // the query so it's parsed here and preselected in the editor.
466
+ const newPageQuery = new URLSearchParams(currentPath.split('?')[1] ?? '')
435
467
  return (
436
468
  <PageSectionEditor
469
+ initialParentId={newPageQuery.get('parent')}
437
470
  config={config}
438
471
  session={session}
439
472
  onNavigate={navigate}
@@ -509,11 +542,11 @@ function AdminShell({
509
542
  }
510
543
 
511
544
  if (matchRoute('/script-tags/new')) {
512
- return <ScriptTagEditor onNavigate={navigate} />
545
+ return <ScriptTags onNavigate={navigate} initialNew />
513
546
  }
514
547
  const scriptTagEdit = matchRoute('/script-tags/:id')
515
548
  if (scriptTagEdit?.id) {
516
- return <ScriptTagEditor tagId={scriptTagEdit.id} onNavigate={navigate} />
549
+ return <ScriptTags onNavigate={navigate} initialSelectedId={scriptTagEdit.id} />
517
550
  }
518
551
  if (matchRoute('/script-tags')) {
519
552
  return <ScriptTags onNavigate={navigate} />
@@ -0,0 +1,64 @@
1
+ // @vitest-environment happy-dom
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import { render, screen } from '@testing-library/react'
4
+ import {
5
+ InspectorPane,
6
+ InspectorPaneBody,
7
+ InspectorPaneFooter,
8
+ InspectorPaneHeader,
9
+ InspectorPaneSection,
10
+ SplitPaneLayout,
11
+ SplitPaneList,
12
+ } from '../../components/ui/InspectorPane.js'
13
+
14
+ function Pane({ open }: { open: boolean }) {
15
+ return (
16
+ <SplitPaneLayout>
17
+ <SplitPaneList>
18
+ <table aria-label="Rows" />
19
+ </SplitPaneList>
20
+ <InspectorPane open={open} width={420} aria-label="Row editor">
21
+ <InspectorPaneHeader onClose={() => {}}>
22
+ <span>Editing</span>
23
+ </InspectorPaneHeader>
24
+ <InspectorPaneBody>
25
+ <InspectorPaneSection title="Details">content</InspectorPaneSection>
26
+ </InspectorPaneBody>
27
+ <InspectorPaneFooter>
28
+ <button type="button">Save</button>
29
+ </InspectorPaneFooter>
30
+ </InspectorPane>
31
+ </SplitPaneLayout>
32
+ )
33
+ }
34
+
35
+ describe('InspectorPane', () => {
36
+ it('renders pane content with a labelled region when open', () => {
37
+ render(<Pane open />)
38
+ expect(screen.getByRole('region', { name: 'Row editor' })).toBeTruthy()
39
+ expect(screen.getByText('Editing')).toBeTruthy()
40
+ expect(screen.getByText('Details')).toBeTruthy()
41
+ expect(screen.getByRole('button', { name: 'Save' })).toBeTruthy()
42
+ })
43
+
44
+ it('collapses to zero width and hides content when closed', () => {
45
+ const { container } = render(<Pane open={false} />)
46
+ const shell = container.querySelector('[aria-hidden="true"]') as HTMLElement
47
+ expect(shell).toBeTruthy()
48
+ expect(shell.style.width).toBe('0px')
49
+ expect(screen.queryByText('Editing')).toBeNull()
50
+ })
51
+
52
+ it('close button in the header fires onClose with an accessible label', () => {
53
+ const onClose = vi.fn()
54
+ render(
55
+ <InspectorPane open width={400} aria-label="Detail">
56
+ <InspectorPaneHeader onClose={onClose} closeLabel="Close detail">
57
+ <span>Title</span>
58
+ </InspectorPaneHeader>
59
+ </InspectorPane>,
60
+ )
61
+ screen.getByRole('button', { name: 'Close detail' }).click()
62
+ expect(onClose).toHaveBeenCalledTimes(1)
63
+ })
64
+ })
@@ -0,0 +1,86 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { render } from '@testing-library/react'
4
+
5
+ import { useIdleLogout } from '../../hooks/useIdleLogout.js'
6
+
7
+ function Harness({
8
+ enabled,
9
+ timeoutMs,
10
+ onIdle,
11
+ }: {
12
+ enabled: boolean
13
+ timeoutMs: number
14
+ onIdle: () => void
15
+ }) {
16
+ useIdleLogout({ enabled, timeoutMs, onIdle })
17
+ return null
18
+ }
19
+
20
+ const TIMEOUT_MS = 60_000
21
+
22
+ beforeEach(() => {
23
+ vi.useFakeTimers()
24
+ })
25
+
26
+ afterEach(() => {
27
+ vi.useRealTimers()
28
+ })
29
+
30
+ describe('useIdleLogout', () => {
31
+ it('fires onIdle exactly once after the timeout with no activity', () => {
32
+ const onIdle = vi.fn()
33
+ render(<Harness enabled timeoutMs={TIMEOUT_MS} onIdle={onIdle} />)
34
+
35
+ vi.advanceTimersByTime(TIMEOUT_MS - 1_000)
36
+ expect(onIdle).not.toHaveBeenCalled()
37
+
38
+ vi.advanceTimersByTime(TIMEOUT_MS)
39
+ expect(onIdle).toHaveBeenCalledTimes(1)
40
+
41
+ // The idle callback must not re-fire on subsequent ticks.
42
+ vi.advanceTimersByTime(TIMEOUT_MS * 3)
43
+ expect(onIdle).toHaveBeenCalledTimes(1)
44
+ })
45
+
46
+ it('resets the idle deadline on user activity', () => {
47
+ const onIdle = vi.fn()
48
+ render(<Harness enabled timeoutMs={TIMEOUT_MS} onIdle={onIdle} />)
49
+
50
+ // Just before the deadline the user types — the clock restarts.
51
+ vi.advanceTimersByTime(TIMEOUT_MS - 5_000)
52
+ window.dispatchEvent(new Event('keydown'))
53
+ vi.advanceTimersByTime(TIMEOUT_MS - 5_000)
54
+ expect(onIdle).not.toHaveBeenCalled()
55
+
56
+ // A full quiet timeout after the last activity signs the user out.
57
+ vi.advanceTimersByTime(TIMEOUT_MS)
58
+ expect(onIdle).toHaveBeenCalledTimes(1)
59
+ })
60
+
61
+ it('does nothing when disabled', () => {
62
+ const onIdle = vi.fn()
63
+ render(<Harness enabled={false} timeoutMs={TIMEOUT_MS} onIdle={onIdle} />)
64
+ vi.advanceTimersByTime(TIMEOUT_MS * 10)
65
+ expect(onIdle).not.toHaveBeenCalled()
66
+ })
67
+
68
+ it('tears everything down on unmount', () => {
69
+ const onIdle = vi.fn()
70
+ const { unmount } = render(<Harness enabled timeoutMs={TIMEOUT_MS} onIdle={onIdle} />)
71
+ unmount()
72
+ vi.advanceTimersByTime(TIMEOUT_MS * 10)
73
+ expect(onIdle).not.toHaveBeenCalled()
74
+ })
75
+
76
+ it('checks the deadline immediately when a hidden tab becomes visible', () => {
77
+ const onIdle = vi.fn()
78
+ render(<Harness enabled timeoutMs={TIMEOUT_MS} onIdle={onIdle} />)
79
+
80
+ // Simulate the deadline passing while the tab was hidden (the interval is
81
+ // shared with visibility, so freeze it by only moving the mocked clock).
82
+ vi.setSystemTime(Date.now() + TIMEOUT_MS + 1_000)
83
+ document.dispatchEvent(new Event('visibilitychange'))
84
+ expect(onIdle).toHaveBeenCalledTimes(1)
85
+ })
86
+ })
@@ -0,0 +1,197 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ buildPageTree,
4
+ defaultExpandedIds,
5
+ flattenPageTree,
6
+ type PageTreeNode,
7
+ } from '../../lib/pages-tree.js'
8
+ import type { Page } from '../../lib/pages-service.js'
9
+
10
+ /**
11
+ * Unit coverage for the Pages tree hierarchy resolution. Pure functions —
12
+ * locked independent of the REST layer and the view. The contract:
13
+ * explicit `parentPageId` wins, path-prefix derivation fills the gaps,
14
+ * and no input (orphans, cycles, duplicates) can lose or duplicate a page.
15
+ */
16
+
17
+ function page(partial: Partial<Page> & Pick<Page, 'id' | 'title' | 'path'>): Page {
18
+ return {
19
+ id: partial.id,
20
+ title: partial.title,
21
+ path: partial.path,
22
+ slug: partial.slug ?? partial.path.split('/').filter(Boolean).pop() ?? '',
23
+ parentPageId: partial.parentPageId ?? null,
24
+ tags: [],
25
+ primaryTagId: null,
26
+ authorId: 'u1',
27
+ authorName: 'Maria K.',
28
+ authorInitials: 'MK',
29
+ status: partial.status ?? 'PUBLISHED',
30
+ publishDate: null,
31
+ scheduledDate: null,
32
+ lastModified: '2026-05-01',
33
+ seoScore: null,
34
+ seoTitle: null,
35
+ seoDescription: null,
36
+ templateId: partial.templateId ?? null,
37
+ isHomepage: partial.path === '/',
38
+ isSystemPage: false,
39
+ createdAt: '2026-01-01',
40
+ updatedAt: '2026-05-01',
41
+ }
42
+ }
43
+
44
+ function titles(nodes: PageTreeNode[]): string[] {
45
+ return nodes.map((n) => n.page.title)
46
+ }
47
+
48
+ function countNodes(nodes: PageTreeNode[]): number {
49
+ return nodes.reduce((n, node) => n + 1 + countNodes(node.children), 0)
50
+ }
51
+
52
+ describe('buildPageTree', () => {
53
+ it('nests children under explicit parentPageId parents', () => {
54
+ const roots = buildPageTree([
55
+ page({ id: 'about', title: 'About Us', path: '/about' }),
56
+ page({ id: 'team', title: 'Our Team', path: '/team-page', parentPageId: 'about' }),
57
+ ])
58
+ expect(titles(roots)).toEqual(['About Us'])
59
+ expect(titles(roots[0]!.children)).toEqual(['Our Team'])
60
+ })
61
+
62
+ it('derives parents from path prefixes when parentPageId is null', () => {
63
+ const roots = buildPageTree([
64
+ page({ id: 'home', title: 'Home', path: '/' }),
65
+ page({ id: 'about', title: 'About Us', path: '/about' }),
66
+ page({ id: 'team', title: 'Our Team', path: '/about/team' }),
67
+ page({ id: 'story', title: 'Our Story', path: '/about/story' }),
68
+ page({ id: 'contact', title: 'Contact', path: '/contact' }),
69
+ ])
70
+ expect(titles(roots)).toEqual(['About Us', 'Contact', 'Home'])
71
+ const about = roots[0]!
72
+ expect(titles(about.children)).toEqual(['Our Story', 'Our Team'])
73
+ })
74
+
75
+ it('skips missing intermediate path levels to the longest existing prefix', () => {
76
+ const roots = buildPageTree([
77
+ page({ id: 'svc', title: 'Services', path: '/services' }),
78
+ // No "/services/web" page exists — attaches to "/services".
79
+ page({ id: 'design', title: 'Web Design', path: '/services/web/design' }),
80
+ ])
81
+ expect(titles(roots)).toEqual(['Services'])
82
+ expect(titles(roots[0]!.children)).toEqual(['Web Design'])
83
+ })
84
+
85
+ it('never derives the homepage ("/") as a path parent', () => {
86
+ const roots = buildPageTree([
87
+ page({ id: 'home', title: 'Home', path: '/' }),
88
+ page({ id: 'blog', title: 'Blog', path: '/blog' }),
89
+ ])
90
+ expect(titles(roots)).toEqual(['Blog', 'Home'])
91
+ })
92
+
93
+ it('prefers explicit parentPageId over the path prefix', () => {
94
+ const roots = buildPageTree([
95
+ page({ id: 'about', title: 'About', path: '/about' }),
96
+ page({ id: 'lp', title: 'Landing', path: '/lp' }),
97
+ // Path says "/about/…" but the explicit link says "lp" — link wins.
98
+ page({ id: 'x', title: 'Campaign', path: '/about/campaign', parentPageId: 'lp' }),
99
+ ])
100
+ const lp = roots.find((n) => n.page.id === 'lp')!
101
+ expect(titles(lp.children)).toEqual(['Campaign'])
102
+ expect(roots.find((n) => n.page.id === 'about')!.children).toHaveLength(0)
103
+ })
104
+
105
+ it('treats orphans (parent not in the set) as roots', () => {
106
+ const roots = buildPageTree([
107
+ page({ id: 'a', title: 'A', path: '/a', parentPageId: 'missing' }),
108
+ page({ id: 'b', title: 'B', path: '/b' }),
109
+ ])
110
+ expect(titles(roots)).toEqual(['A', 'B'])
111
+ })
112
+
113
+ it('breaks parentPageId cycles to the root without losing pages', () => {
114
+ const roots = buildPageTree([
115
+ page({ id: 'a', title: 'A', path: '/x-a', parentPageId: 'b' }),
116
+ page({ id: 'b', title: 'B', path: '/x-b', parentPageId: 'a' }),
117
+ page({ id: 'c', title: 'C', path: '/x-c' }),
118
+ ])
119
+ expect(countNodes(roots)).toBe(3)
120
+ // One of the cycle members must have been rooted.
121
+ const rootIds = roots.map((n) => n.page.id)
122
+ expect(rootIds).toContain('c')
123
+ expect(rootIds.some((id) => id === 'a' || id === 'b')).toBe(true)
124
+ })
125
+
126
+ it('ignores a self-referencing parentPageId', () => {
127
+ const roots = buildPageTree([page({ id: 'a', title: 'A', path: '/a', parentPageId: 'a' })])
128
+ expect(titles(roots)).toEqual(['A'])
129
+ })
130
+
131
+ it('orders siblings by title then path, at every depth', () => {
132
+ const roots = buildPageTree([
133
+ page({ id: 'svc', title: 'Services', path: '/services' }),
134
+ page({ id: 's2', title: 'Web Design', path: '/services/web-design' }),
135
+ page({ id: 's1', title: 'CMS Solutions', path: '/services/cms' }),
136
+ page({ id: 's3', title: 'SEO Services', path: '/services/seo' }),
137
+ page({ id: 'blog', title: 'Blog', path: '/blog' }),
138
+ ])
139
+ expect(titles(roots)).toEqual(['Blog', 'Services'])
140
+ expect(titles(roots.find((n) => n.page.id === 'svc')!.children)).toEqual([
141
+ 'CMS Solutions',
142
+ 'SEO Services',
143
+ 'Web Design',
144
+ ])
145
+ })
146
+
147
+ it('keeps every page exactly once on messy input', () => {
148
+ const pages = [
149
+ page({ id: 'home', title: 'Home', path: '/' }),
150
+ page({ id: 'dup1', title: 'Dup A', path: '/dup' }),
151
+ page({ id: 'dup2', title: 'Dup B', path: '/dup' }),
152
+ page({ id: 'kid', title: 'Kid', path: '/dup/kid' }),
153
+ page({ id: 'cyc1', title: 'Cyc 1', path: '/c1', parentPageId: 'cyc2' }),
154
+ page({ id: 'cyc2', title: 'Cyc 2', path: '/c2', parentPageId: 'cyc1' }),
155
+ page({ id: 'orph', title: 'Orphan', path: '/orphan', parentPageId: 'gone' }),
156
+ ]
157
+ const roots = buildPageTree(pages)
158
+ expect(countNodes(roots)).toBe(pages.length)
159
+ // "/dup/kid" attaches to the first page seen with path "/dup".
160
+ expect(roots.some((n) => n.page.id === 'dup1' && n.children.length === 1)).toBe(true)
161
+ })
162
+ })
163
+
164
+ describe('flattenPageTree', () => {
165
+ const roots = buildPageTree([
166
+ page({ id: 'about', title: 'About Us', path: '/about' }),
167
+ page({ id: 'team', title: 'Our Team', path: '/about/team' }),
168
+ page({ id: 'blog', title: 'Blog', path: '/blog' }),
169
+ ])
170
+
171
+ it('renders children only when the parent is expanded', () => {
172
+ const collapsed = flattenPageTree(roots, new Set())
173
+ expect(collapsed.map((r) => r.page.id)).toEqual(['about', 'blog'])
174
+
175
+ const open = flattenPageTree(roots, new Set(['about']))
176
+ expect(open.map((r) => r.page.id)).toEqual(['about', 'team', 'blog'])
177
+ })
178
+
179
+ it('reports depth and hasChildren per row', () => {
180
+ const rows = flattenPageTree(roots, new Set(['about']))
181
+ expect(rows[0]).toMatchObject({ depth: 0, hasChildren: true })
182
+ expect(rows[1]).toMatchObject({ depth: 1, hasChildren: false })
183
+ expect(rows[2]).toMatchObject({ depth: 0, hasChildren: false })
184
+ })
185
+ })
186
+
187
+ describe('defaultExpandedIds', () => {
188
+ it('expands only root nodes that have children', () => {
189
+ const roots = buildPageTree([
190
+ page({ id: 'about', title: 'About Us', path: '/about' }),
191
+ page({ id: 'team', title: 'Our Team', path: '/about/team' }),
192
+ page({ id: 'deep', title: 'Deep', path: '/about/team/deep' }),
193
+ page({ id: 'blog', title: 'Blog', path: '/blog' }),
194
+ ])
195
+ expect([...defaultExpandedIds(roots)]).toEqual(['about'])
196
+ })
197
+ })