@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,464 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo } from 'react'
4
- import { analyzeSEO } from '@prenta/core/page-builder'
5
- import type {
6
- PageNode,
7
- BuilderSEOAnalysis,
8
- BuilderSEOCheck,
9
- BuilderReadabilityResult,
10
- } from '@prenta/core'
11
- import type { PageSettings } from '../../hooks/useBuilderState.js'
12
- import {
13
- CheckCircle2,
14
- AlertCircle,
15
- XCircle,
16
- ChevronDown,
17
- ChevronUp,
18
- Search,
19
- Globe,
20
- Type,
21
- FileText,
22
- Clock,
23
- BarChart3,
24
- Target,
25
- Eye,
26
- } from 'lucide-react'
27
-
28
- export interface BuilderSEOPanelProps {
29
- tree: PageNode
30
- pageSettings: PageSettings
31
- onPageSettingsChange: (settings: Partial<PageSettings>) => void
32
- selectedNodeId: string | null
33
- }
34
-
35
- function getScoreColor(score: number): string {
36
- if (score >= 70) return 'text-success'
37
- if (score >= 40) return 'text-warning'
38
- return 'text-destructive'
39
- }
40
-
41
- function getRingStrokeColor(score: number): string {
42
- if (score >= 70) return 'rgb(34, 197, 94)'
43
- if (score >= 40) return 'rgb(245, 158, 11)'
44
- return 'rgb(239, 68, 68)'
45
- }
46
-
47
- function getScoreLabel(score: number): string {
48
- if (score >= 80) return 'Excellent'
49
- if (score >= 70) return 'Good'
50
- if (score >= 50) return 'Needs Work'
51
- return 'Poor'
52
- }
53
-
54
- function StatusIcon({ status }: { status: BuilderSEOCheck['status'] }) {
55
- switch (status) {
56
- case 'good':
57
- return <CheckCircle2 size={14} className="text-success shrink-0" />
58
- case 'warning':
59
- return <AlertCircle size={14} className="text-warning shrink-0" />
60
- case 'error':
61
- return <XCircle size={14} className="text-destructive shrink-0" />
62
- }
63
- }
64
-
65
- function ScoreRing({ score }: { score: number }) {
66
- const radius = 32
67
- const strokeWidth = 6
68
- const circumference = 2 * Math.PI * radius
69
- const progress = Math.min(score, 100) / 100
70
- const dashOffset = circumference * (1 - progress)
71
- const size = (radius + strokeWidth) * 2
72
-
73
- return (
74
- <div className="relative">
75
- <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="-rotate-90">
76
- <circle
77
- cx={size / 2}
78
- cy={size / 2}
79
- r={radius}
80
- fill="none"
81
- stroke="var(--border)"
82
- strokeWidth={strokeWidth}
83
- />
84
- <circle
85
- cx={size / 2}
86
- cy={size / 2}
87
- r={radius}
88
- fill="none"
89
- stroke={getRingStrokeColor(score)}
90
- strokeWidth={strokeWidth}
91
- strokeDasharray={circumference}
92
- strokeDashoffset={dashOffset}
93
- strokeLinecap="round"
94
- className="transition-all duration-700 ease-out"
95
- />
96
- </svg>
97
- <div className="absolute inset-0 flex items-center justify-center">
98
- <span className={`text-lg font-medium ${getScoreColor(score)}`}>{score}</span>
99
- </div>
100
- </div>
101
- )
102
- }
103
-
104
- function ScoreSummary({ checks }: { checks: BuilderSEOCheck[] }) {
105
- const passed = checks.filter((c) => c.status === 'good').length
106
- const warnings = checks.filter((c) => c.status === 'warning').length
107
- const errors = checks.filter((c) => c.status === 'error').length
108
-
109
- return (
110
- <div className="flex flex-col gap-1">
111
- <div className="flex items-center gap-1.5">
112
- <CheckCircle2 size={12} className="text-success" />
113
- <span className="text-muted-foreground text-xs">{passed} passed</span>
114
- </div>
115
- <div className="flex items-center gap-1.5">
116
- <AlertCircle size={12} className="text-warning" />
117
- <span className="text-muted-foreground text-xs">{warnings} warnings</span>
118
- </div>
119
- <div className="flex items-center gap-1.5">
120
- <XCircle size={12} className="text-destructive" />
121
- <span className="text-muted-foreground text-xs">{errors} issues</span>
122
- </div>
123
- </div>
124
- )
125
- }
126
-
127
- function CollapsibleSection({
128
- title,
129
- icon: Icon,
130
- expanded,
131
- onToggle,
132
- children,
133
- }: {
134
- title: string
135
- icon: typeof Search
136
- expanded: boolean
137
- onToggle: () => void
138
- children: React.ReactNode
139
- }) {
140
- return (
141
- <div className="border-border border-b last:border-b-0">
142
- <button
143
- type="button"
144
- className="hover:bg-muted/50 flex w-full items-center justify-between px-4 py-3 text-left transition-colors"
145
- onClick={onToggle}
146
- aria-expanded={expanded}
147
- >
148
- <div className="flex items-center gap-2">
149
- <Icon size={14} className="text-muted-foreground" />
150
- <span className="text-foreground text-xs font-medium">{title}</span>
151
- </div>
152
- {expanded ? (
153
- <ChevronUp size={14} className="text-muted-foreground" />
154
- ) : (
155
- <ChevronDown size={14} className="text-muted-foreground" />
156
- )}
157
- </button>
158
- {expanded && <div className="px-4 pb-4">{children}</div>}
159
- </div>
160
- )
161
- }
162
-
163
- function SEOChecksList({ checks }: { checks: BuilderSEOCheck[] }) {
164
- return (
165
- <div className="flex flex-col gap-2">
166
- {checks.map((check) => (
167
- <div key={check.id} className="flex items-start gap-2">
168
- <StatusIcon status={check.status} />
169
- <div className="flex flex-col">
170
- <span className="text-foreground text-xs font-medium">{check.label}</span>
171
- <span className="text-muted-foreground text-xs">{check.detail}</span>
172
- </div>
173
- </div>
174
- ))}
175
- </div>
176
- )
177
- }
178
-
179
- function ReadabilityGrid({ readability }: { readability: BuilderReadabilityResult }) {
180
- const fleschLabel =
181
- readability.fleschScore >= 60
182
- ? 'Easy'
183
- : readability.fleschScore >= 30
184
- ? 'Moderate'
185
- : 'Difficult'
186
-
187
- return (
188
- <div className="grid grid-cols-2 gap-3">
189
- <div className="bg-muted/50 flex flex-col gap-1 rounded-md p-3">
190
- <div className="flex items-center gap-1.5">
191
- <BarChart3 size={12} className="text-muted-foreground" />
192
- <span className="text-muted-foreground text-xs">Flesch Score</span>
193
- </div>
194
- <span className={`text-sm font-medium ${getScoreColor(readability.fleschScore)}`}>
195
- {readability.fleschScore}
196
- </span>
197
- <span className="text-muted-foreground text-xs">{fleschLabel}</span>
198
- </div>
199
-
200
- <div className="bg-muted/50 flex flex-col gap-1 rounded-md p-3">
201
- <div className="flex items-center gap-1.5">
202
- <Type size={12} className="text-muted-foreground" />
203
- <span className="text-muted-foreground text-xs">Word Count</span>
204
- </div>
205
- <span className="text-foreground text-sm font-medium">{readability.wordCount}</span>
206
- <span className="text-muted-foreground text-xs">
207
- {readability.wordCount >= 300 ? 'Good length' : 'Short'}
208
- </span>
209
- </div>
210
-
211
- <div className="bg-muted/50 flex flex-col gap-1 rounded-md p-3">
212
- <div className="flex items-center gap-1.5">
213
- <FileText size={12} className="text-muted-foreground" />
214
- <span className="text-muted-foreground text-xs">Avg Sentence</span>
215
- </div>
216
- <span className="text-foreground text-sm font-medium">
217
- {readability.avgSentenceLength} words
218
- </span>
219
- <span className="text-muted-foreground text-xs">
220
- {readability.avgSentenceLength <= 20 ? 'Good' : 'Long'}
221
- </span>
222
- </div>
223
-
224
- <div className="bg-muted/50 flex flex-col gap-1 rounded-md p-3">
225
- <div className="flex items-center gap-1.5">
226
- <Clock size={12} className="text-muted-foreground" />
227
- <span className="text-muted-foreground text-xs">Reading Time</span>
228
- </div>
229
- <span className="text-foreground text-sm font-medium">{readability.readingTime} min</span>
230
- <span className="text-muted-foreground text-xs">~{readability.wordCount} words</span>
231
- </div>
232
- </div>
233
- )
234
- }
235
-
236
- function PerBlockHints({ hints }: { hints: BuilderSEOCheck[] }) {
237
- return (
238
- <div className="flex flex-col gap-2">
239
- {hints.map((hint, index) => (
240
- <div key={`${hint.id}-${index}`} className="flex items-start gap-2">
241
- <StatusIcon status={hint.status} />
242
- <div className="flex flex-col">
243
- <span className="text-foreground text-xs font-medium">{hint.label}</span>
244
- <span className="text-muted-foreground text-xs">{hint.detail}</span>
245
- </div>
246
- </div>
247
- ))}
248
- </div>
249
- )
250
- }
251
-
252
- function BasicSEOFields({
253
- pageSettings,
254
- onPageSettingsChange,
255
- }: {
256
- pageSettings: PageSettings
257
- onPageSettingsChange: (settings: Partial<PageSettings>) => void
258
- }) {
259
- const metaTitleLength = (pageSettings.metaTitle || '').length
260
- const metaDescLength = (pageSettings.metaDescription || '').length
261
-
262
- return (
263
- <div className="flex flex-col gap-4">
264
- <div className="flex flex-col gap-1.5">
265
- <div className="flex items-center justify-between">
266
- <label className="text-foreground text-xs font-medium">Meta Title</label>
267
- <span
268
- className={`text-xs ${metaTitleLength > 60 ? 'text-destructive' : metaTitleLength > 0 ? 'text-muted-foreground' : 'text-muted-foreground'}`}
269
- >
270
- {metaTitleLength}/60
271
- </span>
272
- </div>
273
- <input
274
- type="text"
275
- value={pageSettings.metaTitle || ''}
276
- onChange={(e) => onPageSettingsChange({ metaTitle: e.target.value })}
277
- placeholder="Page title for search engines"
278
- 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"
279
- />
280
- </div>
281
-
282
- <div className="flex flex-col gap-1.5">
283
- <div className="flex items-center justify-between">
284
- <label className="text-foreground text-xs font-medium">Meta Description</label>
285
- <span
286
- className={`text-xs ${metaDescLength > 160 ? 'text-destructive' : metaDescLength > 0 ? 'text-muted-foreground' : 'text-muted-foreground'}`}
287
- >
288
- {metaDescLength}/160
289
- </span>
290
- </div>
291
- <textarea
292
- value={pageSettings.metaDescription || ''}
293
- onChange={(e) => onPageSettingsChange({ metaDescription: e.target.value })}
294
- placeholder="Brief description of the page content"
295
- rows={3}
296
- 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"
297
- />
298
- </div>
299
-
300
- <div className="flex flex-col gap-1.5">
301
- <label className="text-foreground text-xs font-medium">Focus Keyphrase</label>
302
- <input
303
- type="text"
304
- value={pageSettings.focusKeyphrase || ''}
305
- onChange={(e) => onPageSettingsChange({ focusKeyphrase: e.target.value })}
306
- placeholder="Primary keyword or phrase"
307
- 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"
308
- />
309
- </div>
310
- </div>
311
- )
312
- }
313
-
314
- function SearchPreview({ pageSettings }: { pageSettings: PageSettings }) {
315
- const title = pageSettings.metaTitle || pageSettings.title || 'Untitled Page'
316
- const description = pageSettings.metaDescription || 'No description set'
317
- const slug = pageSettings.slug || 'page'
318
-
319
- return (
320
- <div className="bg-background border-border rounded-md border p-4">
321
- <p className="text-muted-foreground mb-2 text-xs">Google Search Preview</p>
322
- <div className="flex flex-col gap-1">
323
- <p className="text-primary truncate text-sm">{title}</p>
324
- <p className="text-success truncate text-xs">example.com/{slug}</p>
325
- <p className="text-muted-foreground line-clamp-2 text-xs">{description}</p>
326
- </div>
327
- </div>
328
- )
329
- }
330
-
331
- function SocialPreview({ pageSettings }: { pageSettings: PageSettings }) {
332
- const title = pageSettings.metaTitle || pageSettings.title || 'Untitled Page'
333
- const description = pageSettings.metaDescription || 'No description set'
334
-
335
- return (
336
- <div className="bg-background border-border overflow-hidden rounded-md border">
337
- <p className="text-muted-foreground px-4 pt-3 pb-2 text-xs">Open Graph Preview</p>
338
- {pageSettings.ogImage && (
339
- <div className="bg-muted flex h-32 w-full items-center justify-center overflow-hidden">
340
- <img src={pageSettings.ogImage} alt="OG preview" className="h-full w-full object-cover" />
341
- </div>
342
- )}
343
- {!pageSettings.ogImage && (
344
- <div className="bg-muted flex h-32 w-full items-center justify-center">
345
- <Globe size={24} className="text-muted-foreground" />
346
- </div>
347
- )}
348
- <div className="flex flex-col gap-1 p-3">
349
- <p className="text-muted-foreground text-xs uppercase">example.com</p>
350
- <p className="text-foreground truncate text-sm font-medium">{title}</p>
351
- <p className="text-muted-foreground line-clamp-2 text-xs">{description}</p>
352
- </div>
353
- </div>
354
- )
355
- }
356
-
357
- export function BuilderSEOPanel({
358
- tree,
359
- pageSettings,
360
- onPageSettingsChange,
361
- selectedNodeId,
362
- }: BuilderSEOPanelProps) {
363
- const analysis = useMemo<BuilderSEOAnalysis>(
364
- () => analyzeSEO(tree, pageSettings as any),
365
- [tree, pageSettings],
366
- )
367
-
368
- const [expandedSections, setExpandedSections] = useState<string[]>(['checks'])
369
-
370
- const toggleSection = (section: string) => {
371
- setExpandedSections((prev) =>
372
- prev.includes(section) ? prev.filter((s) => s !== section) : [...prev, section],
373
- )
374
- }
375
-
376
- const blockHints = useMemo(() => {
377
- if (!selectedNodeId) return []
378
- return analysis.perBlockHints.get(selectedNodeId) || []
379
- }, [analysis.perBlockHints, selectedNodeId])
380
-
381
- if (!tree.children || tree.children.length === 0) {
382
- return (
383
- <div className="flex min-h-[200px] flex-col items-center justify-center p-6 text-center">
384
- <Search size={32} className="text-muted-foreground mb-3" />
385
- <p className="text-foreground mb-1 text-sm font-medium">SEO Analysis</p>
386
- <p className="text-muted-foreground text-xs">
387
- Add content to your page to see SEO analysis
388
- </p>
389
- </div>
390
- )
391
- }
392
-
393
- return (
394
- <div className="flex flex-col">
395
- <div className="border-border flex items-center gap-4 border-b px-4 py-5">
396
- <ScoreRing score={analysis.score} />
397
- <div className="flex flex-col gap-1">
398
- <span className="text-foreground text-xs font-medium">
399
- {getScoreLabel(analysis.score)}
400
- </span>
401
- <ScoreSummary checks={analysis.checks} />
402
- </div>
403
- </div>
404
-
405
- <div className="flex flex-col">
406
- <CollapsibleSection
407
- title="SEO Checks"
408
- icon={Search}
409
- expanded={expandedSections.includes('checks')}
410
- onToggle={() => toggleSection('checks')}
411
- >
412
- <SEOChecksList checks={analysis.checks} />
413
- </CollapsibleSection>
414
-
415
- <CollapsibleSection
416
- title="Readability"
417
- icon={BarChart3}
418
- expanded={expandedSections.includes('readability')}
419
- onToggle={() => toggleSection('readability')}
420
- >
421
- <ReadabilityGrid readability={analysis.readability} />
422
- </CollapsibleSection>
423
-
424
- {blockHints.length > 0 && (
425
- <CollapsibleSection
426
- title="Block Hints"
427
- icon={Target}
428
- expanded={expandedSections.includes('blockHints')}
429
- onToggle={() => toggleSection('blockHints')}
430
- >
431
- <PerBlockHints hints={blockHints} />
432
- </CollapsibleSection>
433
- )}
434
-
435
- <CollapsibleSection
436
- title="Basic SEO"
437
- icon={FileText}
438
- expanded={expandedSections.includes('basicSeo')}
439
- onToggle={() => toggleSection('basicSeo')}
440
- >
441
- <BasicSEOFields pageSettings={pageSettings} onPageSettingsChange={onPageSettingsChange} />
442
- </CollapsibleSection>
443
-
444
- <CollapsibleSection
445
- title="Search Preview"
446
- icon={Eye}
447
- expanded={expandedSections.includes('searchPreview')}
448
- onToggle={() => toggleSection('searchPreview')}
449
- >
450
- <SearchPreview pageSettings={pageSettings} />
451
- </CollapsibleSection>
452
-
453
- <CollapsibleSection
454
- title="Social Preview"
455
- icon={Globe}
456
- expanded={expandedSections.includes('socialPreview')}
457
- onToggle={() => toggleSection('socialPreview')}
458
- >
459
- <SocialPreview pageSettings={pageSettings} />
460
- </CollapsibleSection>
461
- </div>
462
- </div>
463
- )
464
- }