@prenta/admin 0.98.1 → 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 (267) hide show
  1. package/CHANGELOG.md +6 -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 -3
  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-editor-service-api.test.js +26 -23
  12. package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
  13. package/dist/__tests__/views/page-section-editor.test.js +0 -1
  14. package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
  15. package/dist/__tests__/views/page-tab.render.test.js +0 -1
  16. package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
  17. package/dist/index.d.ts +1 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/layout/Sidebar.d.ts +3 -4
  22. package/dist/layout/Sidebar.d.ts.map +1 -1
  23. package/dist/layout/Sidebar.js +7 -38
  24. package/dist/layout/Sidebar.js.map +1 -1
  25. package/dist/lib/page-editor-service.d.ts +0 -7
  26. package/dist/lib/page-editor-service.d.ts.map +1 -1
  27. package/dist/lib/page-editor-service.js +9 -9
  28. package/dist/lib/page-editor-service.js.map +1 -1
  29. package/dist/prenta-admin.css +1 -1
  30. package/dist/views/PageEditor.d.ts +2 -2
  31. package/dist/views/PageEditor.js +2 -2
  32. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  33. package/dist/views/page-editor/PageSectionEditor.js +9 -15
  34. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/AdminRoot.tsx +18 -68
  37. package/src/__tests__/components/seo-score-bands.test.ts +0 -3
  38. package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
  39. package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
  40. package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
  41. package/src/__tests__/views/page-section-editor.test.tsx +0 -1
  42. package/src/__tests__/views/page-tab.render.test.tsx +0 -1
  43. package/src/index.ts +1 -11
  44. package/src/layout/Sidebar.tsx +6 -41
  45. package/src/lib/page-editor-service.ts +9 -16
  46. package/src/views/PageEditor.tsx +2 -2
  47. package/src/views/page-editor/PageSectionEditor.tsx +8 -37
  48. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
  49. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
  50. package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
  51. package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
  52. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
  53. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
  54. package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
  55. package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
  56. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
  57. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
  58. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
  59. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
  60. package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts +0 -2
  61. package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts.map +0 -1
  62. package/dist/__tests__/views/page-builder-seo-panel.render.test.js +0 -65
  63. package/dist/__tests__/views/page-builder-seo-panel.render.test.js.map +0 -1
  64. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
  65. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
  66. package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
  67. package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
  68. package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
  69. package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
  70. package/dist/__tests__/views/page-templates.render.test.js +0 -126
  71. package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
  72. package/dist/hooks/useBuilderState.d.ts +0 -49
  73. package/dist/hooks/useBuilderState.d.ts.map +0 -1
  74. package/dist/hooks/useBuilderState.js +0 -238
  75. package/dist/hooks/useBuilderState.js.map +0 -1
  76. package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
  77. package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
  78. package/dist/views/page-builder/AIBlockAssist.js +0 -40
  79. package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
  80. package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
  81. package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
  82. package/dist/views/page-builder/AIGenerateDialog.js +0 -173
  83. package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
  84. package/dist/views/page-builder/BlockEditor.d.ts +0 -11
  85. package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
  86. package/dist/views/page-builder/BlockEditor.js +0 -159
  87. package/dist/views/page-builder/BlockEditor.js.map +0 -1
  88. package/dist/views/page-builder/BlockPicker.d.ts +0 -7
  89. package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
  90. package/dist/views/page-builder/BlockPicker.js +0 -103
  91. package/dist/views/page-builder/BlockPicker.js.map +0 -1
  92. package/dist/views/page-builder/BottomBar.d.ts +0 -9
  93. package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
  94. package/dist/views/page-builder/BottomBar.js +0 -13
  95. package/dist/views/page-builder/BottomBar.js.map +0 -1
  96. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
  97. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
  98. package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
  99. package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
  100. package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
  101. package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
  102. package/dist/views/page-builder/BuilderToolbar.js +0 -18
  103. package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
  104. package/dist/views/page-builder/ContextPanel.d.ts +0 -20
  105. package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
  106. package/dist/views/page-builder/ContextPanel.js +0 -43
  107. package/dist/views/page-builder/ContextPanel.js.map +0 -1
  108. package/dist/views/page-builder/DesignScore.d.ts +0 -6
  109. package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
  110. package/dist/views/page-builder/DesignScore.js +0 -93
  111. package/dist/views/page-builder/DesignScore.js.map +0 -1
  112. package/dist/views/page-builder/NodeSettings.d.ts +0 -12
  113. package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
  114. package/dist/views/page-builder/NodeSettings.js +0 -80
  115. package/dist/views/page-builder/NodeSettings.js.map +0 -1
  116. package/dist/views/page-builder/PageBuilder.d.ts +0 -10
  117. package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
  118. package/dist/views/page-builder/PageBuilder.js +0 -222
  119. package/dist/views/page-builder/PageBuilder.js.map +0 -1
  120. package/dist/views/page-builder/PageSettings.d.ts +0 -7
  121. package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
  122. package/dist/views/page-builder/PageSettings.js +0 -27
  123. package/dist/views/page-builder/PageSettings.js.map +0 -1
  124. package/dist/views/page-builder/PageTemplates.d.ts +0 -5
  125. package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
  126. package/dist/views/page-builder/PageTemplates.js +0 -146
  127. package/dist/views/page-builder/PageTemplates.js.map +0 -1
  128. package/dist/views/page-builder/RelationshipField.d.ts +0 -15
  129. package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
  130. package/dist/views/page-builder/RelationshipField.js +0 -65
  131. package/dist/views/page-builder/RelationshipField.js.map +0 -1
  132. package/dist/views/page-builder/SEOPanel.d.ts +0 -9
  133. package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
  134. package/dist/views/page-builder/SEOPanel.js +0 -38
  135. package/dist/views/page-builder/SEOPanel.js.map +0 -1
  136. package/dist/views/page-builder/SavedSections.d.ts +0 -6
  137. package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
  138. package/dist/views/page-builder/SavedSections.js +0 -141
  139. package/dist/views/page-builder/SavedSections.js.map +0 -1
  140. package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
  141. package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
  142. package/dist/views/page-builder/TemplatePicker.js +0 -68
  143. package/dist/views/page-builder/TemplatePicker.js.map +0 -1
  144. package/dist/views/page-builder/Widgets.d.ts +0 -13
  145. package/dist/views/page-builder/Widgets.d.ts.map +0 -1
  146. package/dist/views/page-builder/Widgets.js +0 -15
  147. package/dist/views/page-builder/Widgets.js.map +0 -1
  148. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
  149. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
  150. package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
  151. package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
  152. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
  153. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
  154. package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
  155. package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
  156. package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
  157. package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
  158. package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
  159. package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
  160. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
  161. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
  162. package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
  163. package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
  164. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
  165. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
  166. package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
  167. package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
  168. package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
  169. package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
  170. package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
  171. package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
  172. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
  173. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
  174. package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
  175. package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
  176. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
  177. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
  178. package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
  179. package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
  180. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
  181. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
  182. package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
  183. package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
  184. package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
  185. package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
  186. package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
  187. package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
  188. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
  189. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
  190. package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
  191. package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
  192. package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
  193. package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
  194. package/dist/views/page-builder/block-renderers/index.js +0 -25
  195. package/dist/views/page-builder/block-renderers/index.js.map +0 -1
  196. package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
  197. package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
  198. package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
  199. package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
  200. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
  201. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
  202. package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
  203. package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
  204. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
  205. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
  206. package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
  207. package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
  208. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
  209. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
  210. package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
  211. package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
  212. package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
  213. package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
  214. package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
  215. package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
  216. package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
  217. package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
  218. package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
  219. package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
  220. package/dist/views/page-builder/canvas/index.d.ts +0 -3
  221. package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
  222. package/dist/views/page-builder/canvas/index.js +0 -2
  223. package/dist/views/page-builder/canvas/index.js.map +0 -1
  224. package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
  225. package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
  226. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
  227. package/src/__tests__/views/page-builder-seo-panel.render.test.tsx +0 -77
  228. package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
  229. package/src/__tests__/views/page-templates.render.test.tsx +0 -182
  230. package/src/hooks/useBuilderState.ts +0 -361
  231. package/src/views/page-builder/AIBlockAssist.tsx +0 -64
  232. package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
  233. package/src/views/page-builder/BlockEditor.tsx +0 -523
  234. package/src/views/page-builder/BlockPicker.tsx +0 -324
  235. package/src/views/page-builder/BottomBar.tsx +0 -65
  236. package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
  237. package/src/views/page-builder/BuilderToolbar.tsx +0 -221
  238. package/src/views/page-builder/ContextPanel.tsx +0 -144
  239. package/src/views/page-builder/DesignScore.tsx +0 -251
  240. package/src/views/page-builder/NodeSettings.tsx +0 -500
  241. package/src/views/page-builder/PageBuilder.tsx +0 -405
  242. package/src/views/page-builder/PageSettings.tsx +0 -162
  243. package/src/views/page-builder/PageTemplates.tsx +0 -435
  244. package/src/views/page-builder/RelationshipField.tsx +0 -113
  245. package/src/views/page-builder/SEOPanel.tsx +0 -222
  246. package/src/views/page-builder/SavedSections.tsx +0 -480
  247. package/src/views/page-builder/TemplatePicker.tsx +0 -197
  248. package/src/views/page-builder/Widgets.tsx +0 -44
  249. package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
  250. package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
  251. package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
  252. package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
  253. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
  254. package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
  255. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
  256. package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
  257. package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
  258. package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
  259. package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
  260. package/src/views/page-builder/block-renderers/index.ts +0 -34
  261. package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
  262. package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
  263. package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
  264. package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
  265. package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
  266. package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
  267. package/src/views/page-builder/canvas/index.ts +0 -2
@@ -1,222 +0,0 @@
1
- 'use client'
2
-
3
- import { useMemo, useState } from 'react'
4
- import type { PageNode } from '@prenta/core'
5
- import { auditPage } from '@prenta/core/seo/page-audit'
6
- import { ChevronDown, ChevronUp, Eye, FileText, Globe, Search } from 'lucide-react'
7
- import { PageAuditPanel } from '../../components/seo/PageAuditPanel.js'
8
- import { buildPageAuditInputFromBuilder } from '../../lib/page-audit-input.js'
9
- import type { PageSettings } from '../../hooks/useBuilderState.js'
10
-
11
- export interface BuilderSEOPanelProps {
12
- tree: PageNode
13
- pageSettings: PageSettings
14
- onPageSettingsChange: (settings: Partial<PageSettings>) => void
15
- }
16
-
17
- function CollapsibleSection({
18
- title,
19
- icon: Icon,
20
- expanded,
21
- onToggle,
22
- children,
23
- }: {
24
- title: string
25
- icon: typeof Search
26
- expanded: boolean
27
- onToggle: () => void
28
- children: React.ReactNode
29
- }) {
30
- return (
31
- <div className="border-border border-b last:border-b-0">
32
- <button
33
- type="button"
34
- className="hover:bg-muted/50 flex w-full items-center justify-between px-4 py-3 text-left transition-colors"
35
- onClick={onToggle}
36
- aria-expanded={expanded}
37
- >
38
- <div className="flex items-center gap-2">
39
- <Icon size={14} className="text-muted-foreground" />
40
- <span className="text-foreground text-xs font-medium">{title}</span>
41
- </div>
42
- {expanded ? (
43
- <ChevronUp size={14} className="text-muted-foreground" />
44
- ) : (
45
- <ChevronDown size={14} className="text-muted-foreground" />
46
- )}
47
- </button>
48
- {expanded && <div className="px-4 pb-4">{children}</div>}
49
- </div>
50
- )
51
- }
52
-
53
- function BasicSEOFields({
54
- pageSettings,
55
- onPageSettingsChange,
56
- }: {
57
- pageSettings: PageSettings
58
- onPageSettingsChange: (settings: Partial<PageSettings>) => void
59
- }) {
60
- const metaTitleLength = (pageSettings.metaTitle || '').length
61
- const metaDescLength = (pageSettings.metaDescription || '').length
62
-
63
- return (
64
- <div className="flex flex-col gap-4">
65
- <div className="flex flex-col gap-1.5">
66
- <div className="flex items-center justify-between">
67
- <label htmlFor="builder-meta-title" className="text-foreground text-xs font-medium">
68
- Meta Title
69
- </label>
70
- <span
71
- className={`text-xs ${metaTitleLength > 60 ? 'text-destructive' : metaTitleLength > 0 ? 'text-muted-foreground' : 'text-muted-foreground'}`}
72
- >
73
- {metaTitleLength}/60
74
- </span>
75
- </div>
76
- <input
77
- id="builder-meta-title"
78
- type="text"
79
- value={pageSettings.metaTitle || ''}
80
- onChange={(e) => onPageSettingsChange({ metaTitle: e.target.value })}
81
- placeholder="Page title for search engines"
82
- className="bg-input-background border-border text-foreground placeholder:text-muted-foreground focus:ring-primary w-full rounded-md border px-3 py-2 text-sm focus:ring-1 focus:outline-none"
83
- />
84
- </div>
85
-
86
- <div className="flex flex-col gap-1.5">
87
- <div className="flex items-center justify-between">
88
- <label className="text-foreground text-xs font-medium">Meta Description</label>
89
- <span
90
- className={`text-xs ${metaDescLength > 160 ? 'text-destructive' : metaDescLength > 0 ? 'text-muted-foreground' : 'text-muted-foreground'}`}
91
- >
92
- {metaDescLength}/160
93
- </span>
94
- </div>
95
- <textarea
96
- value={pageSettings.metaDescription || ''}
97
- onChange={(e) => onPageSettingsChange({ metaDescription: e.target.value })}
98
- placeholder="Brief description of the page content"
99
- rows={3}
100
- className="bg-input-background border-border text-foreground placeholder:text-muted-foreground focus:ring-primary w-full resize-none rounded-md border px-3 py-2 text-sm focus:ring-1 focus:outline-none"
101
- />
102
- </div>
103
-
104
- <div className="flex flex-col gap-1.5">
105
- <label className="text-foreground text-xs font-medium">Focus Keyphrase</label>
106
- <input
107
- type="text"
108
- value={pageSettings.focusKeyphrase || ''}
109
- onChange={(e) => onPageSettingsChange({ focusKeyphrase: e.target.value })}
110
- placeholder="Primary keyword or phrase"
111
- className="bg-input-background border-border text-foreground placeholder:text-muted-foreground focus:ring-primary w-full rounded-md border px-3 py-2 text-sm focus:ring-1 focus:outline-none"
112
- />
113
- </div>
114
- </div>
115
- )
116
- }
117
-
118
- function SearchPreview({ pageSettings }: { pageSettings: PageSettings }) {
119
- const title = pageSettings.metaTitle || pageSettings.title || 'Untitled Page'
120
- const description = pageSettings.metaDescription || 'No description set'
121
- const slug = pageSettings.slug || 'page'
122
-
123
- return (
124
- <div className="bg-background border-border rounded-md border p-4">
125
- <p className="text-muted-foreground mb-2 text-xs">Google Search Preview</p>
126
- <div className="flex flex-col gap-1">
127
- <p className="text-primary truncate text-sm">{title}</p>
128
- <p className="text-success truncate text-xs">example.com/{slug}</p>
129
- <p className="text-muted-foreground line-clamp-2 text-xs">{description}</p>
130
- </div>
131
- </div>
132
- )
133
- }
134
-
135
- function SocialPreview({ pageSettings }: { pageSettings: PageSettings }) {
136
- const title = pageSettings.metaTitle || pageSettings.title || 'Untitled Page'
137
- const description = pageSettings.metaDescription || 'No description set'
138
-
139
- return (
140
- <div className="bg-background border-border overflow-hidden rounded-md border">
141
- <p className="text-muted-foreground px-4 pt-3 pb-2 text-xs">Open Graph Preview</p>
142
- {pageSettings.ogImage && (
143
- <div className="bg-muted flex h-32 w-full items-center justify-center overflow-hidden">
144
- <img src={pageSettings.ogImage} alt="OG preview" className="h-full w-full object-cover" />
145
- </div>
146
- )}
147
- {!pageSettings.ogImage && (
148
- <div className="bg-muted flex h-32 w-full items-center justify-center">
149
- <Globe size={24} className="text-muted-foreground" />
150
- </div>
151
- )}
152
- <div className="flex flex-col gap-1 p-3">
153
- <p className="text-muted-foreground text-xs uppercase">example.com</p>
154
- <p className="text-foreground truncate text-sm font-medium">{title}</p>
155
- <p className="text-muted-foreground line-clamp-2 text-xs">{description}</p>
156
- </div>
157
- </div>
158
- )
159
- }
160
-
161
- export function BuilderSEOPanel({
162
- tree,
163
- pageSettings,
164
- onPageSettingsChange,
165
- }: BuilderSEOPanelProps) {
166
- const audit = useMemo(
167
- () => auditPage(buildPageAuditInputFromBuilder({ tree, pageSettings })),
168
- [tree, pageSettings],
169
- )
170
- const [expandedSections, setExpandedSections] = useState<string[]>(['basicSeo'])
171
-
172
- const toggleSection = (section: string) => {
173
- setExpandedSections((prev) =>
174
- prev.includes(section) ? prev.filter((s) => s !== section) : [...prev, section],
175
- )
176
- }
177
-
178
- if (!tree.children || tree.children.length === 0) {
179
- return (
180
- <div className="flex min-h-[200px] flex-col items-center justify-center p-6 text-center">
181
- <Search size={32} className="text-muted-foreground mb-3" />
182
- <p className="text-foreground mb-1 text-sm font-medium">SEO Analysis</p>
183
- <p className="text-muted-foreground text-xs">
184
- Add content to your page to see SEO analysis
185
- </p>
186
- </div>
187
- )
188
- }
189
-
190
- return (
191
- <div className="flex flex-col">
192
- <PageAuditPanel audit={audit} />
193
-
194
- <CollapsibleSection
195
- title="Basic SEO"
196
- icon={FileText}
197
- expanded={expandedSections.includes('basicSeo')}
198
- onToggle={() => toggleSection('basicSeo')}
199
- >
200
- <BasicSEOFields pageSettings={pageSettings} onPageSettingsChange={onPageSettingsChange} />
201
- </CollapsibleSection>
202
-
203
- <CollapsibleSection
204
- title="Search Preview"
205
- icon={Eye}
206
- expanded={expandedSections.includes('searchPreview')}
207
- onToggle={() => toggleSection('searchPreview')}
208
- >
209
- <SearchPreview pageSettings={pageSettings} />
210
- </CollapsibleSection>
211
-
212
- <CollapsibleSection
213
- title="Social Preview"
214
- icon={Globe}
215
- expanded={expandedSections.includes('socialPreview')}
216
- onToggle={() => toggleSection('socialPreview')}
217
- >
218
- <SocialPreview pageSettings={pageSettings} />
219
- </CollapsibleSection>
220
- </div>
221
- )
222
- }
@@ -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
- }