@actuate-media/cms-admin 0.8.0 → 0.8.1

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 (432) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +44 -42
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/lib/search.test.js +10 -10
  5. package/dist/__tests__/lib/search.test.js.map +1 -1
  6. package/dist/__tests__/lib/utils.test.js.map +1 -1
  7. package/dist/__tests__/router/match-route.test.js.map +1 -1
  8. package/dist/__tests__/router/strip-base.test.js.map +1 -1
  9. package/dist/components/Breadcrumbs.d.ts.map +1 -1
  10. package/dist/components/Breadcrumbs.js +2 -4
  11. package/dist/components/Breadcrumbs.js.map +1 -1
  12. package/dist/components/CommandPalette.d.ts.map +1 -1
  13. package/dist/components/CommandPalette.js +7 -3
  14. package/dist/components/CommandPalette.js.map +1 -1
  15. package/dist/components/ContentOverviewChart.d.ts.map +1 -1
  16. package/dist/components/ContentOverviewChart.js.map +1 -1
  17. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  18. package/dist/components/ErrorBoundary.js.map +1 -1
  19. package/dist/components/FocalPointPicker.d.ts.map +1 -1
  20. package/dist/components/FocalPointPicker.js +4 -2
  21. package/dist/components/FocalPointPicker.js.map +1 -1
  22. package/dist/components/FolderTree.d.ts.map +1 -1
  23. package/dist/components/FolderTree.js +18 -10
  24. package/dist/components/FolderTree.js.map +1 -1
  25. package/dist/components/LivePreview.d.ts +1 -1
  26. package/dist/components/LivePreview.d.ts.map +1 -1
  27. package/dist/components/LivePreview.js +6 -2
  28. package/dist/components/LivePreview.js.map +1 -1
  29. package/dist/components/LocaleProvider.d.ts.map +1 -1
  30. package/dist/components/LocaleProvider.js.map +1 -1
  31. package/dist/components/LocaleSwitcher.d.ts.map +1 -1
  32. package/dist/components/LocaleSwitcher.js +1 -1
  33. package/dist/components/LocaleSwitcher.js.map +1 -1
  34. package/dist/components/MediaPickerModal.d.ts.map +1 -1
  35. package/dist/components/MediaPickerModal.js.map +1 -1
  36. package/dist/components/PresenceIndicator.d.ts.map +1 -1
  37. package/dist/components/PresenceIndicator.js +5 -2
  38. package/dist/components/PresenceIndicator.js.map +1 -1
  39. package/dist/components/SEOPanel.d.ts +1 -1
  40. package/dist/components/SEOPanel.d.ts.map +1 -1
  41. package/dist/components/SEOPanel.js +110 -24
  42. package/dist/components/SEOPanel.js.map +1 -1
  43. package/dist/components/SEOPerformance.d.ts.map +1 -1
  44. package/dist/components/SEOPerformance.js +2 -2
  45. package/dist/components/SEOPerformance.js.map +1 -1
  46. package/dist/components/ThemeProvider.d.ts.map +1 -1
  47. package/dist/components/ThemeProvider.js.map +1 -1
  48. package/dist/components/TipTapEditor.d.ts.map +1 -1
  49. package/dist/components/TipTapEditor.js +5 -1
  50. package/dist/components/TipTapEditor.js.map +1 -1
  51. package/dist/components/VersionHistory.d.ts +1 -1
  52. package/dist/components/VersionHistory.d.ts.map +1 -1
  53. package/dist/components/VersionHistory.js +1 -1
  54. package/dist/components/VersionHistory.js.map +1 -1
  55. package/dist/components/ui/Avatar.d.ts.map +1 -1
  56. package/dist/components/ui/Avatar.js.map +1 -1
  57. package/dist/components/ui/Badge.d.ts.map +1 -1
  58. package/dist/components/ui/Badge.js.map +1 -1
  59. package/dist/components/ui/Button.d.ts.map +1 -1
  60. package/dist/components/ui/Button.js.map +1 -1
  61. package/dist/components/ui/CommandPalette.d.ts.map +1 -1
  62. package/dist/components/ui/CommandPalette.js +8 -2
  63. package/dist/components/ui/CommandPalette.js.map +1 -1
  64. package/dist/components/ui/ConfirmDialog.d.ts.map +1 -1
  65. package/dist/components/ui/ConfirmDialog.js.map +1 -1
  66. package/dist/components/ui/DataTable.d.ts.map +1 -1
  67. package/dist/components/ui/DataTable.js +1 -3
  68. package/dist/components/ui/DataTable.js.map +1 -1
  69. package/dist/components/ui/EmptyState.d.ts.map +1 -1
  70. package/dist/components/ui/EmptyState.js +1 -1
  71. package/dist/components/ui/EmptyState.js.map +1 -1
  72. package/dist/components/ui/Modal.d.ts.map +1 -1
  73. package/dist/components/ui/Modal.js.map +1 -1
  74. package/dist/components/ui/Pagination.d.ts +1 -1
  75. package/dist/components/ui/Pagination.d.ts.map +1 -1
  76. package/dist/components/ui/Pagination.js +7 -2
  77. package/dist/components/ui/Pagination.js.map +1 -1
  78. package/dist/components/ui/SearchInput.d.ts.map +1 -1
  79. package/dist/components/ui/SearchInput.js.map +1 -1
  80. package/dist/components/ui/Skeleton.d.ts.map +1 -1
  81. package/dist/components/ui/Skeleton.js.map +1 -1
  82. package/dist/components/ui/Toast.d.ts.map +1 -1
  83. package/dist/components/ui/Toast.js.map +1 -1
  84. package/dist/components/ui/index.d.ts.map +1 -1
  85. package/dist/components/ui/index.js.map +1 -1
  86. package/dist/fields/ArrayField.d.ts.map +1 -1
  87. package/dist/fields/ArrayField.js +1 -1
  88. package/dist/fields/ArrayField.js.map +1 -1
  89. package/dist/fields/BlockBuilderField.d.ts.map +1 -1
  90. package/dist/fields/BlockBuilderField.js +7 -7
  91. package/dist/fields/BlockBuilderField.js.map +1 -1
  92. package/dist/fields/DateField.d.ts.map +1 -1
  93. package/dist/fields/DateField.js +1 -1
  94. package/dist/fields/DateField.js.map +1 -1
  95. package/dist/fields/FieldRenderer.d.ts.map +1 -1
  96. package/dist/fields/FieldRenderer.js.map +1 -1
  97. package/dist/fields/GroupField.d.ts.map +1 -1
  98. package/dist/fields/GroupField.js +1 -1
  99. package/dist/fields/GroupField.js.map +1 -1
  100. package/dist/fields/MediaField.d.ts.map +1 -1
  101. package/dist/fields/MediaField.js +1 -1
  102. package/dist/fields/MediaField.js.map +1 -1
  103. package/dist/fields/NavBuilderField.d.ts.map +1 -1
  104. package/dist/fields/NavBuilderField.js +2 -5
  105. package/dist/fields/NavBuilderField.js.map +1 -1
  106. package/dist/fields/NumberField.d.ts +1 -1
  107. package/dist/fields/NumberField.d.ts.map +1 -1
  108. package/dist/fields/NumberField.js +2 -2
  109. package/dist/fields/NumberField.js.map +1 -1
  110. package/dist/fields/RelationshipField.d.ts.map +1 -1
  111. package/dist/fields/RelationshipField.js +7 -3
  112. package/dist/fields/RelationshipField.js.map +1 -1
  113. package/dist/fields/RichTextField.d.ts +1 -1
  114. package/dist/fields/RichTextField.d.ts.map +1 -1
  115. package/dist/fields/RichTextField.js +2 -2
  116. package/dist/fields/RichTextField.js.map +1 -1
  117. package/dist/fields/SelectField.d.ts.map +1 -1
  118. package/dist/fields/SelectField.js +9 -7
  119. package/dist/fields/SelectField.js.map +1 -1
  120. package/dist/fields/SlugField.d.ts.map +1 -1
  121. package/dist/fields/SlugField.js +1 -1
  122. package/dist/fields/SlugField.js.map +1 -1
  123. package/dist/fields/TextField.d.ts +1 -1
  124. package/dist/fields/TextField.d.ts.map +1 -1
  125. package/dist/fields/TextField.js +2 -2
  126. package/dist/fields/TextField.js.map +1 -1
  127. package/dist/fields/ToggleField.d.ts.map +1 -1
  128. package/dist/fields/ToggleField.js +1 -1
  129. package/dist/fields/ToggleField.js.map +1 -1
  130. package/dist/fields/block-types.d.ts.map +1 -1
  131. package/dist/fields/block-types.js +28 -8
  132. package/dist/fields/block-types.js.map +1 -1
  133. package/dist/fields/index.d.ts.map +1 -1
  134. package/dist/fields/index.js.map +1 -1
  135. package/dist/hooks/useBuilderState.d.ts.map +1 -1
  136. package/dist/hooks/useBuilderState.js.map +1 -1
  137. package/dist/hooks/useContentLock.d.ts.map +1 -1
  138. package/dist/hooks/useContentLock.js.map +1 -1
  139. package/dist/hooks/useDebounce.js.map +1 -1
  140. package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -1
  141. package/dist/hooks/useKeyboardShortcuts.js.map +1 -1
  142. package/dist/index.d.ts +2 -2
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/layout/Header.d.ts.map +1 -1
  146. package/dist/layout/Header.js.map +1 -1
  147. package/dist/layout/Layout.d.ts.map +1 -1
  148. package/dist/layout/Layout.js.map +1 -1
  149. package/dist/layout/Sidebar.d.ts +1 -1
  150. package/dist/layout/Sidebar.d.ts.map +1 -1
  151. package/dist/layout/Sidebar.js +5 -8
  152. package/dist/layout/Sidebar.js.map +1 -1
  153. package/dist/lib/api.js.map +1 -1
  154. package/dist/lib/search.d.ts.map +1 -1
  155. package/dist/lib/search.js +3 -5
  156. package/dist/lib/search.js.map +1 -1
  157. package/dist/lib/useApiData.d.ts.map +1 -1
  158. package/dist/lib/useApiData.js.map +1 -1
  159. package/dist/lib/utils.d.ts.map +1 -1
  160. package/dist/lib/utils.js.map +1 -1
  161. package/dist/router/index.d.ts.map +1 -1
  162. package/dist/router/index.js +1 -3
  163. package/dist/router/index.js.map +1 -1
  164. package/dist/views/CollectionList.d.ts.map +1 -1
  165. package/dist/views/CollectionList.js +56 -17
  166. package/dist/views/CollectionList.js.map +1 -1
  167. package/dist/views/Dashboard.d.ts.map +1 -1
  168. package/dist/views/Dashboard.js +26 -13
  169. package/dist/views/Dashboard.js.map +1 -1
  170. package/dist/views/DocumentEdit.d.ts +1 -1
  171. package/dist/views/DocumentEdit.d.ts.map +1 -1
  172. package/dist/views/DocumentEdit.js +33 -15
  173. package/dist/views/DocumentEdit.js.map +1 -1
  174. package/dist/views/ForgotPassword.d.ts.map +1 -1
  175. package/dist/views/ForgotPassword.js.map +1 -1
  176. package/dist/views/FormEditor.d.ts.map +1 -1
  177. package/dist/views/FormEditor.js +8 -2
  178. package/dist/views/FormEditor.js.map +1 -1
  179. package/dist/views/FormSubmissions.d.ts.map +1 -1
  180. package/dist/views/FormSubmissions.js +6 -6
  181. package/dist/views/FormSubmissions.js.map +1 -1
  182. package/dist/views/Forms.d.ts.map +1 -1
  183. package/dist/views/Forms.js.map +1 -1
  184. package/dist/views/Login.d.ts.map +1 -1
  185. package/dist/views/Login.js +5 -2
  186. package/dist/views/Login.js.map +1 -1
  187. package/dist/views/MediaBrowser.d.ts.map +1 -1
  188. package/dist/views/MediaBrowser.js +39 -19
  189. package/dist/views/MediaBrowser.js.map +1 -1
  190. package/dist/views/PageEditor.d.ts.map +1 -1
  191. package/dist/views/PageEditor.js.map +1 -1
  192. package/dist/views/Pages.d.ts.map +1 -1
  193. package/dist/views/Pages.js +20 -10
  194. package/dist/views/Pages.js.map +1 -1
  195. package/dist/views/PostEditor.d.ts.map +1 -1
  196. package/dist/views/PostEditor.js.map +1 -1
  197. package/dist/views/Posts.d.ts.map +1 -1
  198. package/dist/views/Posts.js +13 -7
  199. package/dist/views/Posts.js.map +1 -1
  200. package/dist/views/Redirects.d.ts.map +1 -1
  201. package/dist/views/Redirects.js +17 -5
  202. package/dist/views/Redirects.js.map +1 -1
  203. package/dist/views/ResetPassword.d.ts.map +1 -1
  204. package/dist/views/ResetPassword.js.map +1 -1
  205. package/dist/views/SEO.d.ts.map +1 -1
  206. package/dist/views/SEO.js +39 -16
  207. package/dist/views/SEO.js.map +1 -1
  208. package/dist/views/ScriptTagEditor.d.ts.map +1 -1
  209. package/dist/views/ScriptTagEditor.js +2 -1
  210. package/dist/views/ScriptTagEditor.js.map +1 -1
  211. package/dist/views/ScriptTags.d.ts.map +1 -1
  212. package/dist/views/ScriptTags.js.map +1 -1
  213. package/dist/views/Settings.d.ts.map +1 -1
  214. package/dist/views/Settings.js +38 -11
  215. package/dist/views/Settings.js.map +1 -1
  216. package/dist/views/SetupWizard.d.ts.map +1 -1
  217. package/dist/views/SetupWizard.js.map +1 -1
  218. package/dist/views/Users.d.ts.map +1 -1
  219. package/dist/views/Users.js +5 -3
  220. package/dist/views/Users.js.map +1 -1
  221. package/dist/views/page-builder/AIBlockAssist.d.ts.map +1 -1
  222. package/dist/views/page-builder/AIBlockAssist.js +1 -1
  223. package/dist/views/page-builder/AIBlockAssist.js.map +1 -1
  224. package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -1
  225. package/dist/views/page-builder/AIGenerateDialog.js +4 -1
  226. package/dist/views/page-builder/AIGenerateDialog.js.map +1 -1
  227. package/dist/views/page-builder/BlockEditor.d.ts.map +1 -1
  228. package/dist/views/page-builder/BlockEditor.js +1 -1
  229. package/dist/views/page-builder/BlockEditor.js.map +1 -1
  230. package/dist/views/page-builder/BlockPicker.d.ts.map +1 -1
  231. package/dist/views/page-builder/BlockPicker.js.map +1 -1
  232. package/dist/views/page-builder/BottomBar.d.ts.map +1 -1
  233. package/dist/views/page-builder/BottomBar.js.map +1 -1
  234. package/dist/views/page-builder/BuilderToolbar.d.ts.map +1 -1
  235. package/dist/views/page-builder/BuilderToolbar.js.map +1 -1
  236. package/dist/views/page-builder/ContextPanel.d.ts.map +1 -1
  237. package/dist/views/page-builder/ContextPanel.js +4 -1
  238. package/dist/views/page-builder/ContextPanel.js.map +1 -1
  239. package/dist/views/page-builder/DesignScore.d.ts.map +1 -1
  240. package/dist/views/page-builder/DesignScore.js.map +1 -1
  241. package/dist/views/page-builder/NodeSettings.d.ts.map +1 -1
  242. package/dist/views/page-builder/NodeSettings.js +1 -1
  243. package/dist/views/page-builder/NodeSettings.js.map +1 -1
  244. package/dist/views/page-builder/PageBuilder.d.ts +1 -1
  245. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  246. package/dist/views/page-builder/PageBuilder.js +4 -2
  247. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  248. package/dist/views/page-builder/PageSettings.d.ts.map +1 -1
  249. package/dist/views/page-builder/PageSettings.js.map +1 -1
  250. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  251. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  252. package/dist/views/page-builder/SEOPanel.d.ts.map +1 -1
  253. package/dist/views/page-builder/SEOPanel.js +1 -3
  254. package/dist/views/page-builder/SEOPanel.js.map +1 -1
  255. package/dist/views/page-builder/SavedSections.d.ts.map +1 -1
  256. package/dist/views/page-builder/SavedSections.js +3 -7
  257. package/dist/views/page-builder/SavedSections.js.map +1 -1
  258. package/dist/views/page-builder/TemplatePicker.d.ts.map +1 -1
  259. package/dist/views/page-builder/TemplatePicker.js.map +1 -1
  260. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +1 -1
  261. package/dist/views/page-builder/block-renderers/CTAPreview.js +1 -1
  262. package/dist/views/page-builder/block-renderers/CTAPreview.js.map +1 -1
  263. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +1 -1
  264. package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
  265. package/dist/views/page-builder/block-renderers/CardsPreview.js.map +1 -1
  266. package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +1 -1
  267. package/dist/views/page-builder/block-renderers/CodePreview.js +1 -5
  268. package/dist/views/page-builder/block-renderers/CodePreview.js.map +1 -1
  269. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +1 -1
  270. package/dist/views/page-builder/block-renderers/FAQPreview.js +4 -1
  271. package/dist/views/page-builder/block-renderers/FAQPreview.js.map +1 -1
  272. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +1 -1
  273. package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +1 -1
  274. package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +1 -1
  275. package/dist/views/page-builder/block-renderers/FormPreview.js +2 -2
  276. package/dist/views/page-builder/block-renderers/FormPreview.js.map +1 -1
  277. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +1 -1
  278. package/dist/views/page-builder/block-renderers/GalleryPreview.js +1 -3
  279. package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +1 -1
  280. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +1 -1
  281. package/dist/views/page-builder/block-renderers/HeroPreview.js.map +1 -1
  282. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +1 -1
  283. package/dist/views/page-builder/block-renderers/ImagePreview.js.map +1 -1
  284. package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +1 -1
  285. package/dist/views/page-builder/block-renderers/TextPreview.js +2 -6
  286. package/dist/views/page-builder/block-renderers/TextPreview.js.map +1 -1
  287. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +1 -1
  288. package/dist/views/page-builder/block-renderers/VideoPreview.js +2 -5
  289. package/dist/views/page-builder/block-renderers/VideoPreview.js.map +1 -1
  290. package/dist/views/page-builder/block-renderers/index.d.ts.map +1 -1
  291. package/dist/views/page-builder/block-renderers/index.js.map +1 -1
  292. package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +1 -1
  293. package/dist/views/page-builder/canvas/BlockRenderer.js +1 -5
  294. package/dist/views/page-builder/canvas/BlockRenderer.js.map +1 -1
  295. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +1 -1
  296. package/dist/views/page-builder/canvas/BuilderCanvas.js.map +1 -1
  297. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +1 -1
  298. package/dist/views/page-builder/canvas/ColumnRenderer.js +1 -5
  299. package/dist/views/page-builder/canvas/ColumnRenderer.js.map +1 -1
  300. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +1 -1
  301. package/dist/views/page-builder/canvas/ContainerRenderer.js +1 -5
  302. package/dist/views/page-builder/canvas/ContainerRenderer.js.map +1 -1
  303. package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +1 -1
  304. package/dist/views/page-builder/canvas/RowRenderer.js +1 -5
  305. package/dist/views/page-builder/canvas/RowRenderer.js.map +1 -1
  306. package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +1 -1
  307. package/dist/views/page-builder/canvas/SectionRenderer.js +1 -5
  308. package/dist/views/page-builder/canvas/SectionRenderer.js.map +1 -1
  309. package/dist/views/page-builder/canvas/index.d.ts.map +1 -1
  310. package/dist/views/page-builder/canvas/index.js.map +1 -1
  311. package/package.json +2 -2
  312. package/src/AdminRoot.tsx +263 -191
  313. package/src/__tests__/lib/search.test.ts +60 -69
  314. package/src/__tests__/lib/utils.test.ts +12 -12
  315. package/src/__tests__/router/match-route.test.ts +24 -26
  316. package/src/__tests__/router/strip-base.test.ts +15 -15
  317. package/src/components/Breadcrumbs.tsx +27 -24
  318. package/src/components/CommandPalette.tsx +115 -99
  319. package/src/components/ContentOverviewChart.tsx +19 -14
  320. package/src/components/ErrorBoundary.tsx +13 -13
  321. package/src/components/FocalPointPicker.tsx +31 -20
  322. package/src/components/FolderTree.tsx +172 -139
  323. package/src/components/LivePreview.tsx +68 -41
  324. package/src/components/LocaleProvider.tsx +26 -20
  325. package/src/components/LocaleSwitcher.tsx +9 -11
  326. package/src/components/MediaPickerModal.tsx +46 -45
  327. package/src/components/PresenceIndicator.tsx +30 -27
  328. package/src/components/SEOPanel.tsx +378 -228
  329. package/src/components/SEOPerformance.tsx +52 -30
  330. package/src/components/ThemeProvider.tsx +46 -46
  331. package/src/components/TipTapEditor.tsx +60 -64
  332. package/src/components/VersionHistory.tsx +63 -52
  333. package/src/components/ui/Avatar.tsx +8 -8
  334. package/src/components/ui/Badge.tsx +7 -5
  335. package/src/components/ui/Button.tsx +24 -13
  336. package/src/components/ui/CommandPalette.tsx +56 -42
  337. package/src/components/ui/ConfirmDialog.tsx +14 -14
  338. package/src/components/ui/DataTable.tsx +37 -39
  339. package/src/components/ui/EmptyState.tsx +9 -11
  340. package/src/components/ui/Modal.tsx +21 -15
  341. package/src/components/ui/Pagination.tsx +34 -19
  342. package/src/components/ui/SearchInput.tsx +17 -7
  343. package/src/components/ui/Skeleton.tsx +7 -7
  344. package/src/components/ui/Toast.tsx +29 -22
  345. package/src/components/ui/index.ts +24 -24
  346. package/src/fields/ArrayField.tsx +43 -25
  347. package/src/fields/BlockBuilderField.tsx +80 -99
  348. package/src/fields/DateField.tsx +20 -12
  349. package/src/fields/FieldRenderer.tsx +34 -34
  350. package/src/fields/GroupField.tsx +8 -10
  351. package/src/fields/MediaField.tsx +8 -10
  352. package/src/fields/NavBuilderField.tsx +24 -25
  353. package/src/fields/NumberField.tsx +21 -14
  354. package/src/fields/RelationshipField.tsx +105 -91
  355. package/src/fields/RichTextField.tsx +16 -12
  356. package/src/fields/SelectField.tsx +42 -34
  357. package/src/fields/SlugField.tsx +29 -17
  358. package/src/fields/TextField.tsx +24 -16
  359. package/src/fields/ToggleField.tsx +7 -9
  360. package/src/fields/block-types.ts +50 -24
  361. package/src/fields/index.ts +17 -17
  362. package/src/hooks/useBuilderState.ts +260 -221
  363. package/src/hooks/useContentLock.ts +23 -20
  364. package/src/hooks/useDebounce.ts +7 -7
  365. package/src/hooks/useKeyboardShortcuts.ts +16 -16
  366. package/src/index.ts +69 -58
  367. package/src/layout/Header.tsx +21 -20
  368. package/src/layout/Layout.tsx +22 -24
  369. package/src/layout/Sidebar.tsx +107 -72
  370. package/src/lib/api.ts +34 -34
  371. package/src/lib/search.ts +30 -34
  372. package/src/lib/useApiData.ts +65 -62
  373. package/src/lib/utils.ts +3 -3
  374. package/src/router/index.ts +33 -35
  375. package/src/styles/build-input.css +2 -2
  376. package/src/styles/tailwind.css +1 -1
  377. package/src/styles/theme.css +7 -1
  378. package/src/views/CollectionList.tsx +275 -121
  379. package/src/views/Dashboard.tsx +164 -117
  380. package/src/views/DocumentEdit.tsx +298 -253
  381. package/src/views/ForgotPassword.tsx +27 -23
  382. package/src/views/FormEditor.tsx +165 -99
  383. package/src/views/FormSubmissions.tsx +261 -117
  384. package/src/views/Forms.tsx +56 -26
  385. package/src/views/Login.tsx +107 -84
  386. package/src/views/MediaBrowser.tsx +717 -523
  387. package/src/views/PageEditor.tsx +44 -46
  388. package/src/views/Pages.tsx +312 -149
  389. package/src/views/PostEditor.tsx +57 -51
  390. package/src/views/Posts.tsx +206 -74
  391. package/src/views/Redirects.tsx +173 -117
  392. package/src/views/ResetPassword.tsx +43 -32
  393. package/src/views/SEO.tsx +589 -160
  394. package/src/views/ScriptTagEditor.tsx +69 -69
  395. package/src/views/ScriptTags.tsx +54 -42
  396. package/src/views/Settings.tsx +430 -220
  397. package/src/views/SetupWizard.tsx +69 -46
  398. package/src/views/Users.tsx +154 -120
  399. package/src/views/page-builder/AIBlockAssist.tsx +21 -25
  400. package/src/views/page-builder/AIGenerateDialog.tsx +134 -127
  401. package/src/views/page-builder/BlockEditor.tsx +94 -96
  402. package/src/views/page-builder/BlockPicker.tsx +73 -88
  403. package/src/views/page-builder/BottomBar.tsx +15 -11
  404. package/src/views/page-builder/BuilderToolbar.tsx +32 -29
  405. package/src/views/page-builder/ContextPanel.tsx +57 -57
  406. package/src/views/page-builder/DesignScore.tsx +52 -59
  407. package/src/views/page-builder/NodeSettings.tsx +59 -59
  408. package/src/views/page-builder/PageBuilder.tsx +156 -155
  409. package/src/views/page-builder/PageSettings.tsx +16 -15
  410. package/src/views/page-builder/PageTemplates.tsx +23 -17
  411. package/src/views/page-builder/SEOPanel.tsx +90 -111
  412. package/src/views/page-builder/SavedSections.tsx +99 -105
  413. package/src/views/page-builder/TemplatePicker.tsx +44 -48
  414. package/src/views/page-builder/block-renderers/CTAPreview.tsx +11 -13
  415. package/src/views/page-builder/block-renderers/CardsPreview.tsx +13 -15
  416. package/src/views/page-builder/block-renderers/CodePreview.tsx +16 -16
  417. package/src/views/page-builder/block-renderers/FAQPreview.tsx +20 -23
  418. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +5 -5
  419. package/src/views/page-builder/block-renderers/FormPreview.tsx +9 -13
  420. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +22 -28
  421. package/src/views/page-builder/block-renderers/HeroPreview.tsx +17 -30
  422. package/src/views/page-builder/block-renderers/ImagePreview.tsx +12 -12
  423. package/src/views/page-builder/block-renderers/TextPreview.tsx +22 -22
  424. package/src/views/page-builder/block-renderers/VideoPreview.tsx +13 -18
  425. package/src/views/page-builder/block-renderers/index.ts +17 -17
  426. package/src/views/page-builder/canvas/BlockRenderer.tsx +19 -23
  427. package/src/views/page-builder/canvas/BuilderCanvas.tsx +17 -20
  428. package/src/views/page-builder/canvas/ColumnRenderer.tsx +22 -26
  429. package/src/views/page-builder/canvas/ContainerRenderer.tsx +20 -24
  430. package/src/views/page-builder/canvas/RowRenderer.tsx +19 -23
  431. package/src/views/page-builder/canvas/SectionRenderer.tsx +30 -34
  432. package/src/views/page-builder/canvas/index.ts +2 -2
package/src/views/SEO.tsx CHANGED
@@ -1,136 +1,196 @@
1
- 'use client';
1
+ 'use client'
2
2
 
3
- import * as Tabs from '@radix-ui/react-tabs';
3
+ import * as Tabs from '@radix-ui/react-tabs'
4
4
  import {
5
- Search, Globe, FileCode2, BarChart3, AlertTriangle, CheckCircle2, XCircle,
6
- ArrowUpRight, RefreshCw, BookOpen, Link2, Bot, Plus, Pencil, Trash2,
7
- ExternalLink, ArrowRightLeft, Copy, ShieldCheck, Loader2,
8
- } from 'lucide-react';
9
- import { useState, useMemo, type FormEvent } from 'react';
10
- import { toast } from 'sonner';
11
- import * as Dialog from '@radix-ui/react-dialog';
12
- import { useApiData } from '../lib/useApiData.js';
13
- import { cmsApi } from '../lib/api.js';
5
+ Search,
6
+ Globe,
7
+ FileCode2,
8
+ BarChart3,
9
+ AlertTriangle,
10
+ CheckCircle2,
11
+ XCircle,
12
+ ArrowUpRight,
13
+ RefreshCw,
14
+ BookOpen,
15
+ Link2,
16
+ Bot,
17
+ Plus,
18
+ Pencil,
19
+ Trash2,
20
+ ExternalLink,
21
+ ArrowRightLeft,
22
+ Copy,
23
+ ShieldCheck,
24
+ Loader2,
25
+ } from 'lucide-react'
26
+ import { useState, useMemo, type FormEvent } from 'react'
27
+ import { toast } from 'sonner'
28
+ import * as Dialog from '@radix-ui/react-dialog'
29
+ import { useApiData } from '../lib/useApiData.js'
30
+ import { cmsApi } from '../lib/api.js'
14
31
 
15
32
  export interface SEOProps {
16
- onNavigate?: (path: string) => void;
17
- initialTab?: string;
33
+ onNavigate?: (path: string) => void
34
+ initialTab?: string
18
35
  }
19
36
 
20
37
  export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
21
- const { data: seoData, loading: seoLoading, error: seoError, refetch: seoRefetch } = useApiData<any[]>('/seo/pages');
22
- const { data: redirectsData, loading: redirectsLoading, error: redirectsError, refetch: redirectsRefetch } = useApiData<any[]>('/redirects');
23
- const { data: linkHealthData, loading: linkHealthLoading } = useApiData<any[]>('/seo/link-health');
24
-
25
- const [activeTab, setActiveTab] = useState(initialTab);
26
- const [searchQuery, setSearchQuery] = useState('');
27
- const [filterScore, setFilterScore] = useState<string>('all');
28
- const [scanning, setScanning] = useState(false);
29
-
30
- const [showAddRedirect, setShowAddRedirect] = useState(false);
31
- const [newRedirect, setNewRedirect] = useState({ source: '', destination: '', type: '301' as '301' | '302' });
32
- const [redirectSearch, setRedirectSearch] = useState('');
33
-
34
- const seoPages = seoData ?? [];
35
- const redirects = redirectsData ?? [];
36
- const linkHealth = linkHealthData ?? [];
38
+ const {
39
+ data: seoData,
40
+ loading: seoLoading,
41
+ error: seoError,
42
+ refetch: seoRefetch,
43
+ } = useApiData<any[]>('/seo/pages')
44
+ const {
45
+ data: redirectsData,
46
+ loading: redirectsLoading,
47
+ error: redirectsError,
48
+ refetch: redirectsRefetch,
49
+ } = useApiData<any[]>('/redirects')
50
+ const { data: linkHealthData, loading: linkHealthLoading } = useApiData<any[]>('/seo/link-health')
51
+
52
+ const [activeTab, setActiveTab] = useState(initialTab)
53
+ const [searchQuery, setSearchQuery] = useState('')
54
+ const [filterScore, setFilterScore] = useState<string>('all')
55
+ const [scanning, setScanning] = useState(false)
56
+
57
+ const [showAddRedirect, setShowAddRedirect] = useState(false)
58
+ const [newRedirect, setNewRedirect] = useState({
59
+ source: '',
60
+ destination: '',
61
+ type: '301' as '301' | '302',
62
+ })
63
+ const [redirectSearch, setRedirectSearch] = useState('')
64
+
65
+ const seoPages = seoData ?? []
66
+ const redirects = redirectsData ?? []
67
+ const linkHealth = linkHealthData ?? []
37
68
 
38
69
  const handleTabChange = (tab: string) => {
39
- setActiveTab(tab);
40
- const path = tab === 'pages' ? '/seo' : `/seo/${tab}`;
41
- onNavigate?.(path);
42
- };
70
+ setActiveTab(tab)
71
+ const path = tab === 'pages' ? '/seo' : `/seo/${tab}`
72
+ onNavigate?.(path)
73
+ }
43
74
 
44
75
  // --- Pages tab data ---
45
76
  const filtered = seoPages
46
77
  .filter((page: any) => {
47
- const matchesSearch = (page.url ?? '').toLowerCase().includes(searchQuery.toLowerCase()) || (page.title ?? '').toLowerCase().includes(searchQuery.toLowerCase());
48
- const matchesScore = filterScore === 'all' || (filterScore === 'good' && page.score >= 80) || (filterScore === 'warning' && page.score >= 50 && page.score < 80) || (filterScore === 'critical' && page.score < 50);
49
- return matchesSearch && matchesScore;
78
+ const matchesSearch =
79
+ (page.url ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
80
+ (page.title ?? '').toLowerCase().includes(searchQuery.toLowerCase())
81
+ const matchesScore =
82
+ filterScore === 'all' ||
83
+ (filterScore === 'good' && page.score >= 80) ||
84
+ (filterScore === 'warning' && page.score >= 50 && page.score < 80) ||
85
+ (filterScore === 'critical' && page.score < 50)
86
+ return matchesSearch && matchesScore
50
87
  })
51
- .sort((a: any, b: any) => !searchQuery ? b.issues - a.issues : 0);
88
+ .sort((a: any, b: any) => (!searchQuery ? b.issues - a.issues : 0))
52
89
 
53
- const totalIssues = seoPages.reduce((sum: number, p: any) => sum + p.issues, 0);
54
- const avgScore = seoPages.length > 0 ? Math.round(seoPages.reduce((sum: number, p: any) => sum + p.score, 0) / seoPages.length) : 0;
55
- const missingMeta = seoPages.filter((p: any) => !p.metaTitle || !p.metaDescription).length;
56
- const missingSchema = seoPages.filter((p: any) => !p.schemaType).length;
90
+ const totalIssues = seoPages.reduce((sum: number, p: any) => sum + p.issues, 0)
91
+ const avgScore =
92
+ seoPages.length > 0
93
+ ? Math.round(seoPages.reduce((sum: number, p: any) => sum + p.score, 0) / seoPages.length)
94
+ : 0
95
+ const missingMeta = seoPages.filter((p: any) => !p.metaTitle || !p.metaDescription).length
96
+ const missingSchema = seoPages.filter((p: any) => !p.schemaType).length
57
97
 
58
98
  // --- Canonical data ---
59
- const missingCanonical = seoPages.filter((p: any) => !p.canonical);
60
- const allCanonicals = seoPages.filter((p: any) => p.canonical);
61
- const canonicalDomains = [...new Set(allCanonicals.map((p: any) => { try { return new URL(p.canonical).hostname; } catch { return ''; } }).filter(Boolean))];
99
+ const missingCanonical = seoPages.filter((p: any) => !p.canonical)
100
+ const allCanonicals = seoPages.filter((p: any) => p.canonical)
101
+ const canonicalDomains = [
102
+ ...new Set(
103
+ allCanonicals
104
+ .map((p: any) => {
105
+ try {
106
+ return new URL(p.canonical).hostname
107
+ } catch {
108
+ return ''
109
+ }
110
+ })
111
+ .filter(Boolean),
112
+ ),
113
+ ]
62
114
 
63
115
  // --- Redirects data ---
64
- const filteredRedirects = redirects.filter((r: any) =>
65
- (r.from ?? '').toLowerCase().includes(redirectSearch.toLowerCase()) ||
66
- (r.to ?? '').toLowerCase().includes(redirectSearch.toLowerCase())
67
- );
116
+ const filteredRedirects = redirects.filter(
117
+ (r: any) =>
118
+ (r.from ?? '').toLowerCase().includes(redirectSearch.toLowerCase()) ||
119
+ (r.to ?? '').toLowerCase().includes(redirectSearch.toLowerCase()),
120
+ )
68
121
 
69
122
  const handleScan = async () => {
70
- setScanning(true);
123
+ setScanning(true)
71
124
  try {
72
- const res = await cmsApi('/seo/scan', { method: 'POST' });
125
+ const res = await cmsApi('/seo/scan', { method: 'POST' })
73
126
  if (res.error) {
74
- toast.error(res.error);
127
+ toast.error(res.error)
75
128
  } else {
76
- const d = res.data as { total: number; pagesWithIssues: number; totalProblems: number };
77
- toast.success(`SEO scan complete — ${d.totalProblems} issues found across ${d.pagesWithIssues} of ${d.total} pages`);
78
- seoRefetch();
129
+ const d = res.data as { total: number; pagesWithIssues: number; totalProblems: number }
130
+ toast.success(
131
+ `SEO scan complete — ${d.totalProblems} issues found across ${d.pagesWithIssues} of ${d.total} pages`,
132
+ )
133
+ seoRefetch()
79
134
  }
80
135
  } catch {
81
- toast.error('SEO scan failed');
136
+ toast.error('SEO scan failed')
82
137
  } finally {
83
- setScanning(false);
138
+ setScanning(false)
84
139
  }
85
- };
140
+ }
86
141
 
87
142
  const handleAddRedirect = async (e: FormEvent) => {
88
- e.preventDefault();
143
+ e.preventDefault()
89
144
  const res = await cmsApi('/redirects', {
90
145
  method: 'POST',
91
- body: JSON.stringify({ from: newRedirect.source, to: newRedirect.destination, type: newRedirect.type }),
92
- });
146
+ body: JSON.stringify({
147
+ from: newRedirect.source,
148
+ to: newRedirect.destination,
149
+ type: newRedirect.type,
150
+ }),
151
+ })
93
152
  if (res.error) {
94
- toast.error(res.error);
153
+ toast.error(res.error)
95
154
  } else {
96
- toast.success('Redirect added');
97
- redirectsRefetch();
155
+ toast.success('Redirect added')
156
+ redirectsRefetch()
98
157
  }
99
- setShowAddRedirect(false);
100
- setNewRedirect({ source: '', destination: '', type: '301' });
101
- };
158
+ setShowAddRedirect(false)
159
+ setNewRedirect({ source: '', destination: '', type: '301' })
160
+ }
102
161
 
103
162
  const handleDeleteRedirect = async (id: number) => {
104
- const res = await cmsApi(`/redirects/${id}`, { method: 'DELETE' });
163
+ const res = await cmsApi(`/redirects/${id}`, { method: 'DELETE' })
105
164
  if (res.error) {
106
- toast.error(res.error);
165
+ toast.error(res.error)
107
166
  } else {
108
- toast.success('Redirect deleted');
109
- redirectsRefetch();
167
+ toast.success('Redirect deleted')
168
+ redirectsRefetch()
110
169
  }
111
- };
170
+ }
112
171
 
113
172
  function scoreBadge(score: number) {
114
- if (score >= 80) return 'bg-green-100 text-green-800';
115
- if (score >= 50) return 'bg-yellow-100 text-yellow-800';
116
- return 'bg-red-100 text-red-800';
173
+ if (score >= 80) return 'bg-green-100 text-green-800'
174
+ if (score >= 50) return 'bg-yellow-100 text-yellow-800'
175
+ return 'bg-red-100 text-red-800'
117
176
  }
118
177
  function scoreIcon(score: number) {
119
- if (score >= 80) return <CheckCircle2 className="w-4 h-4 text-green-600" />;
120
- if (score >= 50) return <AlertTriangle className="w-4 h-4 text-yellow-600" />;
121
- return <XCircle className="w-4 h-4 text-red-600" />;
178
+ if (score >= 80) return <CheckCircle2 className="w-4 h-4 text-green-600" />
179
+ if (score >= 50) return <AlertTriangle className="w-4 h-4 text-yellow-600" />
180
+ return <XCircle className="w-4 h-4 text-red-600" />
122
181
  }
123
182
 
124
- const tabClass = "px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:text-gray-900 data-[state=active]:border-b-2 data-[state=active]:border-blue-600 data-[state=active]:text-blue-600 shrink-0";
183
+ const tabClass =
184
+ 'px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:text-gray-900 data-[state=active]:border-b-2 data-[state=active]:border-blue-600 data-[state=active]:text-blue-600 shrink-0'
125
185
 
126
- const isLoading = seoLoading || redirectsLoading || linkHealthLoading;
186
+ const isLoading = seoLoading || redirectsLoading || linkHealthLoading
127
187
 
128
188
  if (isLoading) {
129
189
  return (
130
190
  <div className="p-3 pr-6 sm:p-4 sm:pr-8 flex items-center justify-center h-64">
131
191
  <Loader2 className="w-6 h-6 animate-spin text-blue-600" />
132
192
  </div>
133
- );
193
+ )
134
194
  }
135
195
 
136
196
  return (
@@ -139,16 +199,30 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
139
199
  <div className="mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
140
200
  <AlertTriangle className="w-5 h-5 text-red-600 shrink-0" />
141
201
  <span className="text-sm text-red-800 flex-1">{seoError || redirectsError}</span>
142
- <button onClick={() => { seoRefetch(); redirectsRefetch(); }} className="px-3 py-1 text-sm text-red-700 border border-red-300 rounded-lg hover:bg-red-100 transition-colors">Retry</button>
202
+ <button
203
+ onClick={() => {
204
+ seoRefetch()
205
+ redirectsRefetch()
206
+ }}
207
+ className="px-3 py-1 text-sm text-red-700 border border-red-300 rounded-lg hover:bg-red-100 transition-colors"
208
+ >
209
+ Retry
210
+ </button>
143
211
  </div>
144
212
  )}
145
213
 
146
214
  <div className="flex flex-col sm:flex-row sm:items-center justify-between mb-4 gap-3">
147
215
  <div>
148
216
  <h1 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-1">SEO & Redirects</h1>
149
- <p className="text-sm text-gray-600">Search optimization, redirects, canonicalization, and link health</p>
217
+ <p className="text-sm text-gray-600">
218
+ Search optimization, redirects, canonicalization, and link health
219
+ </p>
150
220
  </div>
151
- <button onClick={handleScan} disabled={scanning} className="flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm disabled:opacity-60">
221
+ <button
222
+ onClick={handleScan}
223
+ disabled={scanning}
224
+ className="flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm disabled:opacity-60"
225
+ >
152
226
  <RefreshCw className={`w-4 h-4 ${scanning ? 'animate-spin' : ''}`} />
153
227
  {scanning ? 'Scanning...' : 'Run SEO Scan'}
154
228
  </button>
@@ -157,16 +231,28 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
157
231
  <Tabs.Root value={activeTab} onValueChange={handleTabChange}>
158
232
  <Tabs.List className="mb-4 flex gap-1 border-b border-gray-200 overflow-x-auto">
159
233
  <Tabs.Trigger value="pages" className={tabClass}>
160
- <span className="flex items-center gap-1.5"><BarChart3 className="w-4 h-4" />Pages</span>
234
+ <span className="flex items-center gap-1.5">
235
+ <BarChart3 className="w-4 h-4" />
236
+ Pages
237
+ </span>
161
238
  </Tabs.Trigger>
162
239
  <Tabs.Trigger value="redirects" className={tabClass}>
163
- <span className="flex items-center gap-1.5"><ArrowRightLeft className="w-4 h-4" />Redirects</span>
240
+ <span className="flex items-center gap-1.5">
241
+ <ArrowRightLeft className="w-4 h-4" />
242
+ Redirects
243
+ </span>
164
244
  </Tabs.Trigger>
165
245
  <Tabs.Trigger value="canonicals" className={tabClass}>
166
- <span className="flex items-center gap-1.5"><Copy className="w-4 h-4" />Canonicalization</span>
246
+ <span className="flex items-center gap-1.5">
247
+ <Copy className="w-4 h-4" />
248
+ Canonicalization
249
+ </span>
167
250
  </Tabs.Trigger>
168
251
  <Tabs.Trigger value="links" className={tabClass}>
169
- <span className="flex items-center gap-1.5"><Link2 className="w-4 h-4" />Link Health</span>
252
+ <span className="flex items-center gap-1.5">
253
+ <Link2 className="w-4 h-4" />
254
+ Link Health
255
+ </span>
170
256
  </Tabs.Trigger>
171
257
  </Tabs.List>
172
258
 
@@ -174,22 +260,40 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
174
260
  <Tabs.Content value="pages" className="flex flex-col flex-1 min-h-0">
175
261
  <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-4">
176
262
  <div className="bg-white rounded-lg border border-gray-200 p-4">
177
- <div className="flex items-center gap-2 mb-2"><BarChart3 className="w-4 h-4 text-blue-600" /><span className="text-xs font-medium text-gray-600">Avg SEO Score</span></div>
263
+ <div className="flex items-center gap-2 mb-2">
264
+ <BarChart3 className="w-4 h-4 text-blue-600" />
265
+ <span className="text-xs font-medium text-gray-600">Avg SEO Score</span>
266
+ </div>
178
267
  <div className="text-2xl font-semibold text-gray-900">{avgScore}</div>
179
- <div className={`text-xs mt-1 ${avgScore >= 80 ? 'text-green-600' : avgScore >= 50 ? 'text-yellow-600' : 'text-red-600'}`}>{avgScore >= 80 ? 'Good' : avgScore >= 50 ? 'Needs improvement' : 'Critical'}</div>
268
+ <div
269
+ className={`text-xs mt-1 ${avgScore >= 80 ? 'text-green-600' : avgScore >= 50 ? 'text-yellow-600' : 'text-red-600'}`}
270
+ >
271
+ {avgScore >= 80 ? 'Good' : avgScore >= 50 ? 'Needs improvement' : 'Critical'}
272
+ </div>
180
273
  </div>
181
274
  <div className="bg-white rounded-lg border border-gray-200 p-4">
182
- <div className="flex items-center gap-2 mb-2"><AlertTriangle className="w-4 h-4 text-yellow-600" /><span className="text-xs font-medium text-gray-600">Total Issues</span></div>
275
+ <div className="flex items-center gap-2 mb-2">
276
+ <AlertTriangle className="w-4 h-4 text-yellow-600" />
277
+ <span className="text-xs font-medium text-gray-600">Total Issues</span>
278
+ </div>
183
279
  <div className="text-2xl font-semibold text-gray-900">{totalIssues}</div>
184
- <div className="text-xs text-gray-500 mt-1">Across {seoPages.filter((p: any) => p.issues > 0).length} pages</div>
280
+ <div className="text-xs text-gray-500 mt-1">
281
+ Across {seoPages.filter((p: any) => p.issues > 0).length} pages
282
+ </div>
185
283
  </div>
186
284
  <div className="bg-white rounded-lg border border-gray-200 p-4">
187
- <div className="flex items-center gap-2 mb-2"><FileCode2 className="w-4 h-4 text-purple-600" /><span className="text-xs font-medium text-gray-600">Missing Meta</span></div>
285
+ <div className="flex items-center gap-2 mb-2">
286
+ <FileCode2 className="w-4 h-4 text-purple-600" />
287
+ <span className="text-xs font-medium text-gray-600">Missing Meta</span>
288
+ </div>
188
289
  <div className="text-2xl font-semibold text-gray-900">{missingMeta}</div>
189
290
  <div className="text-xs text-gray-500 mt-1">Title or description</div>
190
291
  </div>
191
292
  <div className="bg-white rounded-lg border border-gray-200 p-4">
192
- <div className="flex items-center gap-2 mb-2"><Globe className="w-4 h-4 text-green-600" /><span className="text-xs font-medium text-gray-600">Missing Schema</span></div>
293
+ <div className="flex items-center gap-2 mb-2">
294
+ <Globe className="w-4 h-4 text-green-600" />
295
+ <span className="text-xs font-medium text-gray-600">Missing Schema</span>
296
+ </div>
193
297
  <div className="text-2xl font-semibold text-gray-900">{missingSchema}</div>
194
298
  <div className="text-xs text-gray-500 mt-1">No Schema.org markup</div>
195
299
  </div>
@@ -199,11 +303,23 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
199
303
  <div className="flex items-start gap-3">
200
304
  <Bot className="w-5 h-5 text-indigo-600 mt-0.5 shrink-0" />
201
305
  <div className="flex-1">
202
- <h3 className="text-sm font-semibold text-indigo-900 mb-1">AI SEO Recommendations</h3>
203
- <p className="text-sm text-indigo-700 mb-2">{missingMeta} pages missing meta descriptions. {missingCanonical.length} pages without canonical URLs. AI can auto-generate these from page content.</p>
306
+ <h3 className="text-sm font-semibold text-indigo-900 mb-1">
307
+ AI SEO Recommendations
308
+ </h3>
309
+ <p className="text-sm text-indigo-700 mb-2">
310
+ {missingMeta} pages missing meta descriptions. {missingCanonical.length} pages
311
+ without canonical URLs. AI can auto-generate these from page content.
312
+ </p>
204
313
  <div className="flex items-center gap-2">
205
- <button onClick={() => onNavigate?.('/settings')} className="px-3 py-1.5 text-xs bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">Configure AI</button>
206
- <button className="px-3 py-1.5 text-xs border border-indigo-300 text-indigo-700 rounded-lg hover:bg-indigo-50 transition-colors">Auto-fix All</button>
314
+ <button
315
+ onClick={() => onNavigate?.('/settings')}
316
+ className="px-3 py-1.5 text-xs bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
317
+ >
318
+ Configure AI
319
+ </button>
320
+ <button className="px-3 py-1.5 text-xs border border-indigo-300 text-indigo-700 rounded-lg hover:bg-indigo-50 transition-colors">
321
+ Auto-fix All
322
+ </button>
207
323
  </div>
208
324
  </div>
209
325
  </div>
@@ -213,9 +329,19 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
213
329
  <div className="p-3 flex flex-col sm:flex-row gap-3">
214
330
  <div className="relative flex-1">
215
331
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
216
- <input type="text" placeholder="Search pages by URL or title..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
332
+ <input
333
+ type="text"
334
+ placeholder="Search pages by URL or title..."
335
+ value={searchQuery}
336
+ onChange={(e) => setSearchQuery(e.target.value)}
337
+ className="w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
338
+ />
217
339
  </div>
218
- <select value={filterScore} onChange={(e) => setFilterScore(e.target.value)} className="px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
340
+ <select
341
+ value={filterScore}
342
+ onChange={(e) => setFilterScore(e.target.value)}
343
+ className="px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
344
+ >
219
345
  <option value="all">All Scores</option>
220
346
  <option value="good">Good (80+)</option>
221
347
  <option value="warning">Needs Work (50-79)</option>
@@ -231,23 +357,105 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
231
357
  <tr>
232
358
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Page</th>
233
359
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Score</th>
234
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Readability</th>
235
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Schema</th>
360
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
361
+ Readability
362
+ </th>
363
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
364
+ Schema
365
+ </th>
236
366
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Meta</th>
237
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Issues</th>
238
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Actions</th>
367
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
368
+ Issues
369
+ </th>
370
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
371
+ Actions
372
+ </th>
239
373
  </tr>
240
374
  </thead>
241
375
  <tbody className="divide-y divide-gray-200">
242
376
  {filtered.map((page: any) => (
243
377
  <tr key={page.id} className="hover:bg-gray-50 transition-colors">
244
- <td className="px-4 py-3"><div className="text-sm font-medium text-gray-900">{page.title}</div><div className="text-xs text-gray-500">{page.url}</div></td>
245
- <td className="px-4 py-3"><div className="flex items-center gap-2">{scoreIcon(page.score)}<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${scoreBadge(page.score)}`}>{page.score}</span></div></td>
246
- <td className="px-4 py-3">{page.readability > 0 ? <div className="flex items-center gap-2"><BookOpen className="w-3.5 h-3.5 text-gray-400" /><span className={`text-sm ${page.readability >= 80 ? 'text-green-700' : page.readability >= 60 ? 'text-yellow-700' : 'text-red-700'}`}>{page.readability}</span></div> : <span className="text-xs text-gray-400">—</span>}</td>
247
- <td className="px-4 py-3">{page.schemaType ? <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">{page.schemaType}</span> : <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Missing</span>}</td>
248
- <td className="px-4 py-3"><div className="flex flex-col gap-0.5"><span className={`text-xs ${page.metaTitle ? 'text-green-700' : 'text-red-600'}`}>{page.metaTitle ? '✓ Title' : '✗ Title'}</span><span className={`text-xs ${page.metaDescription ? 'text-green-700' : 'text-red-600'}`}>{page.metaDescription ? '✓ Desc' : '✗ Desc'}</span></div></td>
249
- <td className="px-4 py-3">{page.issues > 0 ? <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">{page.issues}</span> : <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">✓</span>}</td>
250
- <td className="px-4 py-3"><div className="flex items-center gap-1"><button onClick={() => onNavigate?.(`/pages/${page.id}`)} className="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Edit"><ArrowUpRight className="w-4 h-4 text-gray-600" /></button><button className="p-1.5 hover:bg-gray-100 rounded transition-colors" title="AI analyze"><Bot className="w-4 h-4 text-indigo-600" /></button></div></td>
378
+ <td className="px-4 py-3">
379
+ <div className="text-sm font-medium text-gray-900">{page.title}</div>
380
+ <div className="text-xs text-gray-500">{page.url}</div>
381
+ </td>
382
+ <td className="px-4 py-3">
383
+ <div className="flex items-center gap-2">
384
+ {scoreIcon(page.score)}
385
+ <span
386
+ className={`px-2 py-0.5 rounded-full text-xs font-medium ${scoreBadge(page.score)}`}
387
+ >
388
+ {page.score}
389
+ </span>
390
+ </div>
391
+ </td>
392
+ <td className="px-4 py-3">
393
+ {page.readability > 0 ? (
394
+ <div className="flex items-center gap-2">
395
+ <BookOpen className="w-3.5 h-3.5 text-gray-400" />
396
+ <span
397
+ className={`text-sm ${page.readability >= 80 ? 'text-green-700' : page.readability >= 60 ? 'text-yellow-700' : 'text-red-700'}`}
398
+ >
399
+ {page.readability}
400
+ </span>
401
+ </div>
402
+ ) : (
403
+ <span className="text-xs text-gray-400">—</span>
404
+ )}
405
+ </td>
406
+ <td className="px-4 py-3">
407
+ {page.schemaType ? (
408
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
409
+ {page.schemaType}
410
+ </span>
411
+ ) : (
412
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
413
+ Missing
414
+ </span>
415
+ )}
416
+ </td>
417
+ <td className="px-4 py-3">
418
+ <div className="flex flex-col gap-0.5">
419
+ <span
420
+ className={`text-xs ${page.metaTitle ? 'text-green-700' : 'text-red-600'}`}
421
+ >
422
+ {page.metaTitle ? '✓ Title' : '✗ Title'}
423
+ </span>
424
+ <span
425
+ className={`text-xs ${page.metaDescription ? 'text-green-700' : 'text-red-600'}`}
426
+ >
427
+ {page.metaDescription ? '✓ Desc' : '✗ Desc'}
428
+ </span>
429
+ </div>
430
+ </td>
431
+ <td className="px-4 py-3">
432
+ {page.issues > 0 ? (
433
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
434
+ {page.issues}
435
+ </span>
436
+ ) : (
437
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
438
+
439
+ </span>
440
+ )}
441
+ </td>
442
+ <td className="px-4 py-3">
443
+ <div className="flex items-center gap-1">
444
+ <button
445
+ onClick={() => onNavigate?.(`/pages/${page.id}`)}
446
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
447
+ title="Edit"
448
+ >
449
+ <ArrowUpRight className="w-4 h-4 text-gray-600" />
450
+ </button>
451
+ <button
452
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
453
+ title="AI analyze"
454
+ >
455
+ <Bot className="w-4 h-4 text-indigo-600" />
456
+ </button>
457
+ </div>
458
+ </td>
251
459
  </tr>
252
460
  ))}
253
461
  </tbody>
@@ -265,25 +473,41 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
265
473
  </div>
266
474
  <div className="bg-white rounded-lg border border-gray-200 p-4">
267
475
  <div className="text-xs text-gray-600 mb-1">Active</div>
268
- <div className="text-2xl font-semibold text-green-600">{redirects.filter((r: any) => r.active).length}</div>
476
+ <div className="text-2xl font-semibold text-green-600">
477
+ {redirects.filter((r: any) => r.active).length}
478
+ </div>
269
479
  </div>
270
480
  <div className="bg-white rounded-lg border border-gray-200 p-4">
271
481
  <div className="text-xs text-gray-600 mb-1">Total Hits</div>
272
- <div className="text-2xl font-semibold text-blue-600">{redirects.reduce((s: number, r: any) => s + r.hits, 0).toLocaleString()}</div>
482
+ <div className="text-2xl font-semibold text-blue-600">
483
+ {redirects.reduce((s: number, r: any) => s + r.hits, 0).toLocaleString()}
484
+ </div>
273
485
  </div>
274
486
  <div className="bg-white rounded-lg border border-gray-200 p-4">
275
487
  <div className="text-xs text-gray-600 mb-1">301 Permanent</div>
276
- <div className="text-2xl font-semibold text-gray-900">{redirects.filter((r: any) => r.type === '301').length}</div>
488
+ <div className="text-2xl font-semibold text-gray-900">
489
+ {redirects.filter((r: any) => r.type === '301').length}
490
+ </div>
277
491
  </div>
278
492
  </div>
279
493
 
280
494
  <div className="flex items-center gap-3 mb-4">
281
495
  <div className="relative flex-1 max-w-md">
282
496
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
283
- <input type="text" placeholder="Search redirects..." value={redirectSearch} onChange={(e) => setRedirectSearch(e.target.value)} className="w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
497
+ <input
498
+ type="text"
499
+ placeholder="Search redirects..."
500
+ value={redirectSearch}
501
+ onChange={(e) => setRedirectSearch(e.target.value)}
502
+ className="w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
503
+ />
284
504
  </div>
285
- <button onClick={() => setShowAddRedirect(true)} className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm shrink-0">
286
- <Plus className="w-4 h-4" />Add Redirect
505
+ <button
506
+ onClick={() => setShowAddRedirect(true)}
507
+ className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm shrink-0"
508
+ >
509
+ <Plus className="w-4 h-4" />
510
+ Add Redirect
287
511
  </button>
288
512
  </div>
289
513
 
@@ -292,23 +516,63 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
292
516
  <table className="w-full">
293
517
  <thead className="bg-gray-50 border-b border-gray-200 sticky top-0">
294
518
  <tr>
295
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Source</th>
296
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Destination</th>
519
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
520
+ Source
521
+ </th>
522
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
523
+ Destination
524
+ </th>
297
525
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Type</th>
298
526
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Hits</th>
299
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Status</th>
300
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Actions</th>
527
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
528
+ Status
529
+ </th>
530
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
531
+ Actions
532
+ </th>
301
533
  </tr>
302
534
  </thead>
303
535
  <tbody className="divide-y divide-gray-200">
304
536
  {filteredRedirects.map((r: any) => (
305
537
  <tr key={r.id} className="hover:bg-gray-50 transition-colors">
306
- <td className="px-4 py-3"><code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-900">{r.from}</code></td>
307
- <td className="px-4 py-3"><code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-900">{r.to}</code></td>
308
- <td className="px-4 py-3"><span className={`px-2 py-0.5 rounded-full text-xs font-medium ${r.type === '301' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'}`}>{r.type}</span></td>
538
+ <td className="px-4 py-3">
539
+ <code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-900">
540
+ {r.from}
541
+ </code>
542
+ </td>
543
+ <td className="px-4 py-3">
544
+ <code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-900">
545
+ {r.to}
546
+ </code>
547
+ </td>
548
+ <td className="px-4 py-3">
549
+ <span
550
+ className={`px-2 py-0.5 rounded-full text-xs font-medium ${r.type === '301' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'}`}
551
+ >
552
+ {r.type}
553
+ </span>
554
+ </td>
309
555
  <td className="px-4 py-3 text-sm text-gray-600">{r.hits.toLocaleString()}</td>
310
- <td className="px-4 py-3"><span className={`px-2 py-0.5 rounded-full text-xs font-medium ${r.active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>{r.active ? 'Active' : 'Inactive'}</span></td>
311
- <td className="px-4 py-3"><div className="flex items-center gap-2"><button className="p-1.5 hover:bg-gray-100 rounded transition-colors"><Pencil className="w-4 h-4 text-gray-600" /></button><button onClick={() => handleDeleteRedirect(r.id)} className="p-1.5 hover:bg-gray-100 rounded transition-colors"><Trash2 className="w-4 h-4 text-red-600" /></button></div></td>
556
+ <td className="px-4 py-3">
557
+ <span
558
+ className={`px-2 py-0.5 rounded-full text-xs font-medium ${r.active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
559
+ >
560
+ {r.active ? 'Active' : 'Inactive'}
561
+ </span>
562
+ </td>
563
+ <td className="px-4 py-3">
564
+ <div className="flex items-center gap-2">
565
+ <button className="p-1.5 hover:bg-gray-100 rounded transition-colors">
566
+ <Pencil className="w-4 h-4 text-gray-600" />
567
+ </button>
568
+ <button
569
+ onClick={() => handleDeleteRedirect(r.id)}
570
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
571
+ >
572
+ <Trash2 className="w-4 h-4 text-red-600" />
573
+ </button>
574
+ </div>
575
+ </td>
312
576
  </tr>
313
577
  ))}
314
578
  </tbody>
@@ -320,14 +584,66 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
320
584
  <Dialog.Portal>
321
585
  <Dialog.Overlay className="fixed inset-0 z-50 bg-black/50" />
322
586
  <Dialog.Content className="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6 shadow-lg">
323
- <Dialog.Title className="mb-4 text-lg font-semibold text-gray-900">Add Redirect</Dialog.Title>
587
+ <Dialog.Title className="mb-4 text-lg font-semibold text-gray-900">
588
+ Add Redirect
589
+ </Dialog.Title>
324
590
  <form onSubmit={handleAddRedirect} className="space-y-4">
325
- <div><label className="mb-1 block text-sm font-medium text-gray-700">Source URL</label><input type="text" value={newRedirect.source} onChange={(e) => setNewRedirect({ ...newRedirect, source: e.target.value })} placeholder="/old-page" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" required /></div>
326
- <div><label className="mb-1 block text-sm font-medium text-gray-700">Destination URL</label><input type="text" value={newRedirect.destination} onChange={(e) => setNewRedirect({ ...newRedirect, destination: e.target.value })} placeholder="/new-page" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" required /></div>
327
- <div><label className="mb-1 block text-sm font-medium text-gray-700">Type</label><select value={newRedirect.type} onChange={(e) => setNewRedirect({ ...newRedirect, type: e.target.value as '301' | '302' })} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"><option value="301">301 (Permanent)</option><option value="302">302 (Temporary)</option></select></div>
591
+ <div>
592
+ <label className="mb-1 block text-sm font-medium text-gray-700">
593
+ Source URL
594
+ </label>
595
+ <input
596
+ type="text"
597
+ value={newRedirect.source}
598
+ onChange={(e) => setNewRedirect({ ...newRedirect, source: e.target.value })}
599
+ placeholder="/old-page"
600
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
601
+ required
602
+ />
603
+ </div>
604
+ <div>
605
+ <label className="mb-1 block text-sm font-medium text-gray-700">
606
+ Destination URL
607
+ </label>
608
+ <input
609
+ type="text"
610
+ value={newRedirect.destination}
611
+ onChange={(e) =>
612
+ setNewRedirect({ ...newRedirect, destination: e.target.value })
613
+ }
614
+ placeholder="/new-page"
615
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
616
+ required
617
+ />
618
+ </div>
619
+ <div>
620
+ <label className="mb-1 block text-sm font-medium text-gray-700">Type</label>
621
+ <select
622
+ value={newRedirect.type}
623
+ onChange={(e) =>
624
+ setNewRedirect({ ...newRedirect, type: e.target.value as '301' | '302' })
625
+ }
626
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
627
+ >
628
+ <option value="301">301 (Permanent)</option>
629
+ <option value="302">302 (Temporary)</option>
630
+ </select>
631
+ </div>
328
632
  <div className="flex justify-end gap-3 pt-4">
329
- <Dialog.Close asChild><button type="button" className="rounded-lg border border-gray-300 px-4 py-2 text-sm hover:bg-gray-50">Cancel</button></Dialog.Close>
330
- <button type="submit" className="rounded-lg bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700">Add Redirect</button>
633
+ <Dialog.Close asChild>
634
+ <button
635
+ type="button"
636
+ className="rounded-lg border border-gray-300 px-4 py-2 text-sm hover:bg-gray-50"
637
+ >
638
+ Cancel
639
+ </button>
640
+ </Dialog.Close>
641
+ <button
642
+ type="submit"
643
+ className="rounded-lg bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
644
+ >
645
+ Add Redirect
646
+ </button>
331
647
  </div>
332
648
  </form>
333
649
  </Dialog.Content>
@@ -339,19 +655,31 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
339
655
  <Tabs.Content value="canonicals" className="flex flex-col flex-1 min-h-0 space-y-4">
340
656
  <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
341
657
  <div className="bg-white rounded-lg border border-gray-200 p-4">
342
- <div className="flex items-center gap-2 mb-2"><ShieldCheck className="w-4 h-4 text-green-600" /><span className="text-xs font-medium text-gray-600">With Canonical</span></div>
658
+ <div className="flex items-center gap-2 mb-2">
659
+ <ShieldCheck className="w-4 h-4 text-green-600" />
660
+ <span className="text-xs font-medium text-gray-600">With Canonical</span>
661
+ </div>
343
662
  <div className="text-2xl font-semibold text-green-700">{allCanonicals.length}</div>
344
663
  </div>
345
664
  <div className="bg-white rounded-lg border border-gray-200 p-4">
346
- <div className="flex items-center gap-2 mb-2"><AlertTriangle className="w-4 h-4 text-red-600" /><span className="text-xs font-medium text-gray-600">Missing Canonical</span></div>
665
+ <div className="flex items-center gap-2 mb-2">
666
+ <AlertTriangle className="w-4 h-4 text-red-600" />
667
+ <span className="text-xs font-medium text-gray-600">Missing Canonical</span>
668
+ </div>
347
669
  <div className="text-2xl font-semibold text-red-700">{missingCanonical.length}</div>
348
670
  </div>
349
671
  <div className="bg-white rounded-lg border border-gray-200 p-4">
350
- <div className="flex items-center gap-2 mb-2"><Globe className="w-4 h-4 text-blue-600" /><span className="text-xs font-medium text-gray-600">Canonical Domains</span></div>
672
+ <div className="flex items-center gap-2 mb-2">
673
+ <Globe className="w-4 h-4 text-blue-600" />
674
+ <span className="text-xs font-medium text-gray-600">Canonical Domains</span>
675
+ </div>
351
676
  <div className="text-2xl font-semibold text-gray-900">{canonicalDomains.length}</div>
352
677
  </div>
353
678
  <div className="bg-white rounded-lg border border-gray-200 p-4">
354
- <div className="flex items-center gap-2 mb-2"><CheckCircle2 className="w-4 h-4 text-green-600" /><span className="text-xs font-medium text-gray-600">Self-Referencing</span></div>
679
+ <div className="flex items-center gap-2 mb-2">
680
+ <CheckCircle2 className="w-4 h-4 text-green-600" />
681
+ <span className="text-xs font-medium text-gray-600">Self-Referencing</span>
682
+ </div>
355
683
  <div className="text-2xl font-semibold text-green-700">{allCanonicals.length}</div>
356
684
  <div className="text-xs text-gray-500 mt-1">Correct pattern</div>
357
685
  </div>
@@ -362,11 +690,23 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
362
690
  <div className="flex items-start gap-3">
363
691
  <AlertTriangle className="w-5 h-5 text-yellow-600 mt-0.5 shrink-0" />
364
692
  <div className="flex-1">
365
- <h3 className="text-sm font-semibold text-yellow-900 mb-1">{missingCanonical.length} page{missingCanonical.length !== 1 ? 's' : ''} missing canonical URLs</h3>
366
- <p className="text-sm text-yellow-700 mb-2">Without canonical tags, search engines may index duplicate versions of these pages, diluting your ranking signals.</p>
693
+ <h3 className="text-sm font-semibold text-yellow-900 mb-1">
694
+ {missingCanonical.length} page{missingCanonical.length !== 1 ? 's' : ''} missing
695
+ canonical URLs
696
+ </h3>
697
+ <p className="text-sm text-yellow-700 mb-2">
698
+ Without canonical tags, search engines may index duplicate versions of these
699
+ pages, diluting your ranking signals.
700
+ </p>
367
701
  <div className="flex flex-wrap gap-2">
368
702
  {missingCanonical.map((p: any) => (
369
- <button key={p.id} onClick={() => onNavigate?.(`/pages/${p.id}`)} className="px-2.5 py-1 text-xs bg-yellow-100 text-yellow-900 rounded-lg hover:bg-yellow-200 transition-colors">{p.title} ({p.url})</button>
703
+ <button
704
+ key={p.id}
705
+ onClick={() => onNavigate?.(`/pages/${p.id}`)}
706
+ className="px-2.5 py-1 text-xs bg-yellow-100 text-yellow-900 rounded-lg hover:bg-yellow-200 transition-colors"
707
+ >
708
+ {p.title} ({p.url})
709
+ </button>
370
710
  ))}
371
711
  </div>
372
712
  </div>
@@ -381,19 +721,55 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
381
721
  <tr>
382
722
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Page</th>
383
723
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">URL</th>
384
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Canonical URL</th>
385
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Status</th>
386
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Actions</th>
724
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
725
+ Canonical URL
726
+ </th>
727
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
728
+ Status
729
+ </th>
730
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
731
+ Actions
732
+ </th>
387
733
  </tr>
388
734
  </thead>
389
735
  <tbody className="divide-y divide-gray-200">
390
736
  {seoPages.map((page: any) => (
391
737
  <tr key={page.id} className="hover:bg-gray-50 transition-colors">
392
738
  <td className="px-4 py-3 text-sm font-medium text-gray-900">{page.title}</td>
393
- <td className="px-4 py-3"><code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700">{page.url}</code></td>
394
- <td className="px-4 py-3">{page.canonical ? <code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700 truncate max-w-[200px] block">{page.canonical}</code> : <span className="text-xs text-red-600 font-medium">Not set</span>}</td>
395
- <td className="px-4 py-3">{page.canonical ? <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">✓ Set</span> : <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Missing</span>}</td>
396
- <td className="px-4 py-3"><button onClick={() => onNavigate?.(`/pages/${page.id}`)} className="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Edit"><Pencil className="w-4 h-4 text-gray-600" /></button></td>
739
+ <td className="px-4 py-3">
740
+ <code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700">
741
+ {page.url}
742
+ </code>
743
+ </td>
744
+ <td className="px-4 py-3">
745
+ {page.canonical ? (
746
+ <code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700 truncate max-w-[200px] block">
747
+ {page.canonical}
748
+ </code>
749
+ ) : (
750
+ <span className="text-xs text-red-600 font-medium">Not set</span>
751
+ )}
752
+ </td>
753
+ <td className="px-4 py-3">
754
+ {page.canonical ? (
755
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
756
+ ✓ Set
757
+ </span>
758
+ ) : (
759
+ <span className="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
760
+ Missing
761
+ </span>
762
+ )}
763
+ </td>
764
+ <td className="px-4 py-3">
765
+ <button
766
+ onClick={() => onNavigate?.(`/pages/${page.id}`)}
767
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
768
+ title="Edit"
769
+ >
770
+ <Pencil className="w-4 h-4 text-gray-600" />
771
+ </button>
772
+ </td>
397
773
  </tr>
398
774
  ))}
399
775
  </tbody>
@@ -407,19 +783,27 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
407
783
  <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
408
784
  <div className="bg-white rounded-lg border border-gray-200 p-4">
409
785
  <div className="text-xs text-gray-600 mb-1">Broken Links</div>
410
- <div className="text-2xl font-semibold text-red-600">{linkHealth.filter((l: any) => l.status === 404).length}</div>
786
+ <div className="text-2xl font-semibold text-red-600">
787
+ {linkHealth.filter((l: any) => l.status === 404).length}
788
+ </div>
411
789
  </div>
412
790
  <div className="bg-white rounded-lg border border-gray-200 p-4">
413
791
  <div className="text-xs text-gray-600 mb-1">Redirect Chains</div>
414
- <div className="text-2xl font-semibold text-yellow-600">{linkHealth.filter((l: any) => l.status === 301).length}</div>
792
+ <div className="text-2xl font-semibold text-yellow-600">
793
+ {linkHealth.filter((l: any) => l.status === 301).length}
794
+ </div>
415
795
  </div>
416
796
  <div className="bg-white rounded-lg border border-gray-200 p-4">
417
797
  <div className="text-xs text-gray-600 mb-1">Internal Issues</div>
418
- <div className="text-2xl font-semibold text-gray-900">{linkHealth.filter((l: any) => l.type === 'internal').length}</div>
798
+ <div className="text-2xl font-semibold text-gray-900">
799
+ {linkHealth.filter((l: any) => l.type === 'internal').length}
800
+ </div>
419
801
  </div>
420
802
  <div className="bg-white rounded-lg border border-gray-200 p-4">
421
803
  <div className="text-xs text-gray-600 mb-1">External Issues</div>
422
- <div className="text-2xl font-semibold text-gray-900">{linkHealth.filter((l: any) => l.type === 'external').length}</div>
804
+ <div className="text-2xl font-semibold text-gray-900">
805
+ {linkHealth.filter((l: any) => l.type === 'external').length}
806
+ </div>
423
807
  </div>
424
808
  </div>
425
809
 
@@ -428,23 +812,68 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
428
812
  <table className="w-full">
429
813
  <thead className="bg-gray-50 border-b border-gray-200 sticky top-0">
430
814
  <tr>
431
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Found On</th>
432
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Broken URL</th>
433
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Status</th>
815
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
816
+ Found On
817
+ </th>
818
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
819
+ Broken URL
820
+ </th>
821
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
822
+ Status
823
+ </th>
434
824
  <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Type</th>
435
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Last Checked</th>
436
- <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Actions</th>
825
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
826
+ Last Checked
827
+ </th>
828
+ <th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
829
+ Actions
830
+ </th>
437
831
  </tr>
438
832
  </thead>
439
833
  <tbody className="divide-y divide-gray-200">
440
834
  {linkHealth.map((link: any) => (
441
835
  <tr key={link.id} className="hover:bg-gray-50 transition-colors">
442
- <td className="px-4 py-3"><code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700">{link.page}</code></td>
443
- <td className="px-4 py-3"><code className="text-xs bg-red-50 px-2 py-1 rounded text-red-800 truncate max-w-[200px] block">{link.url}</code></td>
444
- <td className="px-4 py-3"><span className={`px-2 py-0.5 rounded-full text-xs font-medium ${link.status === 404 ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'}`}>{link.status}</span></td>
445
- <td className="px-4 py-3"><span className={`px-2 py-0.5 rounded-full text-xs font-medium ${link.type === 'internal' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'}`}>{link.type}</span></td>
836
+ <td className="px-4 py-3">
837
+ <code className="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700">
838
+ {link.page}
839
+ </code>
840
+ </td>
841
+ <td className="px-4 py-3">
842
+ <code className="text-xs bg-red-50 px-2 py-1 rounded text-red-800 truncate max-w-[200px] block">
843
+ {link.url}
844
+ </code>
845
+ </td>
846
+ <td className="px-4 py-3">
847
+ <span
848
+ className={`px-2 py-0.5 rounded-full text-xs font-medium ${link.status === 404 ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'}`}
849
+ >
850
+ {link.status}
851
+ </span>
852
+ </td>
853
+ <td className="px-4 py-3">
854
+ <span
855
+ className={`px-2 py-0.5 rounded-full text-xs font-medium ${link.type === 'internal' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'}`}
856
+ >
857
+ {link.type}
858
+ </span>
859
+ </td>
446
860
  <td className="px-4 py-3 text-sm text-gray-600">{link.lastChecked}</td>
447
- <td className="px-4 py-3"><div className="flex items-center gap-1"><button className="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Create redirect"><ArrowRightLeft className="w-4 h-4 text-gray-600" /></button><button className="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Open URL"><ExternalLink className="w-4 h-4 text-gray-600" /></button></div></td>
861
+ <td className="px-4 py-3">
862
+ <div className="flex items-center gap-1">
863
+ <button
864
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
865
+ title="Create redirect"
866
+ >
867
+ <ArrowRightLeft className="w-4 h-4 text-gray-600" />
868
+ </button>
869
+ <button
870
+ className="p-1.5 hover:bg-gray-100 rounded transition-colors"
871
+ title="Open URL"
872
+ >
873
+ <ExternalLink className="w-4 h-4 text-gray-600" />
874
+ </button>
875
+ </div>
876
+ </td>
448
877
  </tr>
449
878
  ))}
450
879
  </tbody>
@@ -454,5 +883,5 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
454
883
  </Tabs.Content>
455
884
  </Tabs.Root>
456
885
  </div>
457
- );
886
+ )
458
887
  }