@actuate-media/cms-admin 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +8 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/layout/primitives.test.d.ts +2 -0
- package/dist/__tests__/layout/primitives.test.d.ts.map +1 -0
- package/dist/__tests__/layout/primitives.test.js +34 -0
- package/dist/__tests__/layout/primitives.test.js.map +1 -0
- package/dist/__tests__/lib/cv.test.d.ts +2 -0
- package/dist/__tests__/lib/cv.test.d.ts.map +1 -0
- package/dist/__tests__/lib/cv.test.js +66 -0
- package/dist/__tests__/lib/cv.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/assets/actuate-logo.d.ts +36 -0
- package/dist/assets/actuate-logo.d.ts.map +1 -0
- package/dist/assets/actuate-logo.js +15 -0
- package/dist/assets/actuate-logo.js.map +1 -0
- package/dist/components/Breadcrumbs.js +2 -2
- package/dist/components/CommandPalette.js +10 -10
- package/dist/components/ContentOverviewChart.js +3 -3
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/FocalPointPicker.js +2 -2
- package/dist/components/FolderTree.js +20 -20
- package/dist/components/LivePreview.js +3 -3
- package/dist/components/LocaleSwitcher.js +1 -1
- package/dist/components/MediaPickerModal.js +4 -4
- package/dist/components/PresenceIndicator.js +1 -1
- package/dist/components/SEOConfigPanel.d.ts +2 -0
- package/dist/components/SEOConfigPanel.d.ts.map +1 -0
- package/dist/components/SEOConfigPanel.js +174 -0
- package/dist/components/SEOConfigPanel.js.map +1 -0
- package/dist/components/SEOPanel.js +9 -9
- package/dist/components/SEOPerformance.js +2 -2
- package/dist/components/SchedulePublishDialog.d.ts +18 -0
- package/dist/components/SchedulePublishDialog.d.ts.map +1 -0
- package/dist/components/SchedulePublishDialog.js +106 -0
- package/dist/components/SchedulePublishDialog.js.map +1 -0
- package/dist/components/SharePreviewLinkDialog.d.ts +17 -0
- package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -0
- package/dist/components/SharePreviewLinkDialog.js +83 -0
- package/dist/components/SharePreviewLinkDialog.js.map +1 -0
- package/dist/components/TipTapEditor.js +5 -5
- package/dist/components/VersionHistory.js +2 -2
- package/dist/components/ui/Badge.d.ts +33 -3
- package/dist/components/ui/Badge.d.ts.map +1 -1
- package/dist/components/ui/Badge.js +42 -8
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Button.d.ts +19 -8
- package/dist/components/ui/Button.d.ts.map +1 -1
- package/dist/components/ui/Button.js +35 -14
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Card.d.ts +26 -0
- package/dist/components/ui/Card.d.ts.map +1 -0
- package/dist/components/ui/Card.js +45 -0
- package/dist/components/ui/Card.js.map +1 -0
- package/dist/components/ui/DataTable.js +1 -1
- package/dist/components/ui/Input.d.ts +15 -0
- package/dist/components/ui/Input.d.ts.map +1 -0
- package/dist/components/ui/Input.js +23 -0
- package/dist/components/ui/Input.js.map +1 -0
- package/dist/components/ui/SearchInput.js +1 -1
- package/dist/components/ui/Select.d.ts +16 -0
- package/dist/components/ui/Select.d.ts.map +1 -0
- package/dist/components/ui/Select.js +25 -0
- package/dist/components/ui/Select.js.map +1 -0
- package/dist/components/ui/Toast.js +1 -1
- package/dist/components/ui/index.d.ts +10 -4
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -2
- package/dist/components/ui/index.js.map +1 -1
- package/dist/fields/BlockBuilderField.js +3 -3
- package/dist/fields/DateField.js +1 -1
- package/dist/fields/RelationshipField.js +3 -3
- package/dist/fields/TextField.js +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Header.js +1 -1
- package/dist/layout/Layout.d.ts +14 -0
- package/dist/layout/Layout.d.ts.map +1 -1
- package/dist/layout/Layout.js +17 -11
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +21 -11
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/layout/primitives/AdminShell.d.ts +43 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -0
- package/dist/layout/primitives/AdminShell.js +51 -0
- package/dist/layout/primitives/AdminShell.js.map +1 -0
- package/dist/layout/primitives/Box.d.ts +19 -0
- package/dist/layout/primitives/Box.d.ts.map +1 -0
- package/dist/layout/primitives/Box.js +12 -0
- package/dist/layout/primitives/Box.js.map +1 -0
- package/dist/layout/primitives/Cluster.d.ts +27 -0
- package/dist/layout/primitives/Cluster.d.ts.map +1 -0
- package/dist/layout/primitives/Cluster.js +37 -0
- package/dist/layout/primitives/Cluster.js.map +1 -0
- package/dist/layout/primitives/Grid.d.ts +45 -0
- package/dist/layout/primitives/Grid.d.ts.map +1 -0
- package/dist/layout/primitives/Grid.js +59 -0
- package/dist/layout/primitives/Grid.js.map +1 -0
- package/dist/layout/primitives/PageContainer.d.ts +36 -0
- package/dist/layout/primitives/PageContainer.d.ts.map +1 -0
- package/dist/layout/primitives/PageContainer.js +41 -0
- package/dist/layout/primitives/PageContainer.js.map +1 -0
- package/dist/layout/primitives/Split.d.ts +34 -0
- package/dist/layout/primitives/Split.d.ts.map +1 -0
- package/dist/layout/primitives/Split.js +27 -0
- package/dist/layout/primitives/Split.js.map +1 -0
- package/dist/layout/primitives/Stack.d.ts +23 -0
- package/dist/layout/primitives/Stack.d.ts.map +1 -0
- package/dist/layout/primitives/Stack.js +34 -0
- package/dist/layout/primitives/Stack.js.map +1 -0
- package/dist/layout/primitives/index.d.ts +30 -0
- package/dist/layout/primitives/index.d.ts.map +1 -0
- package/dist/layout/primitives/index.js +22 -0
- package/dist/layout/primitives/index.js.map +1 -0
- package/dist/layout/primitives/tokens.d.ts +48 -0
- package/dist/layout/primitives/tokens.d.ts.map +1 -0
- package/dist/layout/primitives/tokens.js +54 -0
- package/dist/layout/primitives/tokens.js.map +1 -0
- package/dist/lib/cv.d.ts +53 -0
- package/dist/lib/cv.d.ts.map +1 -0
- package/dist/lib/cv.js +39 -0
- package/dist/lib/cv.js.map +1 -0
- package/dist/views/ApiKeys.d.ts.map +1 -1
- package/dist/views/ApiKeys.js +13 -11
- package/dist/views/ApiKeys.js.map +1 -1
- package/dist/views/CollectionList.js +8 -8
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +333 -78
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +17 -5
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.js +2 -2
- package/dist/views/FormEditor.js +5 -5
- package/dist/views/FormSubmissions.js +6 -6
- package/dist/views/Forms.js +2 -2
- package/dist/views/Login.d.ts +16 -1
- package/dist/views/Login.d.ts.map +1 -1
- package/dist/views/Login.js +17 -7
- package/dist/views/Login.js.map +1 -1
- package/dist/views/MediaBrowser.js +16 -16
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/Pages.js +10 -10
- package/dist/views/PostEditor.js +2 -2
- package/dist/views/Posts.js +4 -4
- package/dist/views/Redirects.js +4 -4
- package/dist/views/ResetPassword.js +2 -2
- package/dist/views/SEO.js +6 -6
- package/dist/views/ScriptTagEditor.js +4 -4
- package/dist/views/ScriptTags.js +2 -2
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +9 -8
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/SetupWizard.js +2 -2
- package/dist/views/Users.js +4 -4
- package/dist/views/page-builder/AIBlockAssist.js +1 -1
- package/dist/views/page-builder/AIGenerateDialog.js +10 -10
- package/dist/views/page-builder/BlockEditor.js +10 -10
- package/dist/views/page-builder/BlockPicker.js +4 -4
- package/dist/views/page-builder/BottomBar.js +1 -1
- package/dist/views/page-builder/BuilderToolbar.js +2 -2
- package/dist/views/page-builder/ContextPanel.js +2 -2
- package/dist/views/page-builder/DesignScore.js +9 -9
- package/dist/views/page-builder/NodeSettings.js +8 -8
- package/dist/views/page-builder/PageBuilder.js +3 -3
- package/dist/views/page-builder/PageSettings.js +1 -1
- package/dist/views/page-builder/PageTemplates.js +2 -2
- package/dist/views/page-builder/SEOPanel.js +13 -13
- package/dist/views/page-builder/SavedSections.js +5 -5
- package/dist/views/page-builder/TemplatePicker.js +2 -2
- package/dist/views/page-builder/block-renderers/CTAPreview.js +5 -5
- package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +1 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +5 -5
- package/dist/views/page-builder/block-renderers/HeroPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/ImagePreview.js +3 -3
- package/dist/views/page-builder/block-renderers/TextPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/VideoPreview.js +4 -4
- package/dist/views/page-builder/canvas/BlockRenderer.js +1 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js +3 -3
- package/dist/views/page-builder/canvas/ColumnRenderer.js +2 -2
- package/dist/views/page-builder/canvas/ContainerRenderer.js +2 -2
- package/dist/views/page-builder/canvas/RowRenderer.js +2 -2
- package/dist/views/page-builder/canvas/SectionRenderer.js +2 -2
- package/package.json +6 -2
- package/src/AdminRoot.tsx +21 -11
- package/src/__tests__/layout/primitives.test.ts +37 -0
- package/src/__tests__/lib/cv.test.ts +74 -0
- package/src/assets/actuate-logo.tsx +72 -0
- package/src/components/Breadcrumbs.tsx +6 -6
- package/src/components/CommandPalette.tsx +34 -34
- package/src/components/ContentOverviewChart.tsx +3 -3
- package/src/components/ErrorBoundary.tsx +3 -3
- package/src/components/FocalPointPicker.tsx +4 -4
- package/src/components/FolderTree.tsx +38 -38
- package/src/components/LivePreview.tsx +16 -16
- package/src/components/LocaleSwitcher.tsx +7 -7
- package/src/components/MediaPickerModal.tsx +21 -21
- package/src/components/PresenceIndicator.tsx +2 -2
- package/src/components/SEOConfigPanel.tsx +582 -0
- package/src/components/SEOPanel.tsx +46 -46
- package/src/components/SEOPerformance.tsx +21 -21
- package/src/components/SchedulePublishDialog.tsx +241 -0
- package/src/components/SharePreviewLinkDialog.tsx +227 -0
- package/src/components/TipTapEditor.tsx +33 -33
- package/src/components/VersionHistory.tsx +16 -16
- package/src/components/ui/Badge.tsx +66 -14
- package/src/components/ui/Button.tsx +70 -33
- package/src/components/ui/Card.tsx +101 -0
- package/src/components/ui/DataTable.tsx +1 -1
- package/src/components/ui/Input.tsx +35 -0
- package/src/components/ui/SearchInput.tsx +4 -4
- package/src/components/ui/Select.tsx +56 -0
- package/src/components/ui/Toast.tsx +1 -1
- package/src/components/ui/index.ts +18 -4
- package/src/fields/BlockBuilderField.tsx +3 -3
- package/src/fields/DateField.tsx +1 -1
- package/src/fields/RelationshipField.tsx +10 -10
- package/src/fields/TextField.tsx +1 -1
- package/src/index.ts +32 -0
- package/src/layout/Header.tsx +28 -28
- package/src/layout/Layout.tsx +39 -46
- package/src/layout/Sidebar.tsx +37 -64
- package/src/layout/primitives/AdminShell.tsx +118 -0
- package/src/layout/primitives/Box.tsx +30 -0
- package/src/layout/primitives/Cluster.tsx +74 -0
- package/src/layout/primitives/Grid.tsx +120 -0
- package/src/layout/primitives/PageContainer.tsx +96 -0
- package/src/layout/primitives/Split.tsx +73 -0
- package/src/layout/primitives/Stack.tsx +67 -0
- package/src/layout/primitives/index.ts +36 -0
- package/src/layout/primitives/tokens.ts +76 -0
- package/src/lib/cv.ts +96 -0
- package/src/styles/build-input.css +1 -1
- package/src/views/ApiKeys.tsx +57 -57
- package/src/views/CollectionList.tsx +30 -30
- package/src/views/Dashboard.tsx +737 -186
- package/src/views/DocumentEdit.tsx +90 -10
- package/src/views/ForgotPassword.tsx +18 -18
- package/src/views/FormEditor.tsx +75 -75
- package/src/views/FormSubmissions.tsx +76 -76
- package/src/views/Forms.tsx +27 -27
- package/src/views/Login.tsx +65 -25
- package/src/views/MediaBrowser.tsx +127 -127
- package/src/views/PageEditor.tsx +25 -25
- package/src/views/Pages.tsx +59 -59
- package/src/views/PostEditor.tsx +37 -37
- package/src/views/Posts.tsx +48 -48
- package/src/views/Redirects.tsx +21 -21
- package/src/views/ResetPassword.tsx +28 -28
- package/src/views/SEO.tsx +144 -144
- package/src/views/ScriptTagEditor.tsx +24 -24
- package/src/views/ScriptTags.tsx +10 -10
- package/src/views/Settings.tsx +88 -80
- package/src/views/SetupWizard.tsx +28 -28
- package/src/views/Users.tsx +20 -20
- package/src/views/page-builder/AIBlockAssist.tsx +1 -1
- package/src/views/page-builder/AIGenerateDialog.tsx +63 -63
- package/src/views/page-builder/BlockEditor.tsx +26 -26
- package/src/views/page-builder/BlockPicker.tsx +22 -22
- package/src/views/page-builder/BottomBar.tsx +8 -8
- package/src/views/page-builder/BuilderToolbar.tsx +17 -17
- package/src/views/page-builder/ContextPanel.tsx +3 -3
- package/src/views/page-builder/DesignScore.tsx +21 -21
- package/src/views/page-builder/NodeSettings.tsx +27 -27
- package/src/views/page-builder/PageBuilder.tsx +11 -11
- package/src/views/page-builder/PageSettings.tsx +4 -4
- package/src/views/page-builder/PageTemplates.tsx +18 -18
- package/src/views/page-builder/SEOPanel.tsx +53 -53
- package/src/views/page-builder/SavedSections.tsx +37 -37
- package/src/views/page-builder/TemplatePicker.tsx +17 -17
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +13 -13
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +5 -5
- package/src/views/page-builder/block-renderers/CodePreview.tsx +6 -6
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +13 -13
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +3 -3
- package/src/views/page-builder/block-renderers/FormPreview.tsx +20 -20
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +8 -8
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +16 -16
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +4 -4
- package/src/views/page-builder/block-renderers/TextPreview.tsx +14 -14
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +12 -12
- package/src/views/page-builder/canvas/BlockRenderer.tsx +4 -4
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +6 -6
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +3 -3
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +2 -2
- package/src/views/page-builder/canvas/RowRenderer.tsx +2 -2
- package/src/views/page-builder/canvas/SectionRenderer.tsx +2 -2
package/src/views/SEO.tsx
CHANGED
|
@@ -193,9 +193,9 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
193
193
|
return 'bg-red-100 text-red-800'
|
|
194
194
|
}
|
|
195
195
|
function scoreIcon(score: number) {
|
|
196
|
-
if (score >= 80) return <CheckCircle2 className="
|
|
197
|
-
if (score >= 50) return <AlertTriangle className="
|
|
198
|
-
return <XCircle className="
|
|
196
|
+
if (score >= 80) return <CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
197
|
+
if (score >= 50) return <AlertTriangle className="h-4 w-4 text-yellow-600" />
|
|
198
|
+
return <XCircle className="h-4 w-4 text-red-600" />
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
const tabClass =
|
|
@@ -205,33 +205,33 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
205
205
|
|
|
206
206
|
if (isLoading) {
|
|
207
207
|
return (
|
|
208
|
-
<div className="p-3 pr-6 sm:p-4 sm:pr-8
|
|
209
|
-
<Loader2 className="
|
|
208
|
+
<div className="flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8">
|
|
209
|
+
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
|
|
210
210
|
</div>
|
|
211
211
|
)
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
return (
|
|
215
|
-
<div className="p-3 pr-6 sm:p-4 sm:pr-8
|
|
215
|
+
<div className="flex h-full flex-col p-3 pr-6 sm:p-4 sm:pr-8">
|
|
216
216
|
{(seoError || redirectsError) && (
|
|
217
217
|
<div className="mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
|
|
218
|
-
<AlertTriangle className="
|
|
219
|
-
<span className="text-sm text-red-800
|
|
218
|
+
<AlertTriangle className="h-5 w-5 shrink-0 text-red-600" />
|
|
219
|
+
<span className="flex-1 text-sm text-red-800">{seoError || redirectsError}</span>
|
|
220
220
|
<button
|
|
221
221
|
onClick={() => {
|
|
222
222
|
seoRefetch()
|
|
223
223
|
redirectsRefetch()
|
|
224
224
|
}}
|
|
225
|
-
className="px-3 py-1 text-sm text-red-700
|
|
225
|
+
className="rounded-lg border border-red-300 px-3 py-1 text-sm text-red-700 transition-colors hover:bg-red-100"
|
|
226
226
|
>
|
|
227
227
|
Retry
|
|
228
228
|
</button>
|
|
229
229
|
</div>
|
|
230
230
|
)}
|
|
231
231
|
|
|
232
|
-
<div className="flex flex-col sm:flex-row sm:items-center
|
|
232
|
+
<div className="mb-4 flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
|
|
233
233
|
<div>
|
|
234
|
-
<h1 className="
|
|
234
|
+
<h1 className="mb-1 text-xl font-semibold text-gray-900 sm:text-2xl">SEO & Redirects</h1>
|
|
235
235
|
<p className="text-sm text-gray-600">
|
|
236
236
|
Search optimization, redirects, canonicalization, and link health
|
|
237
237
|
</p>
|
|
@@ -239,103 +239,103 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
239
239
|
<button
|
|
240
240
|
onClick={handleScan}
|
|
241
241
|
disabled={scanning}
|
|
242
|
-
className="flex items-center justify-center gap-2 px-4 py-2
|
|
242
|
+
className="flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-700 disabled:opacity-60"
|
|
243
243
|
>
|
|
244
|
-
<RefreshCw className={`
|
|
244
|
+
<RefreshCw className={`h-4 w-4 ${scanning ? 'animate-spin' : ''}`} />
|
|
245
245
|
{scanning ? 'Scanning...' : 'Run SEO Scan'}
|
|
246
246
|
</button>
|
|
247
247
|
</div>
|
|
248
248
|
|
|
249
249
|
<Tabs.Root value={activeTab} onValueChange={handleTabChange}>
|
|
250
|
-
<Tabs.List className="mb-4 flex gap-1 border-b border-gray-200
|
|
250
|
+
<Tabs.List className="mb-4 flex gap-1 overflow-x-auto border-b border-gray-200">
|
|
251
251
|
<Tabs.Trigger value="pages" className={tabClass}>
|
|
252
252
|
<span className="flex items-center gap-1.5">
|
|
253
|
-
<BarChart3 className="
|
|
253
|
+
<BarChart3 className="h-4 w-4" />
|
|
254
254
|
Pages
|
|
255
255
|
</span>
|
|
256
256
|
</Tabs.Trigger>
|
|
257
257
|
<Tabs.Trigger value="redirects" className={tabClass}>
|
|
258
258
|
<span className="flex items-center gap-1.5">
|
|
259
|
-
<ArrowRightLeft className="
|
|
259
|
+
<ArrowRightLeft className="h-4 w-4" />
|
|
260
260
|
Redirects
|
|
261
261
|
</span>
|
|
262
262
|
</Tabs.Trigger>
|
|
263
263
|
<Tabs.Trigger value="canonicals" className={tabClass}>
|
|
264
264
|
<span className="flex items-center gap-1.5">
|
|
265
|
-
<Copy className="
|
|
265
|
+
<Copy className="h-4 w-4" />
|
|
266
266
|
Canonicalization
|
|
267
267
|
</span>
|
|
268
268
|
</Tabs.Trigger>
|
|
269
269
|
<Tabs.Trigger value="links" className={tabClass}>
|
|
270
270
|
<span className="flex items-center gap-1.5">
|
|
271
|
-
<Link2 className="
|
|
271
|
+
<Link2 className="h-4 w-4" />
|
|
272
272
|
Link Health
|
|
273
273
|
</span>
|
|
274
274
|
</Tabs.Trigger>
|
|
275
275
|
</Tabs.List>
|
|
276
276
|
|
|
277
277
|
{/* PAGES TAB */}
|
|
278
|
-
<Tabs.Content value="pages" className="flex
|
|
279
|
-
<div className="grid grid-cols-2 lg:grid-cols-4
|
|
280
|
-
<div className="
|
|
281
|
-
<div className="flex items-center gap-2
|
|
282
|
-
<BarChart3 className="
|
|
278
|
+
<Tabs.Content value="pages" className="flex min-h-0 flex-1 flex-col">
|
|
279
|
+
<div className="mb-4 grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
280
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
281
|
+
<div className="mb-2 flex items-center gap-2">
|
|
282
|
+
<BarChart3 className="h-4 w-4 text-blue-600" />
|
|
283
283
|
<span className="text-xs font-medium text-gray-600">Avg SEO Score</span>
|
|
284
284
|
</div>
|
|
285
285
|
<div className="text-2xl font-semibold text-gray-900">{avgScore}</div>
|
|
286
286
|
<div
|
|
287
|
-
className={`text-xs
|
|
287
|
+
className={`mt-1 text-xs ${avgScore >= 80 ? 'text-green-600' : avgScore >= 50 ? 'text-yellow-600' : 'text-red-600'}`}
|
|
288
288
|
>
|
|
289
289
|
{avgScore >= 80 ? 'Good' : avgScore >= 50 ? 'Needs improvement' : 'Critical'}
|
|
290
290
|
</div>
|
|
291
291
|
</div>
|
|
292
|
-
<div className="
|
|
293
|
-
<div className="flex items-center gap-2
|
|
294
|
-
<AlertTriangle className="
|
|
292
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
293
|
+
<div className="mb-2 flex items-center gap-2">
|
|
294
|
+
<AlertTriangle className="h-4 w-4 text-yellow-600" />
|
|
295
295
|
<span className="text-xs font-medium text-gray-600">Total Issues</span>
|
|
296
296
|
</div>
|
|
297
297
|
<div className="text-2xl font-semibold text-gray-900">{totalIssues}</div>
|
|
298
|
-
<div className="text-xs text-gray-500
|
|
298
|
+
<div className="mt-1 text-xs text-gray-500">
|
|
299
299
|
Across {seoPages.filter((p: any) => p.issues > 0).length} pages
|
|
300
300
|
</div>
|
|
301
301
|
</div>
|
|
302
|
-
<div className="
|
|
303
|
-
<div className="flex items-center gap-2
|
|
304
|
-
<FileCode2 className="
|
|
302
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
303
|
+
<div className="mb-2 flex items-center gap-2">
|
|
304
|
+
<FileCode2 className="h-4 w-4 text-purple-600" />
|
|
305
305
|
<span className="text-xs font-medium text-gray-600">Missing Meta</span>
|
|
306
306
|
</div>
|
|
307
307
|
<div className="text-2xl font-semibold text-gray-900">{missingMeta}</div>
|
|
308
|
-
<div className="text-xs text-gray-500
|
|
308
|
+
<div className="mt-1 text-xs text-gray-500">Title or description</div>
|
|
309
309
|
</div>
|
|
310
|
-
<div className="
|
|
311
|
-
<div className="flex items-center gap-2
|
|
312
|
-
<Globe className="
|
|
310
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
311
|
+
<div className="mb-2 flex items-center gap-2">
|
|
312
|
+
<Globe className="h-4 w-4 text-green-600" />
|
|
313
313
|
<span className="text-xs font-medium text-gray-600">Missing Schema</span>
|
|
314
314
|
</div>
|
|
315
315
|
<div className="text-2xl font-semibold text-gray-900">{missingSchema}</div>
|
|
316
|
-
<div className="text-xs text-gray-500
|
|
316
|
+
<div className="mt-1 text-xs text-gray-500">No Schema.org markup</div>
|
|
317
317
|
</div>
|
|
318
318
|
</div>
|
|
319
319
|
|
|
320
|
-
<div className="bg-linear-to-r from-indigo-50 to-purple-50
|
|
320
|
+
<div className="mb-4 rounded-lg border border-indigo-200 bg-linear-to-r from-indigo-50 to-purple-50 p-4">
|
|
321
321
|
<div className="flex items-start gap-3">
|
|
322
|
-
<Bot className="
|
|
322
|
+
<Bot className="mt-0.5 h-5 w-5 shrink-0 text-indigo-600" />
|
|
323
323
|
<div className="flex-1">
|
|
324
|
-
<h3 className="text-sm font-semibold text-indigo-900
|
|
324
|
+
<h3 className="mb-1 text-sm font-semibold text-indigo-900">
|
|
325
325
|
AI SEO Recommendations
|
|
326
326
|
</h3>
|
|
327
|
-
<p className="text-sm text-indigo-700
|
|
327
|
+
<p className="mb-2 text-sm text-indigo-700">
|
|
328
328
|
{missingMeta} pages missing meta descriptions. {missingCanonical.length} pages
|
|
329
329
|
without canonical URLs. AI can auto-generate these from page content.
|
|
330
330
|
</p>
|
|
331
331
|
<div className="flex items-center gap-2">
|
|
332
332
|
<button
|
|
333
333
|
onClick={() => onNavigate?.('/settings')}
|
|
334
|
-
className="px-3 py-1.5 text-xs
|
|
334
|
+
className="rounded-lg bg-indigo-600 px-3 py-1.5 text-xs text-white transition-colors hover:bg-indigo-700"
|
|
335
335
|
>
|
|
336
336
|
Configure AI
|
|
337
337
|
</button>
|
|
338
|
-
<button className="px-3 py-1.5 text-xs
|
|
338
|
+
<button className="rounded-lg border border-indigo-300 px-3 py-1.5 text-xs text-indigo-700 transition-colors hover:bg-indigo-50">
|
|
339
339
|
Auto-fix All
|
|
340
340
|
</button>
|
|
341
341
|
</div>
|
|
@@ -343,22 +343,22 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
343
343
|
</div>
|
|
344
344
|
</div>
|
|
345
345
|
|
|
346
|
-
<div className="
|
|
347
|
-
<div className="
|
|
346
|
+
<div className="mb-4 rounded-lg border border-gray-200 bg-white">
|
|
347
|
+
<div className="flex flex-col gap-3 p-3 sm:flex-row">
|
|
348
348
|
<div className="relative flex-1">
|
|
349
|
-
<Search className="absolute
|
|
349
|
+
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
|
350
350
|
<input
|
|
351
351
|
type="text"
|
|
352
352
|
placeholder="Search pages by URL or title..."
|
|
353
353
|
value={searchQuery}
|
|
354
354
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
355
|
-
className="w-full
|
|
355
|
+
className="w-full rounded-lg border border-gray-300 py-2 pr-3 pl-9 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
356
356
|
/>
|
|
357
357
|
</div>
|
|
358
358
|
<select
|
|
359
359
|
value={filterScore}
|
|
360
360
|
onChange={(e) => setFilterScore(e.target.value)}
|
|
361
|
-
className="
|
|
361
|
+
className="rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
362
362
|
>
|
|
363
363
|
<option value="all">All Scores</option>
|
|
364
364
|
<option value="good">Good (80+)</option>
|
|
@@ -368,10 +368,10 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
368
368
|
</div>
|
|
369
369
|
</div>
|
|
370
370
|
|
|
371
|
-
<div className="
|
|
372
|
-
<div className="overflow-x-auto
|
|
371
|
+
<div className="flex-1 overflow-hidden rounded-lg border border-gray-200 bg-white">
|
|
372
|
+
<div className="h-full overflow-x-auto">
|
|
373
373
|
<table className="w-full">
|
|
374
|
-
<thead className="
|
|
374
|
+
<thead className="sticky top-0 border-b border-gray-200 bg-gray-50">
|
|
375
375
|
<tr>
|
|
376
376
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Page</th>
|
|
377
377
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Score</th>
|
|
@@ -392,7 +392,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
392
392
|
</thead>
|
|
393
393
|
<tbody className="divide-y divide-gray-200">
|
|
394
394
|
{filtered.map((page: any) => (
|
|
395
|
-
<tr key={page.id} className="hover:bg-gray-50
|
|
395
|
+
<tr key={page.id} className="transition-colors hover:bg-gray-50">
|
|
396
396
|
<td className="px-4 py-3">
|
|
397
397
|
<div className="text-sm font-medium text-gray-900">{page.title}</div>
|
|
398
398
|
<div className="text-xs text-gray-500">{page.url}</div>
|
|
@@ -401,7 +401,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
401
401
|
<div className="flex items-center gap-2">
|
|
402
402
|
{scoreIcon(page.score)}
|
|
403
403
|
<span
|
|
404
|
-
className={`px-2 py-0.5
|
|
404
|
+
className={`rounded-full px-2 py-0.5 text-xs font-medium ${scoreBadge(page.score)}`}
|
|
405
405
|
>
|
|
406
406
|
{page.score}
|
|
407
407
|
</span>
|
|
@@ -410,7 +410,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
410
410
|
<td className="px-4 py-3">
|
|
411
411
|
{page.readability > 0 ? (
|
|
412
412
|
<div className="flex items-center gap-2">
|
|
413
|
-
<BookOpen className="
|
|
413
|
+
<BookOpen className="h-3.5 w-3.5 text-gray-400" />
|
|
414
414
|
<span
|
|
415
415
|
className={`text-sm ${page.readability >= 80 ? 'text-green-700' : page.readability >= 60 ? 'text-yellow-700' : 'text-red-700'}`}
|
|
416
416
|
>
|
|
@@ -423,11 +423,11 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
423
423
|
</td>
|
|
424
424
|
<td className="px-4 py-3">
|
|
425
425
|
{page.schemaType ? (
|
|
426
|
-
<span className="px-2 py-0.5
|
|
426
|
+
<span className="rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-800">
|
|
427
427
|
{page.schemaType}
|
|
428
428
|
</span>
|
|
429
429
|
) : (
|
|
430
|
-
<span className="px-2 py-0.5
|
|
430
|
+
<span className="rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800">
|
|
431
431
|
Missing
|
|
432
432
|
</span>
|
|
433
433
|
)}
|
|
@@ -448,11 +448,11 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
448
448
|
</td>
|
|
449
449
|
<td className="px-4 py-3">
|
|
450
450
|
{page.issues > 0 ? (
|
|
451
|
-
<span className="px-2 py-0.5
|
|
451
|
+
<span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800">
|
|
452
452
|
{page.issues}
|
|
453
453
|
</span>
|
|
454
454
|
) : (
|
|
455
|
-
<span className="px-2 py-0.5
|
|
455
|
+
<span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
|
456
456
|
✓
|
|
457
457
|
</span>
|
|
458
458
|
)}
|
|
@@ -461,16 +461,16 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
461
461
|
<div className="flex items-center gap-1">
|
|
462
462
|
<button
|
|
463
463
|
onClick={() => onNavigate?.(`/pages/${page.id}`)}
|
|
464
|
-
className="p-1.5 hover:bg-gray-100
|
|
464
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
465
465
|
title="Edit"
|
|
466
466
|
>
|
|
467
|
-
<ArrowUpRight className="
|
|
467
|
+
<ArrowUpRight className="h-4 w-4 text-gray-600" />
|
|
468
468
|
</button>
|
|
469
469
|
<button
|
|
470
|
-
className="p-1.5 hover:bg-gray-100
|
|
470
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
471
471
|
title="AI analyze"
|
|
472
472
|
>
|
|
473
|
-
<Bot className="
|
|
473
|
+
<Bot className="h-4 w-4 text-indigo-600" />
|
|
474
474
|
</button>
|
|
475
475
|
</div>
|
|
476
476
|
</td>
|
|
@@ -483,56 +483,56 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
483
483
|
</Tabs.Content>
|
|
484
484
|
|
|
485
485
|
{/* REDIRECTS TAB */}
|
|
486
|
-
<Tabs.Content value="redirects" className="flex
|
|
487
|
-
<div className="grid grid-cols-2 lg:grid-cols-4
|
|
488
|
-
<div className="
|
|
489
|
-
<div className="text-xs text-gray-600
|
|
486
|
+
<Tabs.Content value="redirects" className="flex min-h-0 flex-1 flex-col">
|
|
487
|
+
<div className="mb-4 grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
488
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
489
|
+
<div className="mb-1 text-xs text-gray-600">Total Redirects</div>
|
|
490
490
|
<div className="text-2xl font-semibold text-gray-900">{redirects.length}</div>
|
|
491
491
|
</div>
|
|
492
|
-
<div className="
|
|
493
|
-
<div className="text-xs text-gray-600
|
|
492
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
493
|
+
<div className="mb-1 text-xs text-gray-600">Active</div>
|
|
494
494
|
<div className="text-2xl font-semibold text-green-600">
|
|
495
495
|
{redirects.filter((r: any) => r.active).length}
|
|
496
496
|
</div>
|
|
497
497
|
</div>
|
|
498
|
-
<div className="
|
|
499
|
-
<div className="text-xs text-gray-600
|
|
498
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
499
|
+
<div className="mb-1 text-xs text-gray-600">Total Hits</div>
|
|
500
500
|
<div className="text-2xl font-semibold text-blue-600">
|
|
501
501
|
{redirects.reduce((s: number, r: any) => s + r.hits, 0).toLocaleString()}
|
|
502
502
|
</div>
|
|
503
503
|
</div>
|
|
504
|
-
<div className="
|
|
505
|
-
<div className="text-xs text-gray-600
|
|
504
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
505
|
+
<div className="mb-1 text-xs text-gray-600">301 Permanent</div>
|
|
506
506
|
<div className="text-2xl font-semibold text-gray-900">
|
|
507
507
|
{redirects.filter((r: any) => r.type === '301').length}
|
|
508
508
|
</div>
|
|
509
509
|
</div>
|
|
510
510
|
</div>
|
|
511
511
|
|
|
512
|
-
<div className="flex items-center gap-3
|
|
513
|
-
<div className="relative
|
|
514
|
-
<Search className="absolute
|
|
512
|
+
<div className="mb-4 flex items-center gap-3">
|
|
513
|
+
<div className="relative max-w-md flex-1">
|
|
514
|
+
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
|
515
515
|
<input
|
|
516
516
|
type="text"
|
|
517
517
|
placeholder="Search redirects..."
|
|
518
518
|
value={redirectSearch}
|
|
519
519
|
onChange={(e) => setRedirectSearch(e.target.value)}
|
|
520
|
-
className="w-full
|
|
520
|
+
className="w-full rounded-lg border border-gray-300 bg-white py-2 pr-3 pl-9 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
521
521
|
/>
|
|
522
522
|
</div>
|
|
523
523
|
<button
|
|
524
524
|
onClick={() => setShowAddRedirect(true)}
|
|
525
|
-
className="flex items-center gap-2 px-4 py-2
|
|
525
|
+
className="flex shrink-0 items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-700"
|
|
526
526
|
>
|
|
527
|
-
<Plus className="
|
|
527
|
+
<Plus className="h-4 w-4" />
|
|
528
528
|
Add Redirect
|
|
529
529
|
</button>
|
|
530
530
|
</div>
|
|
531
531
|
|
|
532
|
-
<div className="
|
|
533
|
-
<div className="overflow-x-auto
|
|
532
|
+
<div className="flex-1 overflow-hidden rounded-lg border border-gray-200 bg-white">
|
|
533
|
+
<div className="h-full overflow-x-auto">
|
|
534
534
|
<table className="w-full">
|
|
535
|
-
<thead className="
|
|
535
|
+
<thead className="sticky top-0 border-b border-gray-200 bg-gray-50">
|
|
536
536
|
<tr>
|
|
537
537
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
|
|
538
538
|
Source
|
|
@@ -552,7 +552,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
552
552
|
</thead>
|
|
553
553
|
<tbody className="divide-y divide-gray-200">
|
|
554
554
|
{filteredRedirects.map((r: any) => (
|
|
555
|
-
<tr key={r.id} className="hover:bg-gray-50
|
|
555
|
+
<tr key={r.id} className="transition-colors hover:bg-gray-50">
|
|
556
556
|
<td className="px-4 py-3">
|
|
557
557
|
<code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-900">
|
|
558
558
|
{r.from}
|
|
@@ -565,7 +565,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
565
565
|
</td>
|
|
566
566
|
<td className="px-4 py-3">
|
|
567
567
|
<span
|
|
568
|
-
className={`px-2 py-0.5
|
|
568
|
+
className={`rounded-full px-2 py-0.5 text-xs font-medium ${r.type === '301' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'}`}
|
|
569
569
|
>
|
|
570
570
|
{r.type}
|
|
571
571
|
</span>
|
|
@@ -573,21 +573,21 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
573
573
|
<td className="px-4 py-3 text-sm text-gray-600">{r.hits.toLocaleString()}</td>
|
|
574
574
|
<td className="px-4 py-3">
|
|
575
575
|
<span
|
|
576
|
-
className={`px-2 py-0.5
|
|
576
|
+
className={`rounded-full px-2 py-0.5 text-xs font-medium ${r.active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
|
|
577
577
|
>
|
|
578
578
|
{r.active ? 'Active' : 'Inactive'}
|
|
579
579
|
</span>
|
|
580
580
|
</td>
|
|
581
581
|
<td className="px-4 py-3">
|
|
582
582
|
<div className="flex items-center gap-2">
|
|
583
|
-
<button className="p-1.5 hover:bg-gray-100
|
|
584
|
-
<Pencil className="
|
|
583
|
+
<button className="rounded p-1.5 transition-colors hover:bg-gray-100">
|
|
584
|
+
<Pencil className="h-4 w-4 text-gray-600" />
|
|
585
585
|
</button>
|
|
586
586
|
<button
|
|
587
587
|
onClick={() => handleDeleteRedirect(r.id)}
|
|
588
|
-
className="p-1.5 hover:bg-gray-100
|
|
588
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
589
589
|
>
|
|
590
|
-
<Trash2 className="
|
|
590
|
+
<Trash2 className="h-4 w-4 text-red-600" />
|
|
591
591
|
</button>
|
|
592
592
|
</div>
|
|
593
593
|
</td>
|
|
@@ -601,7 +601,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
601
601
|
<Dialog.Root open={showAddRedirect} onOpenChange={setShowAddRedirect}>
|
|
602
602
|
<Dialog.Portal>
|
|
603
603
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/50" />
|
|
604
|
-
<Dialog.Content className="fixed
|
|
604
|
+
<Dialog.Content className="fixed top-1/2 left-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">
|
|
605
605
|
<Dialog.Title className="mb-4 text-lg font-semibold text-gray-900">
|
|
606
606
|
Add Redirect
|
|
607
607
|
</Dialog.Title>
|
|
@@ -615,7 +615,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
615
615
|
value={newRedirect.source}
|
|
616
616
|
onChange={(e) => setNewRedirect({ ...newRedirect, source: e.target.value })}
|
|
617
617
|
placeholder="/old-page"
|
|
618
|
-
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:
|
|
618
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
619
619
|
required
|
|
620
620
|
/>
|
|
621
621
|
</div>
|
|
@@ -630,7 +630,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
630
630
|
setNewRedirect({ ...newRedirect, destination: e.target.value })
|
|
631
631
|
}
|
|
632
632
|
placeholder="/new-page"
|
|
633
|
-
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:
|
|
633
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
634
634
|
required
|
|
635
635
|
/>
|
|
636
636
|
</div>
|
|
@@ -641,7 +641,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
641
641
|
onChange={(e) =>
|
|
642
642
|
setNewRedirect({ ...newRedirect, type: e.target.value as '301' | '302' })
|
|
643
643
|
}
|
|
644
|
-
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:
|
|
644
|
+
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
|
645
645
|
>
|
|
646
646
|
<option value="301">301 (Permanent)</option>
|
|
647
647
|
<option value="302">302 (Temporary)</option>
|
|
@@ -670,49 +670,49 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
670
670
|
</Tabs.Content>
|
|
671
671
|
|
|
672
672
|
{/* CANONICALIZATION TAB */}
|
|
673
|
-
<Tabs.Content value="canonicals" className="flex
|
|
674
|
-
<div className="grid grid-cols-2 lg:grid-cols-4
|
|
675
|
-
<div className="
|
|
676
|
-
<div className="flex items-center gap-2
|
|
677
|
-
<ShieldCheck className="
|
|
673
|
+
<Tabs.Content value="canonicals" className="flex min-h-0 flex-1 flex-col space-y-4">
|
|
674
|
+
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
675
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
676
|
+
<div className="mb-2 flex items-center gap-2">
|
|
677
|
+
<ShieldCheck className="h-4 w-4 text-green-600" />
|
|
678
678
|
<span className="text-xs font-medium text-gray-600">With Canonical</span>
|
|
679
679
|
</div>
|
|
680
680
|
<div className="text-2xl font-semibold text-green-700">{allCanonicals.length}</div>
|
|
681
681
|
</div>
|
|
682
|
-
<div className="
|
|
683
|
-
<div className="flex items-center gap-2
|
|
684
|
-
<AlertTriangle className="
|
|
682
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
683
|
+
<div className="mb-2 flex items-center gap-2">
|
|
684
|
+
<AlertTriangle className="h-4 w-4 text-red-600" />
|
|
685
685
|
<span className="text-xs font-medium text-gray-600">Missing Canonical</span>
|
|
686
686
|
</div>
|
|
687
687
|
<div className="text-2xl font-semibold text-red-700">{missingCanonical.length}</div>
|
|
688
688
|
</div>
|
|
689
|
-
<div className="
|
|
690
|
-
<div className="flex items-center gap-2
|
|
691
|
-
<Globe className="
|
|
689
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
690
|
+
<div className="mb-2 flex items-center gap-2">
|
|
691
|
+
<Globe className="h-4 w-4 text-blue-600" />
|
|
692
692
|
<span className="text-xs font-medium text-gray-600">Canonical Domains</span>
|
|
693
693
|
</div>
|
|
694
694
|
<div className="text-2xl font-semibold text-gray-900">{canonicalDomains.length}</div>
|
|
695
695
|
</div>
|
|
696
|
-
<div className="
|
|
697
|
-
<div className="flex items-center gap-2
|
|
698
|
-
<CheckCircle2 className="
|
|
696
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
697
|
+
<div className="mb-2 flex items-center gap-2">
|
|
698
|
+
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
699
699
|
<span className="text-xs font-medium text-gray-600">Self-Referencing</span>
|
|
700
700
|
</div>
|
|
701
701
|
<div className="text-2xl font-semibold text-green-700">{allCanonicals.length}</div>
|
|
702
|
-
<div className="text-xs text-gray-500
|
|
702
|
+
<div className="mt-1 text-xs text-gray-500">Correct pattern</div>
|
|
703
703
|
</div>
|
|
704
704
|
</div>
|
|
705
705
|
|
|
706
706
|
{missingCanonical.length > 0 && (
|
|
707
|
-
<div className="
|
|
707
|
+
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
|
|
708
708
|
<div className="flex items-start gap-3">
|
|
709
|
-
<AlertTriangle className="
|
|
709
|
+
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-yellow-600" />
|
|
710
710
|
<div className="flex-1">
|
|
711
|
-
<h3 className="text-sm font-semibold text-yellow-900
|
|
711
|
+
<h3 className="mb-1 text-sm font-semibold text-yellow-900">
|
|
712
712
|
{missingCanonical.length} page{missingCanonical.length !== 1 ? 's' : ''} missing
|
|
713
713
|
canonical URLs
|
|
714
714
|
</h3>
|
|
715
|
-
<p className="text-sm text-yellow-700
|
|
715
|
+
<p className="mb-2 text-sm text-yellow-700">
|
|
716
716
|
Without canonical tags, search engines may index duplicate versions of these
|
|
717
717
|
pages, diluting your ranking signals.
|
|
718
718
|
</p>
|
|
@@ -721,7 +721,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
721
721
|
<button
|
|
722
722
|
key={p.id}
|
|
723
723
|
onClick={() => onNavigate?.(`/pages/${p.id}`)}
|
|
724
|
-
className="px-2.5 py-1 text-xs
|
|
724
|
+
className="rounded-lg bg-yellow-100 px-2.5 py-1 text-xs text-yellow-900 transition-colors hover:bg-yellow-200"
|
|
725
725
|
>
|
|
726
726
|
{p.title} ({p.url})
|
|
727
727
|
</button>
|
|
@@ -732,10 +732,10 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
732
732
|
</div>
|
|
733
733
|
)}
|
|
734
734
|
|
|
735
|
-
<div className="
|
|
736
|
-
<div className="overflow-x-auto
|
|
735
|
+
<div className="flex-1 overflow-hidden rounded-lg border border-gray-200 bg-white">
|
|
736
|
+
<div className="h-full overflow-x-auto">
|
|
737
737
|
<table className="w-full">
|
|
738
|
-
<thead className="
|
|
738
|
+
<thead className="sticky top-0 border-b border-gray-200 bg-gray-50">
|
|
739
739
|
<tr>
|
|
740
740
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">Page</th>
|
|
741
741
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">URL</th>
|
|
@@ -752,29 +752,29 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
752
752
|
</thead>
|
|
753
753
|
<tbody className="divide-y divide-gray-200">
|
|
754
754
|
{seoPages.map((page: any) => (
|
|
755
|
-
<tr key={page.id} className="hover:bg-gray-50
|
|
755
|
+
<tr key={page.id} className="transition-colors hover:bg-gray-50">
|
|
756
756
|
<td className="px-4 py-3 text-sm font-medium text-gray-900">{page.title}</td>
|
|
757
757
|
<td className="px-4 py-3">
|
|
758
|
-
<code className="
|
|
758
|
+
<code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700">
|
|
759
759
|
{page.url}
|
|
760
760
|
</code>
|
|
761
761
|
</td>
|
|
762
762
|
<td className="px-4 py-3">
|
|
763
763
|
{page.canonical ? (
|
|
764
|
-
<code className="
|
|
764
|
+
<code className="block max-w-[200px] truncate rounded bg-gray-100 px-2 py-1 text-xs text-gray-700">
|
|
765
765
|
{page.canonical}
|
|
766
766
|
</code>
|
|
767
767
|
) : (
|
|
768
|
-
<span className="text-xs text-red-600
|
|
768
|
+
<span className="text-xs font-medium text-red-600">Not set</span>
|
|
769
769
|
)}
|
|
770
770
|
</td>
|
|
771
771
|
<td className="px-4 py-3">
|
|
772
772
|
{page.canonical ? (
|
|
773
|
-
<span className="px-2 py-0.5
|
|
773
|
+
<span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
|
774
774
|
✓ Set
|
|
775
775
|
</span>
|
|
776
776
|
) : (
|
|
777
|
-
<span className="px-2 py-0.5
|
|
777
|
+
<span className="rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800">
|
|
778
778
|
Missing
|
|
779
779
|
</span>
|
|
780
780
|
)}
|
|
@@ -782,10 +782,10 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
782
782
|
<td className="px-4 py-3">
|
|
783
783
|
<button
|
|
784
784
|
onClick={() => onNavigate?.(`/pages/${page.id}`)}
|
|
785
|
-
className="p-1.5 hover:bg-gray-100
|
|
785
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
786
786
|
title="Edit"
|
|
787
787
|
>
|
|
788
|
-
<Pencil className="
|
|
788
|
+
<Pencil className="h-4 w-4 text-gray-600" />
|
|
789
789
|
</button>
|
|
790
790
|
</td>
|
|
791
791
|
</tr>
|
|
@@ -797,38 +797,38 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
797
797
|
</Tabs.Content>
|
|
798
798
|
|
|
799
799
|
{/* LINK HEALTH TAB */}
|
|
800
|
-
<Tabs.Content value="links" className="flex
|
|
801
|
-
<div className="grid grid-cols-2 lg:grid-cols-4
|
|
802
|
-
<div className="
|
|
803
|
-
<div className="text-xs text-gray-600
|
|
800
|
+
<Tabs.Content value="links" className="flex min-h-0 flex-1 flex-col space-y-4">
|
|
801
|
+
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
802
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
803
|
+
<div className="mb-1 text-xs text-gray-600">Broken Links</div>
|
|
804
804
|
<div className="text-2xl font-semibold text-red-600">
|
|
805
805
|
{linkHealth.filter((l: any) => l.status === 404).length}
|
|
806
806
|
</div>
|
|
807
807
|
</div>
|
|
808
|
-
<div className="
|
|
809
|
-
<div className="text-xs text-gray-600
|
|
808
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
809
|
+
<div className="mb-1 text-xs text-gray-600">Redirect Chains</div>
|
|
810
810
|
<div className="text-2xl font-semibold text-yellow-600">
|
|
811
811
|
{linkHealth.filter((l: any) => l.status === 301).length}
|
|
812
812
|
</div>
|
|
813
813
|
</div>
|
|
814
|
-
<div className="
|
|
815
|
-
<div className="text-xs text-gray-600
|
|
814
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
815
|
+
<div className="mb-1 text-xs text-gray-600">Internal Issues</div>
|
|
816
816
|
<div className="text-2xl font-semibold text-gray-900">
|
|
817
817
|
{linkHealth.filter((l: any) => l.type === 'internal').length}
|
|
818
818
|
</div>
|
|
819
819
|
</div>
|
|
820
|
-
<div className="
|
|
821
|
-
<div className="text-xs text-gray-600
|
|
820
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
821
|
+
<div className="mb-1 text-xs text-gray-600">External Issues</div>
|
|
822
822
|
<div className="text-2xl font-semibold text-gray-900">
|
|
823
823
|
{linkHealth.filter((l: any) => l.type === 'external').length}
|
|
824
824
|
</div>
|
|
825
825
|
</div>
|
|
826
826
|
</div>
|
|
827
827
|
|
|
828
|
-
<div className="
|
|
829
|
-
<div className="overflow-x-auto
|
|
828
|
+
<div className="flex-1 overflow-hidden rounded-lg border border-gray-200 bg-white">
|
|
829
|
+
<div className="h-full overflow-x-auto">
|
|
830
830
|
<table className="w-full">
|
|
831
|
-
<thead className="
|
|
831
|
+
<thead className="sticky top-0 border-b border-gray-200 bg-gray-50">
|
|
832
832
|
<tr>
|
|
833
833
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-700">
|
|
834
834
|
Found On
|
|
@@ -850,27 +850,27 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
850
850
|
</thead>
|
|
851
851
|
<tbody className="divide-y divide-gray-200">
|
|
852
852
|
{linkHealth.map((link: any) => (
|
|
853
|
-
<tr key={link.id} className="hover:bg-gray-50
|
|
853
|
+
<tr key={link.id} className="transition-colors hover:bg-gray-50">
|
|
854
854
|
<td className="px-4 py-3">
|
|
855
|
-
<code className="
|
|
855
|
+
<code className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-700">
|
|
856
856
|
{link.page}
|
|
857
857
|
</code>
|
|
858
858
|
</td>
|
|
859
859
|
<td className="px-4 py-3">
|
|
860
|
-
<code className="
|
|
860
|
+
<code className="block max-w-[200px] truncate rounded bg-red-50 px-2 py-1 text-xs text-red-800">
|
|
861
861
|
{link.url}
|
|
862
862
|
</code>
|
|
863
863
|
</td>
|
|
864
864
|
<td className="px-4 py-3">
|
|
865
865
|
<span
|
|
866
|
-
className={`px-2 py-0.5
|
|
866
|
+
className={`rounded-full px-2 py-0.5 text-xs font-medium ${link.status === 404 ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'}`}
|
|
867
867
|
>
|
|
868
868
|
{link.status}
|
|
869
869
|
</span>
|
|
870
870
|
</td>
|
|
871
871
|
<td className="px-4 py-3">
|
|
872
872
|
<span
|
|
873
|
-
className={`px-2 py-0.5
|
|
873
|
+
className={`rounded-full px-2 py-0.5 text-xs font-medium ${link.type === 'internal' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'}`}
|
|
874
874
|
>
|
|
875
875
|
{link.type}
|
|
876
876
|
</span>
|
|
@@ -879,16 +879,16 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
|
|
|
879
879
|
<td className="px-4 py-3">
|
|
880
880
|
<div className="flex items-center gap-1">
|
|
881
881
|
<button
|
|
882
|
-
className="p-1.5 hover:bg-gray-100
|
|
882
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
883
883
|
title="Create redirect"
|
|
884
884
|
>
|
|
885
|
-
<ArrowRightLeft className="
|
|
885
|
+
<ArrowRightLeft className="h-4 w-4 text-gray-600" />
|
|
886
886
|
</button>
|
|
887
887
|
<button
|
|
888
|
-
className="p-1.5 hover:bg-gray-100
|
|
888
|
+
className="rounded p-1.5 transition-colors hover:bg-gray-100"
|
|
889
889
|
title="Open URL"
|
|
890
890
|
>
|
|
891
|
-
<ExternalLink className="
|
|
891
|
+
<ExternalLink className="h-4 w-4 text-gray-600" />
|
|
892
892
|
</button>
|
|
893
893
|
</div>
|
|
894
894
|
</td>
|