@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,593 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useCallback } from 'react'
4
- import * as Dialog from '@radix-ui/react-dialog'
5
- import {
6
- Sparkles,
7
- X,
8
- Loader2,
9
- Check,
10
- ChevronDown,
11
- ChevronUp,
12
- AlertTriangle,
13
- Wand2,
14
- FileText,
15
- Search,
16
- Accessibility,
17
- ArrowRight,
18
- } from 'lucide-react'
19
- import { toast } from 'sonner'
20
- import { cmsApi } from '../../lib/api.js'
21
- import { adminPortalContainer } from '../../lib/portal-container.js'
22
- import type { PageNode, GenerationStep } from '@prenta/core'
23
-
24
- export interface AIGenerateDialogProps {
25
- open: boolean
26
- onClose: () => void
27
- onAccept: (tree: PageNode) => void
28
- }
29
-
30
- interface StepInfo {
31
- key: GenerationStep
32
- label: string
33
- icon: React.ReactNode
34
- description: string
35
- }
36
-
37
- const ALL_STEPS: StepInfo[] = [
38
- {
39
- key: 'structure',
40
- label: 'Structure',
41
- icon: <Wand2 size={14} />,
42
- description: 'Generate page layout and block placement',
43
- },
44
- {
45
- key: 'content',
46
- label: 'Content',
47
- icon: <FileText size={14} />,
48
- description: 'Write compelling copy for every block',
49
- },
50
- {
51
- key: 'seo',
52
- label: 'SEO',
53
- icon: <Search size={14} />,
54
- description: 'Optimize headings, meta tags, and keywords',
55
- },
56
- {
57
- key: 'accessibility',
58
- label: 'Accessibility',
59
- icon: <Accessibility size={14} />,
60
- description: 'Fix heading hierarchy, alt text, and ARIA',
61
- },
62
- ]
63
-
64
- type Tone = 'professional' | 'casual' | 'friendly' | 'technical' | 'luxury'
65
-
66
- const TONES: { value: Tone; label: string }[] = [
67
- { value: 'professional', label: 'Professional' },
68
- { value: 'casual', label: 'Casual' },
69
- { value: 'friendly', label: 'Friendly' },
70
- { value: 'technical', label: 'Technical' },
71
- { value: 'luxury', label: 'Luxury' },
72
- ]
73
-
74
- type Phase = 'prompt' | 'generating' | 'review'
75
-
76
- interface GenerationStepResult {
77
- step: string
78
- metadata: {
79
- tokensUsed: number
80
- durationMs: number
81
- changes: string[]
82
- }
83
- }
84
-
85
- interface GenerationResultData {
86
- tree: PageNode
87
- steps: GenerationStepResult[]
88
- totalTokensUsed: number
89
- totalDurationMs: number
90
- }
91
-
92
- export function AIGenerateDialog({ open, onClose, onAccept }: AIGenerateDialogProps) {
93
- const [phase, setPhase] = useState<Phase>('prompt')
94
- const [prompt, setPrompt] = useState('')
95
- const [tone, setTone] = useState<Tone>('professional')
96
- const [enabledSteps, setEnabledSteps] = useState<Set<GenerationStep>>(
97
- new Set(['structure', 'content', 'seo', 'accessibility']),
98
- )
99
- const [showContext, setShowContext] = useState(false)
100
- const [businessName, setBusiness] = useState('')
101
- const [industry, setIndustry] = useState('')
102
- const [phone, setPhone] = useState('')
103
- const [address, setAddress] = useState('')
104
- const [services, setServices] = useState('')
105
-
106
- const [currentStep, setCurrentStep] = useState<string>('')
107
- const [completedSteps, setCompletedSteps] = useState<string[]>([])
108
- const [stepMessage, setStepMessage] = useState('')
109
-
110
- const [result, setResult] = useState<GenerationResultData | null>(null)
111
- const [error, setError] = useState<string | null>(null)
112
- const [expandedStep, setExpandedStep] = useState<string | null>(null)
113
-
114
- const toggleStep = (step: GenerationStep) => {
115
- const next = new Set(enabledSteps)
116
- if (step === 'structure') return
117
- if (next.has(step)) {
118
- next.delete(step)
119
- } else {
120
- next.add(step)
121
- }
122
- setEnabledSteps(next)
123
- }
124
-
125
- const resetState = useCallback(() => {
126
- setPhase('prompt')
127
- setPrompt('')
128
- setTone('professional')
129
- setEnabledSteps(new Set(['structure', 'content', 'seo', 'accessibility']))
130
- setShowContext(false)
131
- setBusiness('')
132
- setIndustry('')
133
- setPhone('')
134
- setAddress('')
135
- setServices('')
136
- setCurrentStep('')
137
- setCompletedSteps([])
138
- setStepMessage('')
139
- setResult(null)
140
- setError(null)
141
- setExpandedStep(null)
142
- }, [])
143
-
144
- const handleClose = useCallback(() => {
145
- resetState()
146
- onClose()
147
- }, [resetState, onClose])
148
-
149
- const handleGenerate = useCallback(async () => {
150
- if (!prompt.trim()) {
151
- toast.error('Please describe the page you want to create')
152
- return
153
- }
154
-
155
- setPhase('generating')
156
- setError(null)
157
-
158
- const steps = ALL_STEPS.filter((s) => enabledSteps.has(s.key)).map((s) => s.key)
159
-
160
- setCurrentStep(steps[0]!)
161
- setStepMessage('Starting generation...')
162
-
163
- const context: Record<string, unknown> = {}
164
- if (businessName) context.businessName = businessName
165
- if (industry) context.industry = industry
166
- if (phone) context.phone = phone
167
- if (address) context.address = address
168
- if (services)
169
- context.services = services
170
- .split(',')
171
- .map((s) => s.trim())
172
- .filter(Boolean)
173
-
174
- try {
175
- const res = await cmsApi<GenerationResultData>('/page-builder/generate', {
176
- method: 'POST',
177
- body: JSON.stringify({
178
- prompt: prompt.trim(),
179
- steps,
180
- tone,
181
- context: Object.keys(context).length > 0 ? context : undefined,
182
- }),
183
- })
184
-
185
- if (res.error) {
186
- setError(res.error)
187
- setPhase('prompt')
188
- return
189
- }
190
-
191
- if (res.data) {
192
- setResult(res.data)
193
- setCompletedSteps(res.data.steps.map((s) => s.step))
194
- setPhase('review')
195
- }
196
- } catch (err) {
197
- setError(err instanceof Error ? err.message : 'Generation failed')
198
- setPhase('prompt')
199
- }
200
- }, [prompt, tone, enabledSteps, businessName, industry, phone, address, services])
201
-
202
- const handleAccept = useCallback(() => {
203
- if (result?.tree) {
204
- onAccept(result.tree)
205
- toast.success('AI-generated page applied')
206
- handleClose()
207
- }
208
- }, [result, onAccept, handleClose])
209
-
210
- return (
211
- <Dialog.Root open={open} onOpenChange={(next) => !next && handleClose()}>
212
- <Dialog.Portal container={adminPortalContainer()}>
213
- <Dialog.Overlay className="fixed inset-0 z-50 bg-black/50" />
214
- <Dialog.Content className="bg-card border-border text-card-foreground fixed top-1/2 left-1/2 z-50 flex max-h-[85vh] w-[calc(100%-2rem)] max-w-2xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border shadow-2xl focus:outline-none">
215
- {/* Header */}
216
- <div className="border-border flex items-center justify-between border-b px-6 py-4">
217
- <div className="flex items-center gap-2.5">
218
- <div className="bg-primary/10 rounded-lg p-1.5">
219
- <Sparkles size={18} className="text-primary" aria-hidden />
220
- </div>
221
- <div>
222
- <Dialog.Title className="text-foreground text-base font-medium">
223
- AI Page Generator
224
- </Dialog.Title>
225
- <Dialog.Description className="text-muted-foreground text-xs">
226
- Describe your page and let AI build it
227
- </Dialog.Description>
228
- </div>
229
- </div>
230
- <Dialog.Close asChild>
231
- <button
232
- type="button"
233
- className="hover:bg-muted text-muted-foreground hover:text-foreground focus-visible:ring-ring rounded-md p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
234
- aria-label="Close AI page generator"
235
- >
236
- <X size={16} aria-hidden />
237
- </button>
238
- </Dialog.Close>
239
- </div>
240
-
241
- {/* Content */}
242
- <div className="flex-1 overflow-y-auto p-6">
243
- {phase === 'prompt' && (
244
- <div className="space-y-5">
245
- {error && (
246
- <div className="bg-destructive/5 border-destructive/20 flex items-start gap-2.5 rounded-lg border p-3">
247
- <AlertTriangle size={16} className="text-destructive mt-0.5 shrink-0" />
248
- <p className="text-destructive text-sm">{error}</p>
249
- </div>
250
- )}
251
-
252
- {/* Prompt */}
253
- <div>
254
- <label className="text-foreground mb-1.5 block text-sm font-medium">
255
- Describe your page
256
- </label>
257
- <textarea
258
- value={prompt}
259
- onChange={(e) => setPrompt(e.target.value)}
260
- placeholder="e.g. A landing page for a plumbing company with a hero section, service cards, testimonials, and a contact form"
261
- rows={4}
262
- className="bg-background border-border focus:ring-primary/40 text-foreground placeholder:text-muted-foreground w-full resize-none rounded-lg border px-3 py-2.5 text-sm focus:ring-2 focus:outline-none"
263
- autoFocus
264
- />
265
- </div>
266
-
267
- {/* Tone */}
268
- <div>
269
- <label className="text-foreground mb-1.5 block text-sm font-medium">Tone</label>
270
- <div className="flex flex-wrap gap-2">
271
- {TONES.map((t) => (
272
- <button
273
- key={t.value}
274
- onClick={() => setTone(t.value)}
275
- className={`rounded-full border px-3 py-1.5 text-xs transition-colors ${
276
- tone === t.value
277
- ? 'border-primary bg-primary/10 text-primary'
278
- : 'border-border bg-background text-muted-foreground hover:border-primary/40'
279
- }`}
280
- >
281
- {t.label}
282
- </button>
283
- ))}
284
- </div>
285
- </div>
286
-
287
- {/* Steps */}
288
- <div>
289
- <label className="text-foreground mb-1.5 block text-sm font-medium">
290
- Generation steps
291
- </label>
292
- <div className="space-y-1.5">
293
- {ALL_STEPS.map((step) => (
294
- <label
295
- key={step.key}
296
- className={`flex cursor-pointer items-center gap-3 rounded-lg border p-2.5 transition-colors ${
297
- enabledSteps.has(step.key)
298
- ? 'border-primary/30 bg-primary/5'
299
- : 'border-border bg-background'
300
- } ${step.key === 'structure' ? 'cursor-not-allowed opacity-70' : 'hover:border-primary/20'}`}
301
- >
302
- <input
303
- type="checkbox"
304
- checked={enabledSteps.has(step.key)}
305
- onChange={() => toggleStep(step.key)}
306
- disabled={step.key === 'structure'}
307
- className="sr-only"
308
- />
309
- <div
310
- className={`flex h-5 w-5 shrink-0 items-center justify-center rounded ${
311
- enabledSteps.has(step.key)
312
- ? 'bg-primary text-primary-foreground'
313
- : 'bg-muted border-border border'
314
- }`}
315
- >
316
- {enabledSteps.has(step.key) && <Check size={12} />}
317
- </div>
318
- <div className="flex min-w-0 flex-1 items-center gap-2">
319
- <span className="text-muted-foreground">{step.icon}</span>
320
- <div>
321
- <span className="text-foreground text-sm">{step.label}</span>
322
- <span className="text-muted-foreground ml-2 text-xs">
323
- {step.description}
324
- </span>
325
- </div>
326
- </div>
327
- </label>
328
- ))}
329
- </div>
330
- </div>
331
-
332
- {/* Business context (collapsible) */}
333
- <div>
334
- <button
335
- onClick={() => setShowContext(!showContext)}
336
- className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 text-sm transition-colors"
337
- >
338
- {showContext ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
339
- Business context (optional)
340
- </button>
341
- {showContext && (
342
- <div className="bg-muted/50 border-border mt-3 space-y-3 rounded-lg border p-4">
343
- <div className="grid grid-cols-2 gap-3">
344
- <div>
345
- <label className="text-muted-foreground mb-1 block text-xs">
346
- Business name
347
- </label>
348
- <input
349
- type="text"
350
- value={businessName}
351
- onChange={(e) => setBusiness(e.target.value)}
352
- className="bg-background border-border focus:ring-primary/40 text-foreground w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-2 focus:outline-none"
353
- />
354
- </div>
355
- <div>
356
- <label className="text-muted-foreground mb-1 block text-xs">
357
- Industry
358
- </label>
359
- <input
360
- type="text"
361
- value={industry}
362
- onChange={(e) => setIndustry(e.target.value)}
363
- className="bg-background border-border focus:ring-primary/40 text-foreground w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-2 focus:outline-none"
364
- />
365
- </div>
366
- <div>
367
- <label className="text-muted-foreground mb-1 block text-xs">Phone</label>
368
- <input
369
- type="text"
370
- value={phone}
371
- onChange={(e) => setPhone(e.target.value)}
372
- className="bg-background border-border focus:ring-primary/40 text-foreground w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-2 focus:outline-none"
373
- />
374
- </div>
375
- <div>
376
- <label className="text-muted-foreground mb-1 block text-xs">
377
- Address
378
- </label>
379
- <input
380
- type="text"
381
- value={address}
382
- onChange={(e) => setAddress(e.target.value)}
383
- className="bg-background border-border focus:ring-primary/40 text-foreground w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-2 focus:outline-none"
384
- />
385
- </div>
386
- </div>
387
- <div>
388
- <label className="text-muted-foreground mb-1 block text-xs">
389
- Services (comma-separated)
390
- </label>
391
- <input
392
- type="text"
393
- value={services}
394
- onChange={(e) => setServices(e.target.value)}
395
- placeholder="e.g. Web design, SEO, Content marketing"
396
- className="bg-background border-border focus:ring-primary/40 text-foreground placeholder:text-muted-foreground w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-2 focus:outline-none"
397
- />
398
- </div>
399
- </div>
400
- )}
401
- </div>
402
- </div>
403
- )}
404
-
405
- {phase === 'generating' && (
406
- <div className="space-y-6 py-8">
407
- <div className="text-center">
408
- <Loader2 size={32} className="text-primary mx-auto mb-3 animate-spin" />
409
- <p className="text-foreground text-sm font-medium">
410
- {stepMessage || 'Generating...'}
411
- </p>
412
- <p className="text-muted-foreground mt-1 text-xs">This may take 15-30 seconds</p>
413
- </div>
414
-
415
- <div className="space-y-2">
416
- {ALL_STEPS.filter((s) => enabledSteps.has(s.key)).map((step) => {
417
- const isComplete = completedSteps.includes(step.key)
418
- const isCurrent = currentStep === step.key
419
-
420
- return (
421
- <div
422
- key={step.key}
423
- className={`flex items-center gap-3 rounded-lg border p-3 transition-colors ${
424
- isComplete
425
- ? 'border-primary/30 bg-primary/5'
426
- : isCurrent
427
- ? 'border-primary/50 bg-primary/10'
428
- : 'border-border bg-muted/30'
429
- }`}
430
- >
431
- <div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full">
432
- {isComplete ? (
433
- <div className="bg-primary flex h-6 w-6 items-center justify-center rounded-full">
434
- <Check size={12} className="text-primary-foreground" />
435
- </div>
436
- ) : isCurrent ? (
437
- <Loader2 size={16} className="text-primary animate-spin" />
438
- ) : (
439
- <div className="bg-muted border-border h-6 w-6 rounded-full border" />
440
- )}
441
- </div>
442
- <div className="flex items-center gap-2">
443
- <span className="text-muted-foreground">{step.icon}</span>
444
- <span
445
- className={`text-sm ${isCurrent || isComplete ? 'text-foreground' : 'text-muted-foreground'}`}
446
- >
447
- {step.label}
448
- </span>
449
- </div>
450
- </div>
451
- )
452
- })}
453
- </div>
454
- </div>
455
- )}
456
-
457
- {phase === 'review' && result && (
458
- <div className="space-y-5">
459
- {/* Summary */}
460
- <div className="bg-primary/5 border-primary/20 flex items-center gap-3 rounded-lg border p-4">
461
- <div className="bg-primary/10 flex h-10 w-10 shrink-0 items-center justify-center rounded-full">
462
- <Sparkles size={18} className="text-primary" />
463
- </div>
464
- <div>
465
- <p className="text-foreground text-sm font-medium">
466
- Page generated successfully
467
- </p>
468
- <p className="text-muted-foreground mt-0.5 text-xs">
469
- {result.totalTokensUsed.toLocaleString()} tokens
470
- {' \u00B7 '}
471
- {(result.totalDurationMs / 1000).toFixed(1)}s{' \u00B7 '}
472
- {result.steps.length} step{result.steps.length !== 1 ? 's' : ''}
473
- </p>
474
- </div>
475
- </div>
476
-
477
- {/* Step details */}
478
- <div className="space-y-2">
479
- <h3 className="text-foreground text-sm font-medium">Generation steps</h3>
480
- {result.steps.map((step) => {
481
- const info = ALL_STEPS.find((s) => s.key === step.step)
482
- const isExpanded = expandedStep === step.step
483
-
484
- return (
485
- <div
486
- key={step.step}
487
- className="border-border overflow-hidden rounded-lg border"
488
- >
489
- <button
490
- onClick={() => setExpandedStep(isExpanded ? null : step.step)}
491
- className="hover:bg-muted/50 flex w-full items-center gap-3 p-3 text-left transition-colors"
492
- >
493
- <div className="bg-primary flex h-6 w-6 shrink-0 items-center justify-center rounded-full">
494
- <Check size={12} className="text-primary-foreground" />
495
- </div>
496
- <span className="text-muted-foreground">{info?.icon}</span>
497
- <span className="text-foreground flex-1 text-sm">{info?.label}</span>
498
- <span className="text-muted-foreground text-xs">
499
- {(step.metadata.durationMs / 1000).toFixed(1)}s
500
- </span>
501
- {isExpanded ? (
502
- <ChevronUp size={14} className="text-muted-foreground" />
503
- ) : (
504
- <ChevronDown size={14} className="text-muted-foreground" />
505
- )}
506
- </button>
507
- {isExpanded && (
508
- <div className="border-border border-t px-3 pt-2 pb-3">
509
- <ul className="space-y-1">
510
- {step.metadata.changes.map((change, i) => (
511
- <li
512
- key={i}
513
- className="text-muted-foreground flex items-start gap-2 text-xs"
514
- >
515
- <ArrowRight size={10} className="mt-0.5 shrink-0" />
516
- {change}
517
- </li>
518
- ))}
519
- </ul>
520
- <p className="text-muted-foreground mt-2 text-xs">
521
- Tokens: {step.metadata.tokensUsed.toLocaleString()}
522
- </p>
523
- </div>
524
- )}
525
- </div>
526
- )
527
- })}
528
- </div>
529
-
530
- <p className="text-muted-foreground text-xs">
531
- Review the generated page in the canvas after accepting. You can always undo to
532
- revert.
533
- </p>
534
- </div>
535
- )}
536
- </div>
537
-
538
- {/* Footer */}
539
- <div className="border-border flex items-center justify-end gap-3 border-t px-6 py-4">
540
- {phase === 'prompt' && (
541
- <>
542
- <button
543
- onClick={handleClose}
544
- className="text-muted-foreground hover:text-foreground px-4 py-2 text-sm transition-colors"
545
- >
546
- Cancel
547
- </button>
548
- <button
549
- onClick={handleGenerate}
550
- disabled={!prompt.trim()}
551
- 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"
552
- >
553
- <Sparkles size={14} />
554
- Generate Page
555
- </button>
556
- </>
557
- )}
558
-
559
- {phase === 'generating' && (
560
- <button
561
- onClick={handleClose}
562
- className="text-muted-foreground hover:text-foreground px-4 py-2 text-sm transition-colors"
563
- >
564
- Cancel
565
- </button>
566
- )}
567
-
568
- {phase === 'review' && (
569
- <>
570
- <button
571
- onClick={() => {
572
- setPhase('prompt')
573
- setResult(null)
574
- }}
575
- className="text-muted-foreground hover:text-foreground px-4 py-2 text-sm transition-colors"
576
- >
577
- Regenerate
578
- </button>
579
- <button
580
- onClick={handleAccept}
581
- 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"
582
- >
583
- <Check size={14} />
584
- Accept &amp; Apply
585
- </button>
586
- </>
587
- )}
588
- </div>
589
- </Dialog.Content>
590
- </Dialog.Portal>
591
- </Dialog.Root>
592
- )
593
- }