@actuate-media/cms-admin 0.14.1 → 0.16.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 (304) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +148 -2
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/components/posts-badges.test.d.ts +2 -0
  5. package/dist/__tests__/components/posts-badges.test.d.ts.map +1 -0
  6. package/dist/__tests__/components/posts-badges.test.js +68 -0
  7. package/dist/__tests__/components/posts-badges.test.js.map +1 -0
  8. package/dist/__tests__/layout/sidebar-posts.test.d.ts +2 -0
  9. package/dist/__tests__/layout/sidebar-posts.test.d.ts.map +1 -0
  10. package/dist/__tests__/layout/sidebar-posts.test.js +162 -0
  11. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -0
  12. package/dist/__tests__/lib/page-editor-service.test.d.ts +2 -0
  13. package/dist/__tests__/lib/page-editor-service.test.d.ts.map +1 -0
  14. package/dist/__tests__/lib/page-editor-service.test.js +137 -0
  15. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -0
  16. package/dist/__tests__/lib/pages-service.test.d.ts +2 -0
  17. package/dist/__tests__/lib/pages-service.test.d.ts.map +1 -0
  18. package/dist/__tests__/lib/pages-service.test.js +140 -0
  19. package/dist/__tests__/lib/pages-service.test.js.map +1 -0
  20. package/dist/__tests__/lib/post-editor-service.test.d.ts +2 -0
  21. package/dist/__tests__/lib/post-editor-service.test.d.ts.map +1 -0
  22. package/dist/__tests__/lib/post-editor-service.test.js +185 -0
  23. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -0
  24. package/dist/__tests__/lib/posts-service.test.d.ts +2 -0
  25. package/dist/__tests__/lib/posts-service.test.d.ts.map +1 -0
  26. package/dist/__tests__/lib/posts-service.test.js +244 -0
  27. package/dist/__tests__/lib/posts-service.test.js.map +1 -0
  28. package/dist/__tests__/views/posts-list-view.test.d.ts +2 -0
  29. package/dist/__tests__/views/posts-list-view.test.d.ts.map +1 -0
  30. package/dist/__tests__/views/posts-list-view.test.js +98 -0
  31. package/dist/__tests__/views/posts-list-view.test.js.map +1 -0
  32. package/dist/actuate-admin.css +1 -1
  33. package/dist/components/pages/tag-badges.d.ts +21 -0
  34. package/dist/components/pages/tag-badges.d.ts.map +1 -0
  35. package/dist/components/pages/tag-badges.js +56 -0
  36. package/dist/components/pages/tag-badges.js.map +1 -0
  37. package/dist/components/posts/badges.d.ts +25 -0
  38. package/dist/components/posts/badges.d.ts.map +1 -0
  39. package/dist/components/posts/badges.js +123 -0
  40. package/dist/components/posts/badges.js.map +1 -0
  41. package/dist/components/posts/icons.d.ts +12 -0
  42. package/dist/components/posts/icons.d.ts.map +1 -0
  43. package/dist/components/posts/icons.js +26 -0
  44. package/dist/components/posts/icons.js.map +1 -0
  45. package/dist/layout/Header.d.ts +14 -1
  46. package/dist/layout/Header.d.ts.map +1 -1
  47. package/dist/layout/Header.js +10 -3
  48. package/dist/layout/Header.js.map +1 -1
  49. package/dist/layout/Layout.d.ts.map +1 -1
  50. package/dist/layout/Layout.js +78 -2
  51. package/dist/layout/Layout.js.map +1 -1
  52. package/dist/layout/Sidebar.d.ts +16 -0
  53. package/dist/layout/Sidebar.d.ts.map +1 -1
  54. package/dist/layout/Sidebar.js +176 -49
  55. package/dist/layout/Sidebar.js.map +1 -1
  56. package/dist/lib/page-editor-service.d.ts +45 -0
  57. package/dist/lib/page-editor-service.d.ts.map +1 -0
  58. package/dist/lib/page-editor-service.js +153 -0
  59. package/dist/lib/page-editor-service.js.map +1 -0
  60. package/dist/lib/pages-service.d.ts +143 -0
  61. package/dist/lib/pages-service.d.ts.map +1 -0
  62. package/dist/lib/pages-service.js +444 -0
  63. package/dist/lib/pages-service.js.map +1 -0
  64. package/dist/lib/post-editor-service.d.ts +57 -0
  65. package/dist/lib/post-editor-service.d.ts.map +1 -0
  66. package/dist/lib/post-editor-service.js +234 -0
  67. package/dist/lib/post-editor-service.js.map +1 -0
  68. package/dist/lib/posts-service.d.ts +73 -0
  69. package/dist/lib/posts-service.d.ts.map +1 -0
  70. package/dist/lib/posts-service.js +245 -0
  71. package/dist/lib/posts-service.js.map +1 -0
  72. package/dist/lib/preview-link.d.ts +32 -0
  73. package/dist/lib/preview-link.d.ts.map +1 -0
  74. package/dist/lib/preview-link.js +40 -0
  75. package/dist/lib/preview-link.js.map +1 -0
  76. package/dist/router/index.d.ts +1 -0
  77. package/dist/router/index.d.ts.map +1 -1
  78. package/dist/router/index.js +9 -1
  79. package/dist/router/index.js.map +1 -1
  80. package/dist/views/Dashboard.d.ts.map +1 -1
  81. package/dist/views/Dashboard.js +147 -62
  82. package/dist/views/Dashboard.js.map +1 -1
  83. package/dist/views/Pages/PageTagManager.d.ts +14 -0
  84. package/dist/views/Pages/PageTagManager.d.ts.map +1 -0
  85. package/dist/views/Pages/PageTagManager.js +145 -0
  86. package/dist/views/Pages/PageTagManager.js.map +1 -0
  87. package/dist/views/Pages/PagesListView.d.ts +18 -0
  88. package/dist/views/Pages/PagesListView.d.ts.map +1 -0
  89. package/dist/views/Pages/PagesListView.js +287 -0
  90. package/dist/views/Pages/PagesListView.js.map +1 -0
  91. package/dist/views/Posts/NewPostPicker.d.ts +16 -0
  92. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -0
  93. package/dist/views/Posts/NewPostPicker.js +59 -0
  94. package/dist/views/Posts/NewPostPicker.js.map +1 -0
  95. package/dist/views/Posts/NewPostTypeInstructions.d.ts +18 -0
  96. package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -0
  97. package/dist/views/Posts/NewPostTypeInstructions.js +147 -0
  98. package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -0
  99. package/dist/views/Posts/PostTypesView.d.ts +26 -0
  100. package/dist/views/Posts/PostTypesView.d.ts.map +1 -0
  101. package/dist/views/Posts/PostTypesView.js +52 -0
  102. package/dist/views/Posts/PostTypesView.js.map +1 -0
  103. package/dist/views/Posts/PostsListView.d.ts +25 -0
  104. package/dist/views/Posts/PostsListView.d.ts.map +1 -0
  105. package/dist/views/Posts/PostsListView.js +239 -0
  106. package/dist/views/Posts/PostsListView.js.map +1 -0
  107. package/dist/views/Posts/post-type-presets.d.ts +32 -0
  108. package/dist/views/Posts/post-type-presets.d.ts.map +1 -0
  109. package/dist/views/Posts/post-type-presets.js +72 -0
  110. package/dist/views/Posts/post-type-presets.js.map +1 -0
  111. package/dist/views/page-builder/Widgets.d.ts +13 -0
  112. package/dist/views/page-builder/Widgets.d.ts.map +1 -0
  113. package/dist/views/page-builder/Widgets.js +15 -0
  114. package/dist/views/page-builder/Widgets.js.map +1 -0
  115. package/dist/views/page-editor/AddSectionModal.d.ts +20 -0
  116. package/dist/views/page-editor/AddSectionModal.d.ts.map +1 -0
  117. package/dist/views/page-editor/AddSectionModal.js +22 -0
  118. package/dist/views/page-editor/AddSectionModal.js.map +1 -0
  119. package/dist/views/page-editor/EditorCanvas.d.ts +18 -0
  120. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -0
  121. package/dist/views/page-editor/EditorCanvas.js +64 -0
  122. package/dist/views/page-editor/EditorCanvas.js.map +1 -0
  123. package/dist/views/page-editor/EditorTopBar.d.ts +30 -0
  124. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -0
  125. package/dist/views/page-editor/EditorTopBar.js +42 -0
  126. package/dist/views/page-editor/EditorTopBar.js.map +1 -0
  127. package/dist/views/page-editor/PagePreview.d.ts +13 -0
  128. package/dist/views/page-editor/PagePreview.d.ts.map +1 -0
  129. package/dist/views/page-editor/PagePreview.js +37 -0
  130. package/dist/views/page-editor/PagePreview.js.map +1 -0
  131. package/dist/views/page-editor/PageSectionEditor.d.ts +16 -0
  132. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -0
  133. package/dist/views/page-editor/PageSectionEditor.js +272 -0
  134. package/dist/views/page-editor/PageSectionEditor.js.map +1 -0
  135. package/dist/views/page-editor/PageSettingsModal.d.ts +12 -0
  136. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -0
  137. package/dist/views/page-editor/PageSettingsModal.js +52 -0
  138. package/dist/views/page-editor/PageSettingsModal.js.map +1 -0
  139. package/dist/views/page-editor/SectionInspector.d.ts +17 -0
  140. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -0
  141. package/dist/views/page-editor/SectionInspector.js +98 -0
  142. package/dist/views/page-editor/SectionInspector.js.map +1 -0
  143. package/dist/views/page-editor/SectionsPanel.d.ts +15 -0
  144. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -0
  145. package/dist/views/page-editor/SectionsPanel.js +43 -0
  146. package/dist/views/page-editor/SectionsPanel.js.map +1 -0
  147. package/dist/views/page-editor/ValidationSummary.d.ts +9 -0
  148. package/dist/views/page-editor/ValidationSummary.d.ts.map +1 -0
  149. package/dist/views/page-editor/ValidationSummary.js +10 -0
  150. package/dist/views/page-editor/ValidationSummary.js.map +1 -0
  151. package/dist/views/page-editor/section-icons.d.ts +4 -0
  152. package/dist/views/page-editor/section-icons.d.ts.map +1 -0
  153. package/dist/views/page-editor/section-icons.js +20 -0
  154. package/dist/views/page-editor/section-icons.js.map +1 -0
  155. package/dist/views/page-editor/section-types.d.ts +11 -0
  156. package/dist/views/page-editor/section-types.d.ts.map +1 -0
  157. package/dist/views/page-editor/section-types.js +10 -0
  158. package/dist/views/page-editor/section-types.js.map +1 -0
  159. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +13 -0
  160. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -0
  161. package/dist/views/page-editor/sections/ArticleBodySection.js +23 -0
  162. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -0
  163. package/dist/views/page-editor/sections/AuthorBioSection.d.ts +8 -0
  164. package/dist/views/page-editor/sections/AuthorBioSection.d.ts.map +1 -0
  165. package/dist/views/page-editor/sections/AuthorBioSection.js +21 -0
  166. package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -0
  167. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts +5 -0
  168. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -0
  169. package/dist/views/page-editor/sections/ByTheNumbersSection.js +10 -0
  170. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -0
  171. package/dist/views/page-editor/sections/CtaBannerSection.d.ts +5 -0
  172. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -0
  173. package/dist/views/page-editor/sections/CtaBannerSection.js +10 -0
  174. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -0
  175. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts +5 -0
  176. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -0
  177. package/dist/views/page-editor/sections/FeatureHighlightSection.js +18 -0
  178. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -0
  179. package/dist/views/page-editor/sections/GallerySection.d.ts +6 -0
  180. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -0
  181. package/dist/views/page-editor/sections/GallerySection.js +17 -0
  182. package/dist/views/page-editor/sections/GallerySection.js.map +1 -0
  183. package/dist/views/page-editor/sections/HeroBannerSection.d.ts +5 -0
  184. package/dist/views/page-editor/sections/HeroBannerSection.d.ts.map +1 -0
  185. package/dist/views/page-editor/sections/HeroBannerSection.js +11 -0
  186. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -0
  187. package/dist/views/page-editor/sections/MissionSection.d.ts +6 -0
  188. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -0
  189. package/dist/views/page-editor/sections/MissionSection.js +12 -0
  190. package/dist/views/page-editor/sections/MissionSection.js.map +1 -0
  191. package/dist/views/page-editor/sections/PullQuoteSection.d.ts +6 -0
  192. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -0
  193. package/dist/views/page-editor/sections/PullQuoteSection.js +13 -0
  194. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -0
  195. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts +12 -0
  196. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -0
  197. package/dist/views/page-editor/sections/RelatedPostsSection.js +23 -0
  198. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -0
  199. package/dist/views/page-editor/sections/SectionRenderer.d.ts +22 -0
  200. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -0
  201. package/dist/views/page-editor/sections/SectionRenderer.js +52 -0
  202. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -0
  203. package/dist/views/page-editor/sections/index.d.ts +14 -0
  204. package/dist/views/page-editor/sections/index.d.ts.map +1 -0
  205. package/dist/views/page-editor/sections/index.js +12 -0
  206. package/dist/views/page-editor/sections/index.js.map +1 -0
  207. package/dist/views/page-editor/sections/parts.d.ts +55 -0
  208. package/dist/views/page-editor/sections/parts.d.ts.map +1 -0
  209. package/dist/views/page-editor/sections/parts.js +60 -0
  210. package/dist/views/page-editor/sections/parts.js.map +1 -0
  211. package/dist/views/page-editor/viewports.d.ts +14 -0
  212. package/dist/views/page-editor/viewports.d.ts.map +1 -0
  213. package/dist/views/page-editor/viewports.js +12 -0
  214. package/dist/views/page-editor/viewports.js.map +1 -0
  215. package/dist/views/post-editor/PostEditorCanvas.d.ts +21 -0
  216. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -0
  217. package/dist/views/post-editor/PostEditorCanvas.js +58 -0
  218. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -0
  219. package/dist/views/post-editor/PostFieldsModal.d.ts +12 -0
  220. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -0
  221. package/dist/views/post-editor/PostFieldsModal.js +53 -0
  222. package/dist/views/post-editor/PostFieldsModal.js.map +1 -0
  223. package/dist/views/post-editor/PostHeader.d.ts +15 -0
  224. package/dist/views/post-editor/PostHeader.d.ts.map +1 -0
  225. package/dist/views/post-editor/PostHeader.js +27 -0
  226. package/dist/views/post-editor/PostHeader.js.map +1 -0
  227. package/dist/views/post-editor/PostHeaderPanel.d.ts +11 -0
  228. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -0
  229. package/dist/views/post-editor/PostHeaderPanel.js +21 -0
  230. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -0
  231. package/dist/views/post-editor/PostPreview.d.ts +14 -0
  232. package/dist/views/post-editor/PostPreview.d.ts.map +1 -0
  233. package/dist/views/post-editor/PostPreview.js +53 -0
  234. package/dist/views/post-editor/PostPreview.js.map +1 -0
  235. package/dist/views/post-editor/PostSectionEditor.d.ts +17 -0
  236. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -0
  237. package/dist/views/post-editor/PostSectionEditor.js +290 -0
  238. package/dist/views/post-editor/PostSectionEditor.js.map +1 -0
  239. package/dist/views/post-editor/PostTemplateEditor.d.ts +9 -0
  240. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -0
  241. package/dist/views/post-editor/PostTemplateEditor.js +144 -0
  242. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -0
  243. package/package.json +2 -2
  244. package/src/AdminRoot.tsx +213 -2
  245. package/src/__tests__/components/posts-badges.test.tsx +77 -0
  246. package/src/__tests__/layout/sidebar-posts.test.ts +179 -0
  247. package/src/__tests__/lib/page-editor-service.test.ts +173 -0
  248. package/src/__tests__/lib/pages-service.test.ts +157 -0
  249. package/src/__tests__/lib/post-editor-service.test.ts +215 -0
  250. package/src/__tests__/lib/posts-service.test.ts +279 -0
  251. package/src/__tests__/views/posts-list-view.test.ts +110 -0
  252. package/src/components/pages/tag-badges.tsx +104 -0
  253. package/src/components/posts/badges.tsx +170 -0
  254. package/src/components/posts/icons.ts +40 -0
  255. package/src/layout/Header.tsx +90 -43
  256. package/src/layout/Layout.tsx +166 -1
  257. package/src/layout/Sidebar.tsx +287 -101
  258. package/src/lib/page-editor-service.ts +233 -0
  259. package/src/lib/pages-service.ts +599 -0
  260. package/src/lib/post-editor-service.ts +342 -0
  261. package/src/lib/posts-service.ts +366 -0
  262. package/src/lib/preview-link.ts +63 -0
  263. package/src/router/index.ts +10 -1
  264. package/src/views/Dashboard.tsx +270 -131
  265. package/src/views/Pages/PageTagManager.tsx +436 -0
  266. package/src/views/Pages/PagesListView.tsx +944 -0
  267. package/src/views/Posts/NewPostPicker.tsx +147 -0
  268. package/src/views/Posts/NewPostTypeInstructions.tsx +344 -0
  269. package/src/views/Posts/PostTypesView.tsx +194 -0
  270. package/src/views/Posts/PostsListView.tsx +869 -0
  271. package/src/views/Posts/post-type-presets.ts +105 -0
  272. package/src/views/page-builder/Widgets.tsx +44 -0
  273. package/src/views/page-editor/AddSectionModal.tsx +56 -0
  274. package/src/views/page-editor/EditorCanvas.tsx +127 -0
  275. package/src/views/page-editor/EditorTopBar.tsx +233 -0
  276. package/src/views/page-editor/PagePreview.tsx +96 -0
  277. package/src/views/page-editor/PageSectionEditor.tsx +476 -0
  278. package/src/views/page-editor/PageSettingsModal.tsx +154 -0
  279. package/src/views/page-editor/SectionInspector.tsx +599 -0
  280. package/src/views/page-editor/SectionsPanel.tsx +270 -0
  281. package/src/views/page-editor/ValidationSummary.tsx +53 -0
  282. package/src/views/page-editor/section-icons.ts +33 -0
  283. package/src/views/page-editor/section-types.ts +27 -0
  284. package/src/views/page-editor/sections/ArticleBodySection.tsx +57 -0
  285. package/src/views/page-editor/sections/AuthorBioSection.tsx +61 -0
  286. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +41 -0
  287. package/src/views/page-editor/sections/CtaBannerSection.tsx +29 -0
  288. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +63 -0
  289. package/src/views/page-editor/sections/GallerySection.tsx +54 -0
  290. package/src/views/page-editor/sections/HeroBannerSection.tsx +44 -0
  291. package/src/views/page-editor/sections/MissionSection.tsx +22 -0
  292. package/src/views/page-editor/sections/PullQuoteSection.tsx +27 -0
  293. package/src/views/page-editor/sections/RelatedPostsSection.tsx +55 -0
  294. package/src/views/page-editor/sections/SectionRenderer.tsx +102 -0
  295. package/src/views/page-editor/sections/index.ts +13 -0
  296. package/src/views/page-editor/sections/parts.tsx +113 -0
  297. package/src/views/page-editor/viewports.ts +25 -0
  298. package/src/views/post-editor/PostEditorCanvas.tsx +123 -0
  299. package/src/views/post-editor/PostFieldsModal.tsx +196 -0
  300. package/src/views/post-editor/PostHeader.tsx +87 -0
  301. package/src/views/post-editor/PostHeaderPanel.tsx +107 -0
  302. package/src/views/post-editor/PostPreview.tsx +114 -0
  303. package/src/views/post-editor/PostSectionEditor.tsx +506 -0
  304. package/src/views/post-editor/PostTemplateEditor.tsx +347 -0
@@ -0,0 +1,105 @@
1
+ import type { PostTypeIconName } from '../../components/posts/icons.js'
2
+
3
+ /**
4
+ * Quick Post Type presets — curated starter templates surfaced on the
5
+ * New Post Type screen. Picking one pre-fills the form (slug, labels,
6
+ * icon, accent color, description) AND a tailored set of extra fields
7
+ * that get injected into the generated `actuate.config.ts` snippet
8
+ * after the shared base fields (title / slug / body / SEO / …).
9
+ *
10
+ * Presets are NOT a runtime data model — they only seed the snippet.
11
+ * Collections stay code-defined; this just saves the developer from
12
+ * hand-authoring common field sets.
13
+ */
14
+
15
+ export type PresetColor = 'purple' | 'green' | 'orange' | 'blue' | 'red' | 'pink'
16
+
17
+ export interface QuickPreset {
18
+ key: string
19
+ /** Gallery card label, e.g. "Case Studies". */
20
+ label: string
21
+ singular: string
22
+ plural: string
23
+ slug: string
24
+ description: string
25
+ color: PresetColor
26
+ icon: PostTypeIconName
27
+ /**
28
+ * Field definition lines injected after the shared base fields.
29
+ * Each entry is a complete `key: { … },` line (no leading indent —
30
+ * the snippet generator indents them).
31
+ */
32
+ extraFields: string[]
33
+ }
34
+
35
+ export const POST_TYPE_PRESETS: QuickPreset[] = [
36
+ {
37
+ key: 'case-studies',
38
+ label: 'Case Studies',
39
+ singular: 'Case Study',
40
+ plural: 'Case Studies',
41
+ slug: 'case-studies',
42
+ description: 'Client success stories and detailed project case studies.',
43
+ color: 'green',
44
+ icon: 'briefcase',
45
+ extraFields: [
46
+ "client: { type: 'text', label: 'Client Name' },",
47
+ "industry: { type: 'text', label: 'Industry' },",
48
+ "results: { type: 'richText', label: 'Results & Outcomes' },",
49
+ ],
50
+ },
51
+ {
52
+ key: 'news',
53
+ label: 'News',
54
+ singular: 'News Item',
55
+ plural: 'News',
56
+ slug: 'news',
57
+ description: 'Company announcements, press releases, and industry news.',
58
+ color: 'orange',
59
+ icon: 'newspaper',
60
+ extraFields: ["source: { type: 'text', label: 'Source' },"],
61
+ },
62
+ {
63
+ key: 'portfolio',
64
+ label: 'Portfolio',
65
+ singular: 'Portfolio Item',
66
+ plural: 'Portfolio',
67
+ slug: 'portfolio',
68
+ description: 'Showcase projects, work samples, and creative pieces.',
69
+ color: 'blue',
70
+ icon: 'image',
71
+ extraFields: [
72
+ "projectUrl: { type: 'url', label: 'Project URL' },",
73
+ "gallery: { type: 'media', label: 'Gallery' },",
74
+ ],
75
+ },
76
+ {
77
+ key: 'events',
78
+ label: 'Events',
79
+ singular: 'Event',
80
+ plural: 'Events',
81
+ slug: 'events',
82
+ description: 'Webinars, conferences, and scheduled happenings.',
83
+ color: 'pink',
84
+ icon: 'calendar',
85
+ extraFields: [
86
+ "startDate: { type: 'date', label: 'Start Date', includeTime: true },",
87
+ "endDate: { type: 'date', label: 'End Date', includeTime: true },",
88
+ "location: { type: 'text', label: 'Location' },",
89
+ ],
90
+ },
91
+ {
92
+ key: 'team',
93
+ label: 'Team Members',
94
+ singular: 'Team Member',
95
+ plural: 'Team',
96
+ slug: 'team',
97
+ description: 'Staff bios, headshots, and role information.',
98
+ color: 'purple',
99
+ icon: 'users',
100
+ extraFields: [
101
+ "role: { type: 'text', label: 'Role' },",
102
+ "headshot: { type: 'media', label: 'Headshot' },",
103
+ ],
104
+ },
105
+ ]
@@ -0,0 +1,44 @@
1
+ 'use client'
2
+
3
+ import { LayoutGrid } from 'lucide-react'
4
+
5
+ export interface WidgetsProps {
6
+ onNavigate?: (path: string) => void
7
+ }
8
+
9
+ /**
10
+ * /widgets — placeholder for the embeddable Widgets library.
11
+ *
12
+ * Widgets are reusable, embeddable components (forms, CTAs, banners)
13
+ * that can be dropped into pages and saved sections. The management UI
14
+ * ships in a later release; this view establishes the route and nav
15
+ * entry with an honest empty state rather than a dead link.
16
+ */
17
+ export function Widgets({ onNavigate }: WidgetsProps) {
18
+ return (
19
+ <div className="p-4 pr-8">
20
+ <div className="mb-4">
21
+ <h1 className="text-foreground mb-1 text-2xl font-medium">Widgets</h1>
22
+ <p className="text-muted-foreground text-sm">
23
+ Reusable, embeddable components for pages and saved sections.
24
+ </p>
25
+ </div>
26
+
27
+ <div className="border-border bg-card rounded-lg border p-8 text-center">
28
+ <LayoutGrid className="text-muted-foreground mx-auto mb-3 h-8 w-8" aria-hidden />
29
+ <h2 className="text-foreground mb-1 text-lg font-medium">Widgets are coming soon</h2>
30
+ <p className="text-muted-foreground mx-auto mb-4 max-w-md text-sm">
31
+ The widget library lets you build embeddable components once and reuse them across pages.
32
+ In the meantime, use Saved Sections for reusable content blocks.
33
+ </p>
34
+ <button
35
+ type="button"
36
+ onClick={() => onNavigate?.('/saved-sections')}
37
+ className="bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm font-medium hover:opacity-90"
38
+ >
39
+ View Saved Sections
40
+ </button>
41
+ </div>
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1,56 @@
1
+ 'use client'
2
+
3
+ import { Modal } from '../../components/ui/Modal.js'
4
+ import { getSectionTypesForScope, type SectionScope, type SectionTypeId } from './section-types.js'
5
+ import { sectionIconFor } from './section-icons.js'
6
+
7
+ interface AddSectionModalProps {
8
+ open: boolean
9
+ onClose: () => void
10
+ onAdd: (type: SectionTypeId) => void
11
+ /** Which editor surface this gallery serves. Defaults to the page editor. */
12
+ scope?: SectionScope
13
+ }
14
+
15
+ /**
16
+ * Predefined section gallery. Only types that have a working renderer +
17
+ * inspector are listed (the registry is the source of truth), so every
18
+ * card here actually inserts a usable section.
19
+ *
20
+ * (Saved-section / template insertion is a separate flow — deferred until
21
+ * the saved-section storage model is reconciled with the flat section
22
+ * model; see the page-editor changeset.)
23
+ */
24
+ export function AddSectionModal({ open, onClose, onAdd, scope = 'page' }: AddSectionModalProps) {
25
+ const types = getSectionTypesForScope(scope)
26
+ return (
27
+ <Modal open={open} onClose={onClose} title="Add a section">
28
+ <p className="text-muted-foreground mb-4 text-sm">
29
+ Choose a section type. It will be added to the bottom and selected for editing.
30
+ </p>
31
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
32
+ {types.map((type) => {
33
+ const Icon = sectionIconFor(type.id)
34
+ return (
35
+ <button
36
+ key={type.id}
37
+ type="button"
38
+ onClick={() => onAdd(type.id)}
39
+ className="border-border hover:border-primary hover:bg-accent/50 focus-visible:ring-ring flex items-start gap-3 rounded-lg border p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none"
40
+ >
41
+ <span className="bg-muted text-foreground flex h-9 w-9 shrink-0 items-center justify-center rounded-md">
42
+ <Icon className="h-5 w-5" aria-hidden />
43
+ </span>
44
+ <span className="min-w-0">
45
+ <span className="text-foreground block text-sm font-medium">{type.name}</span>
46
+ <span className="text-muted-foreground mt-0.5 block text-xs leading-snug">
47
+ {type.description}
48
+ </span>
49
+ </span>
50
+ </button>
51
+ )
52
+ })}
53
+ </div>
54
+ </Modal>
55
+ )
56
+ }
@@ -0,0 +1,127 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
4
+ import { LayoutTemplate, Plus } from 'lucide-react'
5
+ import type { PageSection } from '../../lib/page-editor-service.js'
6
+ import { SectionRenderer } from './sections/index.js'
7
+ import { getViewport, type ViewportId } from './viewports.js'
8
+
9
+ interface EditorCanvasProps {
10
+ sections: PageSection[]
11
+ selectedId: string | null
12
+ viewport: ViewportId
13
+ onSelect: (id: string) => void
14
+ onAddSection: () => void
15
+ }
16
+
17
+ /**
18
+ * The preview canvas. Renders the page at the selected viewport's logical
19
+ * width and scales it down with a CSS transform so wide viewports (1440px,
20
+ * 1920px) fit inside the available canvas column. Scale is recomputed from
21
+ * a ResizeObserver so it stays correct as panels open/close.
22
+ */
23
+ export function EditorCanvas({
24
+ sections,
25
+ selectedId,
26
+ viewport,
27
+ onSelect,
28
+ onAddSection,
29
+ }: EditorCanvasProps) {
30
+ const vp = getViewport(viewport)
31
+ const containerRef = useRef<HTMLDivElement>(null)
32
+ const surfaceRef = useRef<HTMLDivElement>(null)
33
+ const sectionRefs = useRef<Map<string, HTMLElement>>(new Map())
34
+ const [scale, setScale] = useState(1)
35
+ const [surfaceHeight, setSurfaceHeight] = useState<number | null>(null)
36
+
37
+ const registerRef = useCallback((id: string, el: HTMLElement | null) => {
38
+ if (el) sectionRefs.current.set(id, el)
39
+ else sectionRefs.current.delete(id)
40
+ }, [])
41
+
42
+ // Recompute scale + scaled height from the container width.
43
+ useLayoutEffect(() => {
44
+ const container = containerRef.current
45
+ if (!container) return
46
+
47
+ const recompute = () => {
48
+ // 48px breathing room so the surface never kisses the column edges.
49
+ const available = container.clientWidth - 48
50
+ const next = Math.min(1, available / vp.width)
51
+ setScale(next > 0 ? next : 1)
52
+ if (surfaceRef.current) {
53
+ setSurfaceHeight(surfaceRef.current.offsetHeight * (next > 0 ? next : 1))
54
+ }
55
+ }
56
+
57
+ recompute()
58
+ const ro = new ResizeObserver(recompute)
59
+ ro.observe(container)
60
+ if (surfaceRef.current) ro.observe(surfaceRef.current)
61
+ return () => ro.disconnect()
62
+ }, [vp.width, sections])
63
+
64
+ // Scroll the selected section into view within the canvas.
65
+ useEffect(() => {
66
+ if (!selectedId) return
67
+ const el = sectionRefs.current.get(selectedId)
68
+ if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
69
+ }, [selectedId])
70
+
71
+ return (
72
+ <div ref={containerRef} className="bg-muted h-full overflow-auto p-6">
73
+ {sections.length === 0 ? (
74
+ <EmptyCanvas onAddSection={onAddSection} />
75
+ ) : (
76
+ <div
77
+ className="mx-auto"
78
+ style={{ width: vp.width * scale, height: surfaceHeight ?? undefined }}
79
+ >
80
+ <div
81
+ ref={surfaceRef}
82
+ className="border-border origin-top overflow-hidden rounded-xl border bg-white shadow-sm"
83
+ style={{
84
+ width: vp.width,
85
+ transform: `scale(${scale})`,
86
+ transformOrigin: 'top left',
87
+ }}
88
+ >
89
+ {sections.map((section) => (
90
+ <SectionRenderer
91
+ key={section.id}
92
+ section={section}
93
+ editable
94
+ selected={section.id === selectedId}
95
+ onSelect={onSelect}
96
+ registerRef={registerRef}
97
+ />
98
+ ))}
99
+ </div>
100
+ </div>
101
+ )}
102
+ </div>
103
+ )
104
+ }
105
+
106
+ function EmptyCanvas({ onAddSection }: { onAddSection: () => void }) {
107
+ return (
108
+ <div className="mx-auto flex h-full max-w-md flex-col items-center justify-center text-center">
109
+ <div className="bg-card border-border mb-4 flex h-14 w-14 items-center justify-center rounded-full border">
110
+ <LayoutTemplate className="text-muted-foreground h-6 w-6" aria-hidden />
111
+ </div>
112
+ <h2 className="text-foreground text-lg font-medium">This page has no sections yet</h2>
113
+ <p className="text-muted-foreground mt-1 mb-5 text-sm">
114
+ Add your first section to start building the page. You can reorder, hide, and edit sections
115
+ at any time.
116
+ </p>
117
+ <button
118
+ type="button"
119
+ onClick={onAddSection}
120
+ className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors"
121
+ >
122
+ <Plus className="h-4 w-4" aria-hidden />
123
+ Add Section
124
+ </button>
125
+ </div>
126
+ )
127
+ }
@@ -0,0 +1,233 @@
1
+ 'use client'
2
+
3
+ import {
4
+ Check,
5
+ ExternalLink,
6
+ Eye,
7
+ Laptop,
8
+ Loader2,
9
+ type LucideIcon,
10
+ Monitor,
11
+ Settings2,
12
+ Smartphone,
13
+ Tablet,
14
+ TriangleAlert,
15
+ } from 'lucide-react'
16
+ import type { PageStatus } from '../../lib/page-editor-service.js'
17
+ import { VIEWPORTS, type ViewportId } from './viewports.js'
18
+
19
+ export type SaveState = 'idle' | 'saving' | 'saved' | 'error'
20
+
21
+ interface EditorTopBarProps {
22
+ title: string
23
+ status: PageStatus
24
+ dirty: boolean
25
+ saveState: SaveState
26
+ publishing: boolean
27
+ canEdit: boolean
28
+ canPublish: boolean
29
+ viewport: ViewportId
30
+ /** Breadcrumb root label + back-button text (e.g. "Pages" or "Posts"). */
31
+ backLabel?: string
32
+ onViewport: (v: ViewportId) => void
33
+ onBack: () => void
34
+ onPreview: () => void
35
+ /**
36
+ * Open the draft on the public site (real front-end) in a new tab. When
37
+ * omitted the "View on site" button is hidden — e.g. for unsaved documents
38
+ * or installs without a public URL.
39
+ */
40
+ onViewOnSite?: () => void
41
+ onOpenSettings: () => void
42
+ onSaveDraft: () => void
43
+ onPublish: () => void
44
+ }
45
+
46
+ const VIEWPORT_ICONS: Record<string, LucideIcon> = {
47
+ monitor: Monitor,
48
+ laptop: Laptop,
49
+ tablet: Tablet,
50
+ smartphone: Smartphone,
51
+ }
52
+
53
+ const STATUS_TEXT: Record<PageStatus, { label: string; className: string }> = {
54
+ DRAFT: { label: 'Draft', className: 'text-amber-600 dark:text-amber-400' },
55
+ PUBLISHED: { label: 'Published', className: 'text-emerald-600 dark:text-emerald-400' },
56
+ SCHEDULED: { label: 'Scheduled', className: 'text-amber-600 dark:text-amber-400' },
57
+ ARCHIVED: { label: 'Archived', className: 'text-muted-foreground' },
58
+ }
59
+
60
+ export function EditorTopBar({
61
+ title,
62
+ status,
63
+ dirty,
64
+ saveState,
65
+ publishing,
66
+ canEdit,
67
+ canPublish,
68
+ viewport,
69
+ backLabel = 'Pages',
70
+ onViewport,
71
+ onBack,
72
+ onPreview,
73
+ onViewOnSite,
74
+ onOpenSettings,
75
+ onSaveDraft,
76
+ onPublish,
77
+ }: EditorTopBarProps) {
78
+ const statusInfo = STATUS_TEXT[status]
79
+
80
+ return (
81
+ <header className="bg-card border-border flex h-14 shrink-0 items-center justify-between gap-4 border-b px-4">
82
+ {/* Breadcrumb + status */}
83
+ <div className="flex min-w-0 items-center gap-2">
84
+ <nav aria-label="Breadcrumb" className="flex min-w-0 items-center gap-1.5 text-sm">
85
+ <button
86
+ type="button"
87
+ onClick={onBack}
88
+ className="text-muted-foreground hover:text-foreground shrink-0"
89
+ >
90
+ {backLabel}
91
+ </button>
92
+ <span className="text-muted-foreground" aria-hidden>
93
+ /
94
+ </span>
95
+ <span className="text-foreground truncate font-medium">{title || 'Untitled page'}</span>
96
+ </nav>
97
+ <span
98
+ className={`ml-2 shrink-0 text-xs font-medium ${statusInfo.className}`}
99
+ aria-label={`Status: ${statusInfo.label}`}
100
+ >
101
+ {statusInfo.label}
102
+ </span>
103
+ <SaveStatus state={saveState} dirty={dirty} />
104
+ </div>
105
+
106
+ {/* Right controls */}
107
+ <div className="flex items-center gap-2">
108
+ <div
109
+ className="border-border bg-background hidden items-center gap-0.5 rounded-lg border p-0.5 md:flex"
110
+ role="group"
111
+ aria-label="Preview viewport"
112
+ >
113
+ {VIEWPORTS.map((v) => {
114
+ const Icon = VIEWPORT_ICONS[v.icon] ?? Monitor
115
+ const active = v.id === viewport
116
+ return (
117
+ <button
118
+ key={v.id}
119
+ type="button"
120
+ onClick={() => onViewport(v.id)}
121
+ aria-pressed={active}
122
+ title={`${v.label} (${v.width}px)`}
123
+ className={`flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors ${
124
+ active
125
+ ? 'bg-primary text-primary-foreground'
126
+ : 'text-muted-foreground hover:text-foreground hover:bg-accent'
127
+ }`}
128
+ >
129
+ <Icon className="h-3.5 w-3.5" aria-hidden />
130
+ <span className="hidden lg:inline">{v.label}</span>
131
+ </button>
132
+ )
133
+ })}
134
+ </div>
135
+
136
+ <button
137
+ type="button"
138
+ onClick={onOpenSettings}
139
+ aria-label="Page settings"
140
+ title="Page settings"
141
+ className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg p-2"
142
+ >
143
+ <Settings2 className="h-4 w-4" aria-hidden />
144
+ </button>
145
+
146
+ <button
147
+ type="button"
148
+ onClick={onPreview}
149
+ aria-label="Preview page"
150
+ title="Preview page"
151
+ className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg p-2"
152
+ >
153
+ <Eye className="h-4 w-4" aria-hidden />
154
+ </button>
155
+
156
+ {onViewOnSite && (
157
+ <button
158
+ type="button"
159
+ onClick={onViewOnSite}
160
+ aria-label="View draft on site"
161
+ title="View draft on site"
162
+ className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg p-2"
163
+ >
164
+ <ExternalLink className="h-4 w-4" aria-hidden />
165
+ </button>
166
+ )}
167
+
168
+ <button
169
+ type="button"
170
+ onClick={onSaveDraft}
171
+ disabled={!canEdit || saveState === 'saving' || (!dirty && saveState !== 'error')}
172
+ className="border-border text-foreground hover:bg-accent rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
173
+ >
174
+ {saveState === 'saving' ? 'Saving…' : 'Save Draft'}
175
+ </button>
176
+
177
+ <button
178
+ type="button"
179
+ onClick={onPublish}
180
+ disabled={!canPublish || publishing || saveState === 'saving'}
181
+ className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
182
+ >
183
+ {publishing && <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />}
184
+ Publish
185
+ </button>
186
+ </div>
187
+ </header>
188
+ )
189
+ }
190
+
191
+ function SaveStatus({ state, dirty }: { state: SaveState; dirty: boolean }) {
192
+ if (state === 'saving') {
193
+ return (
194
+ <span
195
+ className="text-muted-foreground ml-2 inline-flex items-center gap-1 text-xs"
196
+ aria-live="polite"
197
+ >
198
+ <Loader2 className="h-3 w-3 animate-spin" aria-hidden />
199
+ Saving…
200
+ </span>
201
+ )
202
+ }
203
+ if (state === 'error') {
204
+ return (
205
+ <span
206
+ className="text-destructive ml-2 inline-flex items-center gap-1 text-xs"
207
+ aria-live="polite"
208
+ >
209
+ <TriangleAlert className="h-3 w-3" aria-hidden />
210
+ Failed to save
211
+ </span>
212
+ )
213
+ }
214
+ if (dirty) {
215
+ return (
216
+ <span className="text-muted-foreground ml-2 text-xs" aria-live="polite">
217
+ Unsaved changes
218
+ </span>
219
+ )
220
+ }
221
+ if (state === 'saved') {
222
+ return (
223
+ <span
224
+ className="text-muted-foreground ml-2 inline-flex items-center gap-1 text-xs"
225
+ aria-live="polite"
226
+ >
227
+ <Check className="h-3 w-3" aria-hidden />
228
+ Saved
229
+ </span>
230
+ )
231
+ }
232
+ return null
233
+ }
@@ -0,0 +1,96 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+ import { AlertTriangle, Loader2, Pencil, X } from 'lucide-react'
5
+ import { fetchPageForEditor, type EditorPage } from '../../lib/page-editor-service.js'
6
+ import { SectionRenderer } from './sections/index.js'
7
+
8
+ interface PagePreviewProps {
9
+ documentId: string
10
+ onNavigate: (path: string) => void
11
+ }
12
+
13
+ /**
14
+ * Clean, chrome-free render of a page using the SAME section renderers as
15
+ * the editor canvas — so the preview reflects what will actually ship.
16
+ * Only visible sections are rendered (hidden sections are omitted, exactly
17
+ * as the public page would behave).
18
+ */
19
+ export function PagePreview({ documentId, onNavigate }: PagePreviewProps) {
20
+ const [page, setPage] = useState<EditorPage | null>(null)
21
+ const [loading, setLoading] = useState(true)
22
+ const [error, setError] = useState<string | null>(null)
23
+
24
+ useEffect(() => {
25
+ let cancelled = false
26
+ setLoading(true)
27
+ fetchPageForEditor(documentId)
28
+ .then((p) => !cancelled && setPage(p))
29
+ .catch((err) => !cancelled && setError(err instanceof Error ? err.message : 'Failed to load'))
30
+ .finally(() => !cancelled && setLoading(false))
31
+ return () => {
32
+ cancelled = true
33
+ }
34
+ }, [documentId])
35
+
36
+ if (loading) {
37
+ return (
38
+ <div className="bg-background flex h-full items-center justify-center">
39
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" aria-hidden />
40
+ <span className="sr-only">Loading preview…</span>
41
+ </div>
42
+ )
43
+ }
44
+
45
+ if (error || !page) {
46
+ return (
47
+ <div className="bg-background flex h-full items-center justify-center p-6">
48
+ <div className="border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4">
49
+ <AlertTriangle className="text-destructive shrink-0" size={20} aria-hidden />
50
+ <p className="text-foreground text-sm font-medium">{error ?? 'Page not found.'}</p>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
55
+
56
+ const visible = page.sections.filter((s) => s.visible)
57
+
58
+ return (
59
+ <div className="flex h-full flex-col">
60
+ {/* Slim preview bar — the only chrome; the page itself renders clean.
61
+ This view opens in its own browser tab, so actions are tab-scoped. */}
62
+ <div className="bg-card border-border flex h-12 shrink-0 items-center justify-between border-b px-4">
63
+ <button
64
+ type="button"
65
+ onClick={() => window.close()}
66
+ className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm"
67
+ aria-label="Close preview tab"
68
+ >
69
+ <X className="h-4 w-4" aria-hidden />
70
+ Close
71
+ </button>
72
+ <span className="text-muted-foreground text-xs">
73
+ Preview · {page.title || 'Untitled'} {page.path}
74
+ </span>
75
+ <button
76
+ type="button"
77
+ onClick={() => onNavigate(`/pages/${documentId}/edit`)}
78
+ className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium"
79
+ >
80
+ <Pencil className="h-3.5 w-3.5" aria-hidden />
81
+ Open editor
82
+ </button>
83
+ </div>
84
+
85
+ <div className="flex-1 overflow-auto bg-white">
86
+ {visible.length === 0 ? (
87
+ <div className="text-muted-foreground flex h-full items-center justify-center text-sm">
88
+ This page has no visible sections yet.
89
+ </div>
90
+ ) : (
91
+ visible.map((section) => <SectionRenderer key={section.id} section={section} />)
92
+ )}
93
+ </div>
94
+ </div>
95
+ )
96
+ }