@actuate-media/cms-admin 0.8.0 → 0.8.2

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