@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,221 +0,0 @@
1
- 'use client'
2
-
3
- import {
4
- ChevronLeft,
5
- Undo2,
6
- Redo2,
7
- Monitor,
8
- Tablet,
9
- Smartphone,
10
- Loader2,
11
- Sparkles,
12
- } from 'lucide-react'
13
- import type { DeviceMode, PageSettings } from '../../hooks/useBuilderState.js'
14
-
15
- export interface BuilderToolbarProps {
16
- collectionSlug: string
17
- pageSettings: PageSettings
18
- status: 'DRAFT' | 'PUBLISHED' | 'SCHEDULED'
19
- dirty: boolean
20
- saving: boolean
21
- canUndo: boolean
22
- canRedo: boolean
23
- deviceMode: DeviceMode
24
- onNavigate: (path: string) => void
25
- onTitleChange: (title: string) => void
26
- onUndo: () => void
27
- onRedo: () => void
28
- onDeviceMode: (mode: DeviceMode) => void
29
- onSave: () => void
30
- onPublish: () => void
31
- onOpenAI?: () => void
32
- }
33
-
34
- const STATUS_STYLES: Record<string, string> = {
35
- DRAFT: 'bg-muted text-muted-foreground',
36
- PUBLISHED: 'bg-primary/10 text-primary',
37
- SCHEDULED: 'bg-accent text-accent-foreground',
38
- }
39
-
40
- export function BuilderToolbar({
41
- collectionSlug,
42
- pageSettings,
43
- status,
44
- dirty,
45
- saving,
46
- canUndo,
47
- canRedo,
48
- deviceMode,
49
- onNavigate,
50
- onTitleChange,
51
- onUndo,
52
- onRedo,
53
- onDeviceMode,
54
- onSave,
55
- onPublish,
56
- onOpenAI,
57
- }: BuilderToolbarProps) {
58
- const collectionLabel = collectionSlug.charAt(0).toUpperCase() + collectionSlug.slice(1)
59
-
60
- return (
61
- <div
62
- className="bg-card border-border flex h-14 shrink-0 items-center gap-3 border-b px-4"
63
- role="toolbar"
64
- aria-label="Page builder toolbar"
65
- >
66
- {/* Back button */}
67
- <button
68
- onClick={() => onNavigate(`/collections/${collectionSlug}`)}
69
- className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-sm transition-colors"
70
- aria-label={`Back to ${collectionLabel}`}
71
- >
72
- <ChevronLeft size={16} />
73
- <span className="hidden sm:inline">{collectionLabel}</span>
74
- </button>
75
-
76
- {/* Divider */}
77
- <div className="bg-border h-6 w-px" />
78
-
79
- {/* Page title */}
80
- <input
81
- type="text"
82
- value={pageSettings.title}
83
- onChange={(e) => onTitleChange(e.target.value)}
84
- placeholder="Untitled Page"
85
- className="text-foreground focus-visible:ring-ring placeholder:text-muted-foreground min-w-0 flex-1 rounded-sm border-none bg-transparent text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
86
- aria-label="Page title"
87
- />
88
-
89
- {/* Status badge */}
90
- <span
91
- className={`rounded-md px-2 py-0.5 text-xs font-medium whitespace-nowrap ${STATUS_STYLES[status] ?? STATUS_STYLES.DRAFT}`}
92
- >
93
- {status}
94
- </span>
95
-
96
- {/* Unsaved indicator */}
97
- {dirty && !saving && (
98
- <span
99
- className="bg-destructive h-2 w-2 shrink-0 rounded-full"
100
- title="Unsaved changes"
101
- aria-label="Unsaved changes"
102
- />
103
- )}
104
-
105
- {/* Divider */}
106
- <div className="bg-border h-6 w-px" />
107
-
108
- {/* Undo / Redo */}
109
- <div className="flex items-center gap-1">
110
- <button
111
- onClick={onUndo}
112
- disabled={!canUndo}
113
- className="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
114
- aria-label="Undo"
115
- >
116
- <Undo2 size={16} />
117
- </button>
118
- <button
119
- onClick={onRedo}
120
- disabled={!canRedo}
121
- className="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
122
- aria-label="Redo"
123
- >
124
- <Redo2 size={16} />
125
- </button>
126
- </div>
127
-
128
- {/* Divider */}
129
- <div className="bg-border h-6 w-px" />
130
-
131
- {/* Device toggle */}
132
- <div className="bg-muted flex items-center gap-0.5 rounded-md p-0.5">
133
- <DeviceButton
134
- active={deviceMode === 'desktop'}
135
- onClick={() => onDeviceMode('desktop')}
136
- label="Desktop view"
137
- >
138
- <Monitor size={16} />
139
- </DeviceButton>
140
- <DeviceButton
141
- active={deviceMode === 'tablet'}
142
- onClick={() => onDeviceMode('tablet')}
143
- label="Tablet view"
144
- >
145
- <Tablet size={16} />
146
- </DeviceButton>
147
- <DeviceButton
148
- active={deviceMode === 'mobile'}
149
- onClick={() => onDeviceMode('mobile')}
150
- label="Mobile view"
151
- >
152
- <Smartphone size={16} />
153
- </DeviceButton>
154
- </div>
155
-
156
- {/* Divider */}
157
- <div className="bg-border h-6 w-px" />
158
-
159
- {/* AI Generate */}
160
- {onOpenAI && (
161
- <>
162
- <button
163
- onClick={onOpenAI}
164
- className="text-primary bg-primary/10 hover:bg-primary/20 flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm font-medium transition-colors"
165
- aria-label="Generate page with AI"
166
- >
167
- <Sparkles size={14} />
168
- <span className="hidden sm:inline">AI</span>
169
- </button>
170
- <div className="bg-border h-6 w-px" />
171
- </>
172
- )}
173
-
174
- {/* Save / Publish */}
175
- <div className="flex items-center gap-2">
176
- <button
177
- onClick={onSave}
178
- disabled={saving || !dirty}
179
- className="bg-muted text-muted-foreground hover:bg-muted/80 flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50"
180
- >
181
- {saving && <Loader2 size={14} className="animate-spin" />}
182
- {saving ? 'Saving...' : 'Save'}
183
- </button>
184
- <button
185
- onClick={onPublish}
186
- disabled={saving}
187
- className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50"
188
- >
189
- Publish
190
- </button>
191
- </div>
192
- </div>
193
- )
194
- }
195
-
196
- function DeviceButton({
197
- active,
198
- onClick,
199
- label,
200
- children,
201
- }: {
202
- active: boolean
203
- onClick: () => void
204
- label: string
205
- children: React.ReactNode
206
- }) {
207
- return (
208
- <button
209
- onClick={onClick}
210
- className={`rounded-md p-1.5 transition-colors ${
211
- active
212
- ? 'bg-background text-foreground shadow-sm'
213
- : 'text-muted-foreground hover:text-foreground'
214
- }`}
215
- aria-label={label}
216
- aria-pressed={active}
217
- >
218
- {children}
219
- </button>
220
- )
221
- }
@@ -1,144 +0,0 @@
1
- 'use client'
2
-
3
- import { useMemo } from 'react'
4
- import * as Tabs from '@radix-ui/react-tabs'
5
- import { Blocks, Settings2, FileText, Search, Palette } from 'lucide-react'
6
- import type { BuilderNode, PageNode } from '@prenta/core'
7
- import { BlockEditor } from './BlockEditor.js'
8
- import { NodeSettings } from './NodeSettings.js'
9
- import { PageSettingsEditor } from './PageSettings.js'
10
- import { BuilderSEOPanel } from './SEOPanel.js'
11
- import { DesignScorePanel } from './DesignScore.js'
12
- import type { PageSettings } from '../../hooks/useBuilderState.js'
13
-
14
- export interface ContextPanelProps {
15
- activeTab: 'block' | 'node' | 'page' | 'seo' | 'design'
16
- onTabChange: (tab: 'block' | 'node' | 'page' | 'seo' | 'design') => void
17
- selectedNode: BuilderNode | null
18
- tree: PageNode
19
- pageSettings: PageSettings
20
- onUpdateSettings: (id: string, settings: Record<string, unknown>) => void
21
- onUpdateBlock: (id: string, data: Record<string, unknown>) => void
22
- onRemoveNode: (id: string) => void
23
- onDuplicateNode: (id: string) => void
24
- onMoveNodeUp: (id: string) => void
25
- onMoveNodeDown: (id: string) => void
26
- onPageSettingsChange: (settings: Partial<PageSettings>) => void
27
- onAddRow: (sectionId: string) => void
28
- config: any
29
- }
30
-
31
- type TabDef = {
32
- value: 'block' | 'node' | 'page' | 'seo' | 'design'
33
- label: string
34
- icon: typeof Blocks
35
- }
36
-
37
- const TABS: TabDef[] = [
38
- { value: 'block', label: 'Block', icon: Blocks },
39
- { value: 'node', label: 'Node', icon: Settings2 },
40
- { value: 'page', label: 'Page', icon: FileText },
41
- { value: 'seo', label: 'SEO', icon: Search },
42
- { value: 'design', label: 'Design', icon: Palette },
43
- ]
44
-
45
- export function ContextPanel({
46
- activeTab,
47
- onTabChange,
48
- selectedNode,
49
- tree,
50
- pageSettings,
51
- onUpdateSettings,
52
- onUpdateBlock,
53
- onRemoveNode,
54
- onDuplicateNode,
55
- onMoveNodeUp,
56
- onMoveNodeDown,
57
- onPageSettingsChange,
58
- onAddRow,
59
- config,
60
- }: ContextPanelProps) {
61
- const availableTabs = useMemo(() => {
62
- if (!selectedNode) return TABS.filter((t) => t.value !== 'block' && t.value !== 'node')
63
- if (selectedNode.type === 'block') return TABS.filter((t) => t.value !== 'node')
64
- return TABS.filter((t) => t.value !== 'block')
65
- }, [selectedNode])
66
-
67
- const effectiveTab = useMemo(() => {
68
- if (availableTabs.some((t) => t.value === activeTab)) return activeTab
69
- if (selectedNode?.type === 'block') return 'block'
70
- if (selectedNode) return 'node'
71
- return 'page'
72
- }, [activeTab, availableTabs, selectedNode])
73
-
74
- return (
75
- <div className="bg-card h-full w-full overflow-y-auto">
76
- <Tabs.Root
77
- value={effectiveTab}
78
- onValueChange={(value) => onTabChange(value as ContextPanelProps['activeTab'])}
79
- >
80
- <Tabs.List className="border-border bg-muted/30 flex border-b">
81
- {availableTabs.map((tab) => {
82
- const Icon = tab.icon
83
- return (
84
- <Tabs.Trigger
85
- key={tab.value}
86
- value={tab.value}
87
- className="text-muted-foreground hover:text-foreground data-[state=active]:text-foreground data-[state=active]:border-primary data-[state=active]:bg-background flex flex-1 items-center justify-center gap-1.5 px-2 py-2.5 text-xs font-medium transition-colors data-[state=active]:border-b-2"
88
- >
89
- <Icon size={14} />
90
- <span>{tab.label}</span>
91
- </Tabs.Trigger>
92
- )
93
- })}
94
- </Tabs.List>
95
-
96
- <Tabs.Content value="block">
97
- {selectedNode?.type === 'block' && (
98
- <BlockEditor
99
- node={selectedNode}
100
- onUpdateBlock={onUpdateBlock}
101
- onUpdateSettings={onUpdateSettings}
102
- onRemoveNode={onRemoveNode}
103
- onDuplicateNode={onDuplicateNode}
104
- config={config}
105
- />
106
- )}
107
- </Tabs.Content>
108
-
109
- <Tabs.Content value="node">
110
- {selectedNode &&
111
- selectedNode.type !== 'block' &&
112
- selectedNode.type !== 'page' &&
113
- selectedNode.type !== 'savedSectionRef' && (
114
- <NodeSettings
115
- node={selectedNode}
116
- onUpdateSettings={onUpdateSettings}
117
- onRemoveNode={onRemoveNode}
118
- onDuplicateNode={onDuplicateNode}
119
- onMoveNodeUp={onMoveNodeUp}
120
- onMoveNodeDown={onMoveNodeDown}
121
- onAddRow={selectedNode.type === 'section' ? onAddRow : undefined}
122
- />
123
- )}
124
- </Tabs.Content>
125
-
126
- <Tabs.Content value="page">
127
- <PageSettingsEditor settings={pageSettings} onChange={onPageSettingsChange} />
128
- </Tabs.Content>
129
-
130
- <Tabs.Content value="seo">
131
- <BuilderSEOPanel
132
- tree={tree}
133
- pageSettings={pageSettings}
134
- onPageSettingsChange={onPageSettingsChange}
135
- />
136
- </Tabs.Content>
137
-
138
- <Tabs.Content value="design">
139
- <DesignScorePanel tree={tree} />
140
- </Tabs.Content>
141
- </Tabs.Root>
142
- </div>
143
- )
144
- }
@@ -1,251 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo } from 'react'
4
- import { analyzeDesign } from '@prenta/core/page-builder'
5
- import type {
6
- PageNode,
7
- DesignAnalysis,
8
- DesignCategory,
9
- BuilderDesignCheck,
10
- DesignSuggestion,
11
- } from '@prenta/core'
12
- import {
13
- CheckCircle2,
14
- AlertCircle,
15
- XCircle,
16
- ChevronDown,
17
- ChevronUp,
18
- Lightbulb,
19
- Palette,
20
- } from 'lucide-react'
21
-
22
- export interface DesignScorePanelProps {
23
- tree: PageNode
24
- }
25
-
26
- function getScoreColor(score: number, max: number): string {
27
- const ratio = max > 0 ? score / max : 0
28
- if (ratio >= 0.8) return 'text-success'
29
- if (ratio >= 0.5) return 'text-warning'
30
- return 'text-destructive'
31
- }
32
-
33
- function getBarColor(score: number, max: number): string {
34
- const ratio = max > 0 ? score / max : 0
35
- if (ratio >= 0.8) return 'bg-success'
36
- if (ratio >= 0.5) return 'bg-warning'
37
- return 'bg-destructive'
38
- }
39
-
40
- function getScoreLabel(score: number): string {
41
- if (score >= 80) return 'Excellent'
42
- if (score >= 65) return 'Good'
43
- if (score >= 50) return 'Needs Work'
44
- return 'Poor'
45
- }
46
-
47
- function getRingStrokeColor(score: number): string {
48
- if (score >= 80) return 'rgb(34, 197, 94)'
49
- if (score >= 50) return 'rgb(245, 158, 11)'
50
- return 'rgb(239, 68, 68)'
51
- }
52
-
53
- function StatusIcon({ status }: { status: BuilderDesignCheck['status'] }) {
54
- switch (status) {
55
- case 'good':
56
- return <CheckCircle2 size={14} className="text-success shrink-0" />
57
- case 'warning':
58
- return <AlertCircle size={14} className="text-warning shrink-0" />
59
- case 'error':
60
- return <XCircle size={14} className="text-destructive shrink-0" />
61
- }
62
- }
63
-
64
- function SeverityBadge({ severity }: { severity: DesignSuggestion['severity'] }) {
65
- const classes: Record<DesignSuggestion['severity'], string> = {
66
- important: 'bg-destructive/10 text-destructive',
67
- suggestion: 'bg-warning/10 text-warning',
68
- info: 'bg-info/10 text-info',
69
- }
70
-
71
- return (
72
- <span className={`inline-block rounded px-1.5 py-0.5 text-xs font-medium ${classes[severity]}`}>
73
- {severity}
74
- </span>
75
- )
76
- }
77
-
78
- function ScoreRing({ score }: { score: number }) {
79
- const radius = 32
80
- const strokeWidth = 6
81
- const circumference = 2 * Math.PI * radius
82
- const progress = Math.min(score, 100) / 100
83
- const dashOffset = circumference * (1 - progress)
84
- const size = (radius + strokeWidth) * 2
85
-
86
- return (
87
- <div className="flex flex-col items-center gap-2">
88
- <div className="relative">
89
- <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="-rotate-90">
90
- <circle
91
- cx={size / 2}
92
- cy={size / 2}
93
- r={radius}
94
- fill="none"
95
- stroke="var(--border)"
96
- strokeWidth={strokeWidth}
97
- />
98
- <circle
99
- cx={size / 2}
100
- cy={size / 2}
101
- r={radius}
102
- fill="none"
103
- stroke={getRingStrokeColor(score)}
104
- strokeWidth={strokeWidth}
105
- strokeDasharray={circumference}
106
- strokeDashoffset={dashOffset}
107
- strokeLinecap="round"
108
- className="transition-all duration-700 ease-out"
109
- />
110
- </svg>
111
- <div className="absolute inset-0 flex items-center justify-center">
112
- <span className={`text-lg font-medium ${getScoreColor(score, 100)}`}>{score}</span>
113
- </div>
114
- </div>
115
- <span className="text-muted-foreground text-xs">{getScoreLabel(score)}</span>
116
- </div>
117
- )
118
- }
119
-
120
- function CategoryRow({
121
- category,
122
- expanded,
123
- onToggle,
124
- }: {
125
- category: DesignCategory
126
- expanded: boolean
127
- onToggle: () => void
128
- }) {
129
- const percentage = category.maxScore > 0 ? (category.score / category.maxScore) * 100 : 0
130
-
131
- return (
132
- <div className="border-border border-b last:border-b-0">
133
- <button
134
- type="button"
135
- className="hover:bg-muted/50 flex w-full flex-col gap-2 px-4 py-3 text-left transition-colors"
136
- onClick={onToggle}
137
- aria-expanded={expanded}
138
- >
139
- <div className="flex items-center justify-between">
140
- <span className="text-foreground text-xs font-medium">{category.label}</span>
141
- <div className="flex items-center gap-2">
142
- <span className="text-muted-foreground text-xs">
143
- {category.score}/{category.maxScore}
144
- </span>
145
- {expanded ? (
146
- <ChevronUp size={14} className="text-muted-foreground" />
147
- ) : (
148
- <ChevronDown size={14} className="text-muted-foreground" />
149
- )}
150
- </div>
151
- </div>
152
- <div className="bg-muted h-1.5 w-full overflow-hidden rounded-full">
153
- <div
154
- className={`h-full rounded-full transition-all duration-500 ease-out ${getBarColor(category.score, category.maxScore)}`}
155
- style={{ width: `${percentage}%` }}
156
- />
157
- </div>
158
- </button>
159
-
160
- {expanded && category.checks.length > 0 && (
161
- <div className="flex flex-col gap-2 px-4 pb-3">
162
- {category.checks.map((check) => (
163
- <div key={check.id} className="flex items-start gap-2 pl-1">
164
- <StatusIcon status={check.status} />
165
- <div className="flex flex-col">
166
- <span className="text-foreground text-xs font-medium">{check.label}</span>
167
- <span className="text-muted-foreground text-xs">{check.detail}</span>
168
- </div>
169
- </div>
170
- ))}
171
- </div>
172
- )}
173
- </div>
174
- )
175
- }
176
-
177
- function SuggestionsList({ suggestions }: { suggestions: DesignSuggestion[] }) {
178
- const sorted = useMemo(() => {
179
- const order: Record<DesignSuggestion['severity'], number> = {
180
- important: 0,
181
- suggestion: 1,
182
- info: 2,
183
- }
184
- return [...suggestions].sort((a, b) => order[a.severity] - order[b.severity])
185
- }, [suggestions])
186
-
187
- if (sorted.length === 0) return null
188
-
189
- return (
190
- <div className="border-border border-t px-4 py-3">
191
- <div className="mb-3 flex items-center gap-2">
192
- <Lightbulb size={14} className="text-muted-foreground" />
193
- <span className="text-foreground text-xs font-medium">Suggestions</span>
194
- </div>
195
- <div className="flex flex-col gap-2.5">
196
- {sorted.map((suggestion) => (
197
- <div key={suggestion.id} className="flex items-start gap-2">
198
- <SeverityBadge severity={suggestion.severity} />
199
- <span className="text-muted-foreground text-xs leading-relaxed">
200
- {suggestion.message}
201
- </span>
202
- </div>
203
- ))}
204
- </div>
205
- </div>
206
- )
207
- }
208
-
209
- export function DesignScorePanel({ tree }: DesignScorePanelProps) {
210
- const analysis = useMemo(() => analyzeDesign(tree), [tree])
211
- const [expandedCategories, setExpandedCategories] = useState<string[]>([])
212
-
213
- const toggleCategory = (name: string) => {
214
- setExpandedCategories((prev) =>
215
- prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name],
216
- )
217
- }
218
-
219
- if (!tree.children || tree.children.length === 0) {
220
- return (
221
- <div className="flex min-h-[200px] flex-col items-center justify-center p-6 text-center">
222
- <Palette size={32} className="text-muted-foreground mb-3" />
223
- <p className="text-foreground mb-1 text-sm font-medium">Design Score</p>
224
- <p className="text-muted-foreground text-xs">
225
- Add sections to your page to see the design analysis
226
- </p>
227
- </div>
228
- )
229
- }
230
-
231
- return (
232
- <div className="flex flex-col">
233
- <div className="border-border flex flex-col items-center border-b py-5">
234
- <ScoreRing score={analysis.score} />
235
- </div>
236
-
237
- <div className="flex flex-col">
238
- {analysis.categories.map((category) => (
239
- <CategoryRow
240
- key={category.name}
241
- category={category}
242
- expanded={expandedCategories.includes(category.name)}
243
- onToggle={() => toggleCategory(category.name)}
244
- />
245
- ))}
246
- </div>
247
-
248
- <SuggestionsList suggestions={analysis.suggestions} />
249
- </div>
250
- )
251
- }