@prenta/admin 0.98.0 → 1.0.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 (270) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +17 -28
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/seo-score-bands.test.js +0 -10
  6. package/dist/__tests__/components/seo-score-bands.test.js.map +1 -1
  7. package/dist/__tests__/layout/sidebar-posts.test.js +6 -11
  8. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
  9. package/dist/__tests__/layout/sidebar-submenu.render.test.js +3 -2
  10. package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
  11. package/dist/__tests__/lib/page-audit-input.test.js +77 -1
  12. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -1
  13. package/dist/__tests__/lib/page-editor-service-api.test.js +26 -23
  14. package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
  15. package/dist/__tests__/views/page-section-editor.test.js +0 -1
  16. package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
  17. package/dist/__tests__/views/page-tab.render.test.js +0 -1
  18. package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
  19. package/dist/index.d.ts +1 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/layout/Sidebar.d.ts +3 -4
  24. package/dist/layout/Sidebar.d.ts.map +1 -1
  25. package/dist/layout/Sidebar.js +7 -38
  26. package/dist/layout/Sidebar.js.map +1 -1
  27. package/dist/lib/page-audit-input.d.ts +14 -1
  28. package/dist/lib/page-audit-input.d.ts.map +1 -1
  29. package/dist/lib/page-audit-input.js +19 -0
  30. package/dist/lib/page-audit-input.js.map +1 -1
  31. package/dist/lib/page-editor-service.d.ts +0 -7
  32. package/dist/lib/page-editor-service.d.ts.map +1 -1
  33. package/dist/lib/page-editor-service.js +9 -9
  34. package/dist/lib/page-editor-service.js.map +1 -1
  35. package/dist/prenta-admin.css +1 -1
  36. package/dist/views/PageEditor.d.ts +2 -2
  37. package/dist/views/PageEditor.js +2 -2
  38. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  39. package/dist/views/page-editor/PageSectionEditor.js +9 -15
  40. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  41. package/package.json +4 -4
  42. package/src/AdminRoot.tsx +18 -68
  43. package/src/__tests__/components/seo-score-bands.test.ts +0 -10
  44. package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
  45. package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
  46. package/src/__tests__/lib/page-audit-input.test.ts +91 -1
  47. package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
  48. package/src/__tests__/views/page-section-editor.test.tsx +0 -1
  49. package/src/__tests__/views/page-tab.render.test.tsx +0 -1
  50. package/src/index.ts +1 -11
  51. package/src/layout/Sidebar.tsx +6 -41
  52. package/src/lib/page-audit-input.ts +36 -1
  53. package/src/lib/page-editor-service.ts +9 -16
  54. package/src/views/PageEditor.tsx +2 -2
  55. package/src/views/page-editor/PageSectionEditor.tsx +8 -37
  56. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
  57. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
  58. package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
  59. package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
  60. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
  61. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
  62. package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
  63. package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
  64. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
  65. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
  66. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
  67. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
  68. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
  69. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
  70. package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
  71. package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
  72. package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
  73. package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
  74. package/dist/__tests__/views/page-templates.render.test.js +0 -126
  75. package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
  76. package/dist/hooks/useBuilderState.d.ts +0 -49
  77. package/dist/hooks/useBuilderState.d.ts.map +0 -1
  78. package/dist/hooks/useBuilderState.js +0 -238
  79. package/dist/hooks/useBuilderState.js.map +0 -1
  80. package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
  81. package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
  82. package/dist/views/page-builder/AIBlockAssist.js +0 -40
  83. package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
  84. package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
  85. package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
  86. package/dist/views/page-builder/AIGenerateDialog.js +0 -173
  87. package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
  88. package/dist/views/page-builder/BlockEditor.d.ts +0 -11
  89. package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
  90. package/dist/views/page-builder/BlockEditor.js +0 -159
  91. package/dist/views/page-builder/BlockEditor.js.map +0 -1
  92. package/dist/views/page-builder/BlockPicker.d.ts +0 -7
  93. package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
  94. package/dist/views/page-builder/BlockPicker.js +0 -103
  95. package/dist/views/page-builder/BlockPicker.js.map +0 -1
  96. package/dist/views/page-builder/BottomBar.d.ts +0 -9
  97. package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
  98. package/dist/views/page-builder/BottomBar.js +0 -13
  99. package/dist/views/page-builder/BottomBar.js.map +0 -1
  100. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
  101. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
  102. package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
  103. package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
  104. package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
  105. package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
  106. package/dist/views/page-builder/BuilderToolbar.js +0 -18
  107. package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
  108. package/dist/views/page-builder/ContextPanel.d.ts +0 -20
  109. package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
  110. package/dist/views/page-builder/ContextPanel.js +0 -43
  111. package/dist/views/page-builder/ContextPanel.js.map +0 -1
  112. package/dist/views/page-builder/DesignScore.d.ts +0 -6
  113. package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
  114. package/dist/views/page-builder/DesignScore.js +0 -93
  115. package/dist/views/page-builder/DesignScore.js.map +0 -1
  116. package/dist/views/page-builder/NodeSettings.d.ts +0 -12
  117. package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
  118. package/dist/views/page-builder/NodeSettings.js +0 -80
  119. package/dist/views/page-builder/NodeSettings.js.map +0 -1
  120. package/dist/views/page-builder/PageBuilder.d.ts +0 -10
  121. package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
  122. package/dist/views/page-builder/PageBuilder.js +0 -222
  123. package/dist/views/page-builder/PageBuilder.js.map +0 -1
  124. package/dist/views/page-builder/PageSettings.d.ts +0 -7
  125. package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
  126. package/dist/views/page-builder/PageSettings.js +0 -27
  127. package/dist/views/page-builder/PageSettings.js.map +0 -1
  128. package/dist/views/page-builder/PageTemplates.d.ts +0 -5
  129. package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
  130. package/dist/views/page-builder/PageTemplates.js +0 -146
  131. package/dist/views/page-builder/PageTemplates.js.map +0 -1
  132. package/dist/views/page-builder/RelationshipField.d.ts +0 -15
  133. package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
  134. package/dist/views/page-builder/RelationshipField.js +0 -65
  135. package/dist/views/page-builder/RelationshipField.js.map +0 -1
  136. package/dist/views/page-builder/SEOPanel.d.ts +0 -10
  137. package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
  138. package/dist/views/page-builder/SEOPanel.js +0 -103
  139. package/dist/views/page-builder/SEOPanel.js.map +0 -1
  140. package/dist/views/page-builder/SavedSections.d.ts +0 -6
  141. package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
  142. package/dist/views/page-builder/SavedSections.js +0 -141
  143. package/dist/views/page-builder/SavedSections.js.map +0 -1
  144. package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
  145. package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
  146. package/dist/views/page-builder/TemplatePicker.js +0 -68
  147. package/dist/views/page-builder/TemplatePicker.js.map +0 -1
  148. package/dist/views/page-builder/Widgets.d.ts +0 -13
  149. package/dist/views/page-builder/Widgets.d.ts.map +0 -1
  150. package/dist/views/page-builder/Widgets.js +0 -15
  151. package/dist/views/page-builder/Widgets.js.map +0 -1
  152. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
  153. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
  154. package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
  155. package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
  156. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
  157. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
  158. package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
  159. package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
  160. package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
  161. package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
  162. package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
  163. package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
  164. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
  165. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
  166. package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
  167. package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
  168. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
  169. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
  170. package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
  171. package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
  172. package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
  173. package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
  174. package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
  175. package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
  176. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
  177. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
  178. package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
  179. package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
  180. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
  181. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
  182. package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
  183. package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
  184. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
  185. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
  186. package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
  187. package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
  188. package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
  189. package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
  190. package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
  191. package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
  192. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
  193. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
  194. package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
  195. package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
  196. package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
  197. package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
  198. package/dist/views/page-builder/block-renderers/index.js +0 -25
  199. package/dist/views/page-builder/block-renderers/index.js.map +0 -1
  200. package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
  201. package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
  202. package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
  203. package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
  204. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
  205. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
  206. package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
  207. package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
  208. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
  209. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
  210. package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
  211. package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
  212. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
  213. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
  214. package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
  215. package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
  216. package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
  217. package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
  218. package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
  219. package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
  220. package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
  221. package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
  222. package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
  223. package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
  224. package/dist/views/page-builder/canvas/index.d.ts +0 -3
  225. package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
  226. package/dist/views/page-builder/canvas/index.js +0 -2
  227. package/dist/views/page-builder/canvas/index.js.map +0 -1
  228. package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
  229. package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
  230. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
  231. package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
  232. package/src/__tests__/views/page-templates.render.test.tsx +0 -182
  233. package/src/hooks/useBuilderState.ts +0 -361
  234. package/src/views/page-builder/AIBlockAssist.tsx +0 -64
  235. package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
  236. package/src/views/page-builder/BlockEditor.tsx +0 -523
  237. package/src/views/page-builder/BlockPicker.tsx +0 -324
  238. package/src/views/page-builder/BottomBar.tsx +0 -65
  239. package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
  240. package/src/views/page-builder/BuilderToolbar.tsx +0 -221
  241. package/src/views/page-builder/ContextPanel.tsx +0 -145
  242. package/src/views/page-builder/DesignScore.tsx +0 -251
  243. package/src/views/page-builder/NodeSettings.tsx +0 -500
  244. package/src/views/page-builder/PageBuilder.tsx +0 -405
  245. package/src/views/page-builder/PageSettings.tsx +0 -162
  246. package/src/views/page-builder/PageTemplates.tsx +0 -435
  247. package/src/views/page-builder/RelationshipField.tsx +0 -113
  248. package/src/views/page-builder/SEOPanel.tsx +0 -464
  249. package/src/views/page-builder/SavedSections.tsx +0 -480
  250. package/src/views/page-builder/TemplatePicker.tsx +0 -197
  251. package/src/views/page-builder/Widgets.tsx +0 -44
  252. package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
  253. package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
  254. package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
  255. package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
  256. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
  257. package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
  258. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
  259. package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
  260. package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
  261. package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
  262. package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
  263. package/src/views/page-builder/block-renderers/index.ts +0 -34
  264. package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
  265. package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
  266. package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
  267. package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
  268. package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
  269. package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
  270. package/src/views/page-builder/canvas/index.ts +0 -2
@@ -1,480 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useEffect, useCallback } from 'react'
4
- import {
5
- Plus,
6
- ArrowLeft,
7
- Edit,
8
- Trash2,
9
- Copy,
10
- Layers,
11
- Loader2,
12
- AlertTriangle,
13
- Calendar,
14
- Hash,
15
- } from 'lucide-react'
16
- import { toast } from 'sonner'
17
- import { cmsApi } from '../../lib/api.js'
18
- import { createSection, createContainer, createRow, createColumn } from '@prenta/core/page-builder'
19
-
20
- export interface SavedSectionsProps {
21
- onNavigate: (path: string) => void
22
- config: any
23
- }
24
-
25
- interface SavedSection {
26
- id: string
27
- name: string
28
- description: string
29
- category: string
30
- tree: Record<string, unknown> | null
31
- usageCount: number
32
- createdAt: string
33
- updatedAt: string
34
- }
35
-
36
- type ViewState = { mode: 'list' } | { mode: 'create' } | { mode: 'edit'; section: SavedSection }
37
-
38
- const CATEGORIES = ['header', 'footer', 'content', 'sidebar'] as const
39
- type Category = (typeof CATEGORIES)[number]
40
-
41
- const CATEGORY_COLORS: Record<string, string> = {
42
- header: 'bg-primary/10 text-primary',
43
- footer: 'bg-accent text-foreground',
44
- content: 'bg-muted text-muted-foreground',
45
- sidebar: 'bg-primary/10 text-primary',
46
- }
47
-
48
- function formatDate(dateStr: string): string {
49
- return new Date(dateStr).toLocaleDateString(undefined, {
50
- month: 'short',
51
- day: 'numeric',
52
- year: 'numeric',
53
- })
54
- }
55
-
56
- function createEmptySectionTree(): Record<string, unknown> {
57
- const col = createColumn(100)
58
- const row = createRow([col])
59
- const container = createContainer()
60
- ;(container as unknown as { children: unknown[] }).children = [row]
61
- const section = createSection()
62
- ;(section as unknown as { children: unknown[] }).children = [container]
63
- return section as unknown as Record<string, unknown>
64
- }
65
-
66
- export function SavedSections({ onNavigate, config }: SavedSectionsProps) {
67
- const [sections, setSections] = useState<SavedSection[]>([])
68
- const [loading, setLoading] = useState(true)
69
- const [error, setError] = useState<string | null>(null)
70
- const [viewState, setViewState] = useState<ViewState>({ mode: 'list' })
71
- const [activeCategory, setActiveCategory] = useState<Category | 'all'>('all')
72
- const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
73
- const [saving, setSaving] = useState(false)
74
-
75
- const fetchSections = useCallback(async () => {
76
- setLoading(true)
77
- setError(null)
78
- const res = await cmsApi<SavedSection[]>('/saved-sections')
79
- if (res.error) {
80
- setError(res.error)
81
- } else {
82
- setSections(res.data ?? [])
83
- }
84
- setLoading(false)
85
- }, [])
86
-
87
- useEffect(() => {
88
- fetchSections()
89
- }, [fetchSections])
90
-
91
- const filteredSections =
92
- activeCategory === 'all' ? sections : sections.filter((s) => s.category === activeCategory)
93
-
94
- async function handleDelete(id: string) {
95
- const res = await cmsApi(`/saved-sections/${id}`, { method: 'DELETE' })
96
- if (res.error) {
97
- toast.error(res.error)
98
- } else {
99
- toast.success('Section deleted')
100
- setSections((prev) => prev.filter((s) => s.id !== id))
101
- }
102
- setDeleteConfirmId(null)
103
- }
104
-
105
- async function handleDuplicate(section: SavedSection) {
106
- const res = await cmsApi<SavedSection>('/saved-sections', {
107
- method: 'POST',
108
- body: JSON.stringify({
109
- name: `${section.name} (Copy)`,
110
- description: section.description,
111
- category: section.category,
112
- tree: section.tree,
113
- }),
114
- })
115
- if (res.error) {
116
- toast.error(res.error)
117
- } else {
118
- toast.success('Section duplicated')
119
- fetchSections()
120
- }
121
- }
122
-
123
- if (viewState.mode === 'create' || viewState.mode === 'edit') {
124
- return (
125
- <SectionForm
126
- initial={viewState.mode === 'edit' ? viewState.section : undefined}
127
- saving={saving}
128
- onSave={async (data) => {
129
- setSaving(true)
130
- if (viewState.mode === 'create') {
131
- const res = await cmsApi<SavedSection>('/saved-sections', {
132
- method: 'POST',
133
- body: JSON.stringify({ ...data, tree: createEmptySectionTree() }),
134
- })
135
- setSaving(false)
136
- if (res.error) {
137
- toast.error(res.error)
138
- } else {
139
- toast.success('Section created')
140
- setViewState({ mode: 'list' })
141
- fetchSections()
142
- }
143
- } else {
144
- const res = await cmsApi<SavedSection>(`/saved-sections/${viewState.section.id}`, {
145
- method: 'PUT',
146
- body: JSON.stringify(data),
147
- })
148
- setSaving(false)
149
- if (res.error) {
150
- toast.error(res.error)
151
- } else {
152
- toast.success('Section updated')
153
- setViewState({ mode: 'list' })
154
- fetchSections()
155
- }
156
- }
157
- }}
158
- onCancel={() => setViewState({ mode: 'list' })}
159
- />
160
- )
161
- }
162
-
163
- return (
164
- <div className="p-4 pr-8">
165
- <div className="mb-6 flex items-center justify-between">
166
- <div>
167
- <h1 className="text-foreground">Saved Sections</h1>
168
- <p className="text-muted-foreground mt-1 text-sm">
169
- Reusable sections for the page builder
170
- </p>
171
- </div>
172
- <button
173
- type="button"
174
- onClick={() => setViewState({ mode: 'create' })}
175
- className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors"
176
- >
177
- <Plus size={16} />
178
- Create Section
179
- </button>
180
- </div>
181
-
182
- <div className="border-border mb-4 flex items-center gap-1 border-b">
183
- <CategoryTab
184
- label="All"
185
- active={activeCategory === 'all'}
186
- onClick={() => setActiveCategory('all')}
187
- />
188
- {CATEGORIES.map((cat) => (
189
- <CategoryTab
190
- key={cat}
191
- label={cat.charAt(0).toUpperCase() + cat.slice(1)}
192
- active={activeCategory === cat}
193
- onClick={() => setActiveCategory(cat)}
194
- />
195
- ))}
196
- </div>
197
-
198
- {loading && (
199
- <div className="flex items-center justify-center py-16">
200
- <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
201
- </div>
202
- )}
203
-
204
- {error && !loading && (
205
- <div className="flex flex-col items-center justify-center py-12 text-center">
206
- <AlertTriangle size={24} className="text-destructive mb-2" />
207
- <p className="text-muted-foreground mb-3 text-sm">{error}</p>
208
- <button
209
- type="button"
210
- onClick={fetchSections}
211
- className="text-foreground border-border hover:bg-accent rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
212
- >
213
- Retry
214
- </button>
215
- </div>
216
- )}
217
-
218
- {!loading && !error && filteredSections.length === 0 && (
219
- <div className="flex flex-col items-center justify-center py-16 text-center">
220
- <Layers size={32} className="text-muted-foreground mb-3" />
221
- <p className="text-foreground mb-1 text-sm font-medium">No saved sections</p>
222
- <p className="text-muted-foreground mb-4 text-xs">
223
- {activeCategory === 'all'
224
- ? 'Create your first reusable section to get started.'
225
- : `No sections in the "${activeCategory}" category.`}
226
- </p>
227
- {activeCategory === 'all' && (
228
- <button
229
- type="button"
230
- onClick={() => setViewState({ mode: 'create' })}
231
- className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors"
232
- >
233
- <Plus size={14} />
234
- Create Section
235
- </button>
236
- )}
237
- </div>
238
- )}
239
-
240
- {!loading && !error && filteredSections.length > 0 && (
241
- <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
242
- {filteredSections.map((section) => (
243
- <div
244
- key={section.id}
245
- className="border-border bg-card hover:border-primary/50 rounded-lg border p-4 transition-colors"
246
- >
247
- <div className="flex items-start justify-between gap-3">
248
- <div className="min-w-0 flex-1">
249
- <p className="text-foreground truncate text-sm font-medium">{section.name}</p>
250
- {section.description && (
251
- <p className="text-muted-foreground mt-0.5 line-clamp-2 text-xs">
252
- {section.description}
253
- </p>
254
- )}
255
- </div>
256
- <span
257
- className={`shrink-0 rounded-full px-2 py-0.5 text-xs ${CATEGORY_COLORS[section.category] ?? 'bg-muted text-muted-foreground'}`}
258
- >
259
- {section.category}
260
- </span>
261
- </div>
262
-
263
- <div className="text-muted-foreground mt-3 flex items-center gap-4 text-xs">
264
- <span className="flex items-center gap-1">
265
- <Hash size={12} />
266
- {section.usageCount} use{section.usageCount !== 1 ? 's' : ''}
267
- </span>
268
- <span className="flex items-center gap-1">
269
- <Calendar size={12} />
270
- {formatDate(section.createdAt)}
271
- </span>
272
- {section.tree && (
273
- <span className="flex items-center gap-1">
274
- <Layers size={12} />
275
- {JSON.stringify(section.tree).length > 1000 ? 'Complex' : 'Simple'}
276
- </span>
277
- )}
278
- </div>
279
-
280
- <div className="border-border mt-3 flex items-center gap-1 border-t pt-3">
281
- <button
282
- type="button"
283
- onClick={() => setViewState({ mode: 'edit', section })}
284
- className="text-foreground hover:bg-accent flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
285
- aria-label={`Edit ${section.name}`}
286
- >
287
- <Edit size={13} />
288
- Edit
289
- </button>
290
- <button
291
- type="button"
292
- onClick={() => handleDuplicate(section)}
293
- className="text-foreground hover:bg-accent flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
294
- aria-label={`Duplicate ${section.name}`}
295
- >
296
- <Copy size={13} />
297
- Duplicate
298
- </button>
299
- {deleteConfirmId === section.id ? (
300
- <div className="ml-auto flex items-center gap-1">
301
- <span className="text-destructive mr-1 text-xs">Delete?</span>
302
- <button
303
- type="button"
304
- onClick={() => handleDelete(section.id)}
305
- className="text-destructive-foreground bg-destructive hover:bg-destructive/90 rounded-md px-2 py-1 text-xs font-medium transition-colors"
306
- >
307
- Yes
308
- </button>
309
- <button
310
- type="button"
311
- onClick={() => setDeleteConfirmId(null)}
312
- className="text-foreground border-border hover:bg-accent rounded-md border px-2 py-1 text-xs font-medium transition-colors"
313
- >
314
- No
315
- </button>
316
- </div>
317
- ) : (
318
- <button
319
- type="button"
320
- onClick={() => setDeleteConfirmId(section.id)}
321
- className="text-destructive hover:bg-destructive/10 ml-auto flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors"
322
- aria-label={`Delete ${section.name}`}
323
- >
324
- <Trash2 size={13} />
325
- Delete
326
- </button>
327
- )}
328
- </div>
329
- </div>
330
- ))}
331
- </div>
332
- )}
333
- </div>
334
- )
335
- }
336
-
337
- interface CategoryTabProps {
338
- label: string
339
- active: boolean
340
- onClick: () => void
341
- }
342
-
343
- function CategoryTab({ label, active, onClick }: CategoryTabProps) {
344
- return (
345
- <button
346
- type="button"
347
- onClick={onClick}
348
- className={`-mb-px border-b-2 px-3 py-2 text-sm transition-colors ${
349
- active
350
- ? 'border-primary text-foreground font-medium'
351
- : 'text-muted-foreground hover:text-foreground border-transparent'
352
- }`}
353
- >
354
- {label}
355
- </button>
356
- )
357
- }
358
-
359
- interface SectionFormProps {
360
- initial?: SavedSection
361
- saving: boolean
362
- onSave: (data: { name: string; description: string; category: string }) => void
363
- onCancel: () => void
364
- }
365
-
366
- function SectionForm({ initial, saving, onSave, onCancel }: SectionFormProps) {
367
- const [name, setName] = useState(initial?.name ?? '')
368
- const [description, setDescription] = useState(initial?.description ?? '')
369
- const [category, setCategory] = useState(initial?.category ?? 'content')
370
-
371
- function handleSubmit(e: React.FormEvent) {
372
- e.preventDefault()
373
- if (!name.trim()) return
374
- onSave({ name: name.trim(), description: description.trim(), category })
375
- }
376
-
377
- const treeSize = initial?.tree ? JSON.stringify(initial.tree).length : null
378
-
379
- return (
380
- <div className="p-4 pr-8">
381
- <div className="mb-6">
382
- <button
383
- type="button"
384
- onClick={onCancel}
385
- className="text-muted-foreground hover:text-foreground mb-3 flex items-center gap-1.5 text-sm transition-colors"
386
- aria-label="Back to saved sections"
387
- >
388
- <ArrowLeft size={16} />
389
- Back
390
- </button>
391
- <h1 className="text-foreground">
392
- {initial ? `Edit: ${initial.name}` : 'Create Saved Section'}
393
- </h1>
394
- </div>
395
-
396
- <form onSubmit={handleSubmit} className="max-w-lg space-y-4">
397
- <div>
398
- <label htmlFor="section-name" className="text-foreground mb-1 block text-sm font-medium">
399
- Name <span className="text-destructive">*</span>
400
- </label>
401
- <input
402
- id="section-name"
403
- type="text"
404
- value={name}
405
- onChange={(e) => setName(e.target.value)}
406
- required
407
- placeholder="e.g. Hero Banner"
408
- className="bg-background border-input focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
409
- />
410
- </div>
411
-
412
- <div>
413
- <label
414
- htmlFor="section-description"
415
- className="text-foreground mb-1 block text-sm font-medium"
416
- >
417
- Description
418
- </label>
419
- <textarea
420
- id="section-description"
421
- value={description}
422
- onChange={(e) => setDescription(e.target.value)}
423
- placeholder="Optional description of this section..."
424
- rows={3}
425
- className="bg-background border-input focus:ring-ring w-full resize-none rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
426
- />
427
- </div>
428
-
429
- <div>
430
- <label
431
- htmlFor="section-category"
432
- className="text-foreground mb-1 block text-sm font-medium"
433
- >
434
- Category
435
- </label>
436
- <select
437
- id="section-category"
438
- value={category}
439
- onChange={(e) => setCategory(e.target.value)}
440
- className="bg-background border-input focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
441
- >
442
- {CATEGORIES.map((cat) => (
443
- <option key={cat} value={cat}>
444
- {cat.charAt(0).toUpperCase() + cat.slice(1)}
445
- </option>
446
- ))}
447
- </select>
448
- </div>
449
-
450
- {treeSize !== null && (
451
- <div className="border-border bg-muted rounded-md border p-3">
452
- <p className="text-muted-foreground text-xs">
453
- Section tree editing is available in the page builder. Current tree size:{' '}
454
- {(treeSize / 1024).toFixed(1)} KB
455
- </p>
456
- </div>
457
- )}
458
-
459
- <div className="flex items-center gap-3 pt-2">
460
- <button
461
- type="submit"
462
- disabled={saving || !name.trim()}
463
- className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
464
- >
465
- {saving && <Loader2 size={14} className="animate-spin" />}
466
- {initial ? 'Save Changes' : 'Create Section'}
467
- </button>
468
- <button
469
- type="button"
470
- onClick={onCancel}
471
- disabled={saving}
472
- className="text-foreground border-border hover:bg-accent rounded-lg border px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50"
473
- >
474
- Cancel
475
- </button>
476
- </div>
477
- </form>
478
- </div>
479
- )
480
- }
@@ -1,197 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useEffect, useCallback } from 'react'
4
- import {
5
- X,
6
- Loader2,
7
- AlertTriangle,
8
- Layout,
9
- FileText,
10
- MapPin,
11
- Phone,
12
- BookOpen,
13
- File,
14
- } from 'lucide-react'
15
- import type { LucideIcon } from 'lucide-react'
16
- import { cmsApi } from '../../lib/api.js'
17
- import { createEmptyPage } from '@prenta/core/page-builder'
18
- import type { PageNode } from '@prenta/core'
19
-
20
- export interface TemplatePickerProps {
21
- onSelect: (templateTree: PageNode) => void
22
- onClose: () => void
23
- }
24
-
25
- interface PageTemplate {
26
- id: string
27
- name: string
28
- description: string
29
- category: string
30
- tree: PageNode
31
- }
32
-
33
- const categoryIcons: Record<string, LucideIcon> = {
34
- landing: Layout,
35
- blog: FileText,
36
- contact: MapPin,
37
- portfolio: BookOpen,
38
- mobile: Phone,
39
- }
40
-
41
- const categoryColors: Record<string, string> = {
42
- landing: 'bg-primary/10 text-primary',
43
- blog: 'bg-accent text-foreground',
44
- contact: 'bg-primary/10 text-primary',
45
- portfolio: 'bg-accent text-foreground',
46
- mobile: 'bg-primary/10 text-primary',
47
- }
48
-
49
- function resolveIcon(category: string): LucideIcon {
50
- return categoryIcons[category] ?? File
51
- }
52
-
53
- function resolveBadgeColor(category: string): string {
54
- return categoryColors[category] ?? 'bg-muted text-muted-foreground'
55
- }
56
-
57
- export function TemplatePicker({ onSelect, onClose }: TemplatePickerProps) {
58
- const [templates, setTemplates] = useState<PageTemplate[]>([])
59
- const [loading, setLoading] = useState(true)
60
- const [error, setError] = useState<string | null>(null)
61
-
62
- const fetchTemplates = useCallback(async () => {
63
- setLoading(true)
64
- setError(null)
65
- const res = await cmsApi<PageTemplate[]>('/page-templates')
66
- if (res.error) {
67
- setError(res.error)
68
- } else {
69
- setTemplates(res.data ?? [])
70
- }
71
- setLoading(false)
72
- }, [])
73
-
74
- useEffect(() => {
75
- fetchTemplates()
76
- }, [fetchTemplates])
77
-
78
- useEffect(() => {
79
- function handleKeyDown(e: KeyboardEvent) {
80
- if (e.key === 'Escape') onClose()
81
- }
82
- document.addEventListener('keydown', handleKeyDown)
83
- return () => document.removeEventListener('keydown', handleKeyDown)
84
- }, [onClose])
85
-
86
- function handleBlankPage() {
87
- onSelect(createEmptyPage())
88
- onClose()
89
- }
90
-
91
- function handleTemplateSelect(template: PageTemplate) {
92
- onSelect(template.tree)
93
- onClose()
94
- }
95
-
96
- return (
97
- <div
98
- className="fixed inset-0 z-50 flex items-center justify-center"
99
- role="dialog"
100
- aria-modal="true"
101
- aria-label="Choose a template"
102
- >
103
- <div
104
- className="motion-safe:animate-in motion-safe:fade-in-0 absolute inset-0 bg-black/50"
105
- onClick={onClose}
106
- aria-hidden="true"
107
- />
108
-
109
- <div className="bg-card border-border relative z-10 flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden rounded-xl border shadow-2xl">
110
- <div className="flex items-center justify-between px-5 pt-5 pb-3">
111
- <h2 className="text-foreground text-lg font-medium">Choose a Template</h2>
112
- <button
113
- type="button"
114
- onClick={onClose}
115
- className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-md p-1.5 transition-colors"
116
- aria-label="Close"
117
- >
118
- <X size={18} />
119
- </button>
120
- </div>
121
-
122
- <div className="flex-1 overflow-y-auto px-5 pb-5">
123
- {loading && (
124
- <div className="flex items-center justify-center py-16">
125
- <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
126
- </div>
127
- )}
128
-
129
- {error && !loading && (
130
- <div className="flex flex-col items-center justify-center py-12 text-center">
131
- <AlertTriangle size={24} className="text-destructive mb-2" />
132
- <p className="text-muted-foreground mb-3 text-sm">{error}</p>
133
- <button
134
- type="button"
135
- onClick={fetchTemplates}
136
- className="text-foreground border-border hover:bg-accent rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
137
- >
138
- Retry
139
- </button>
140
- </div>
141
- )}
142
-
143
- {!loading && !error && (
144
- <div
145
- className="grid grid-cols-2 gap-3 md:grid-cols-3"
146
- role="list"
147
- aria-label="Available templates"
148
- >
149
- <button
150
- type="button"
151
- role="listitem"
152
- onClick={handleBlankPage}
153
- className="border-border hover:border-primary bg-card group focus:ring-ring flex cursor-pointer flex-col items-center gap-2 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
154
- >
155
- <div className="bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary flex h-10 w-10 items-center justify-center rounded-md transition-colors">
156
- <File size={20} />
157
- </div>
158
- <p className="text-foreground text-center text-sm font-medium">Blank Page</p>
159
- <p className="text-muted-foreground text-center text-xs">Start from scratch</p>
160
- </button>
161
-
162
- {templates.map((template) => {
163
- const Icon = resolveIcon(template.category)
164
- const badgeColor = resolveBadgeColor(template.category)
165
-
166
- return (
167
- <button
168
- key={template.id}
169
- type="button"
170
- role="listitem"
171
- onClick={() => handleTemplateSelect(template)}
172
- className="border-border hover:border-primary bg-card group focus:ring-ring flex cursor-pointer flex-col items-center gap-2 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
173
- >
174
- <div className="bg-accent text-foreground group-hover:bg-primary/10 group-hover:text-primary flex h-10 w-10 items-center justify-center rounded-md transition-colors">
175
- <Icon size={20} />
176
- </div>
177
- <p className="text-foreground w-full truncate text-center text-sm font-medium">
178
- {template.name}
179
- </p>
180
- {template.description && (
181
- <p className="text-muted-foreground line-clamp-2 text-center text-xs">
182
- {template.description}
183
- </p>
184
- )}
185
- <span className={`inline-block rounded-full px-2 py-0.5 text-xs ${badgeColor}`}>
186
- {template.category}
187
- </span>
188
- </button>
189
- )
190
- })}
191
- </div>
192
- )}
193
- </div>
194
- </div>
195
- </div>
196
- )
197
- }