@actuate-media/cms-admin 0.19.0 → 0.20.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 +9 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/seo/Drawer.d.ts +11 -0
- package/dist/components/seo/Drawer.d.ts.map +1 -0
- package/dist/components/seo/Drawer.js +13 -0
- package/dist/components/seo/Drawer.js.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts +9 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.js +94 -0
- package/dist/components/seo/RedirectEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts +10 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.js +34 -0
- package/dist/components/seo/SeoCopilotDrawer.js.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts +8 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.js +111 -0
- package/dist/components/seo/SeoEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts +8 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.js +111 -0
- package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -0
- package/dist/components/seo/primitives.d.ts +53 -0
- package/dist/components/seo/primitives.d.ts.map +1 -0
- package/dist/components/seo/primitives.js +100 -0
- package/dist/components/seo/primitives.js.map +1 -0
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +3 -0
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/seo-service.d.ts +322 -0
- package/dist/lib/seo-service.d.ts.map +1 -0
- package/dist/lib/seo-service.js +295 -0
- package/dist/lib/seo-service.js.map +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +4 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +36 -25
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.js +2 -2
- package/dist/views/Posts/post-type-presets.d.ts +5 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -1
- package/dist/views/Posts/post-type-presets.js +10 -64
- package/dist/views/Posts/post-type-presets.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +89 -132
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/seo/AuditTab.d.ts +6 -0
- package/dist/views/seo/AuditTab.d.ts.map +1 -0
- package/dist/views/seo/AuditTab.js +57 -0
- package/dist/views/seo/AuditTab.js.map +1 -0
- package/dist/views/seo/ContentTab.d.ts +5 -0
- package/dist/views/seo/ContentTab.d.ts.map +1 -0
- package/dist/views/seo/ContentTab.js +27 -0
- package/dist/views/seo/ContentTab.js.map +1 -0
- package/dist/views/seo/OverviewTab.d.ts +8 -0
- package/dist/views/seo/OverviewTab.d.ts.map +1 -0
- package/dist/views/seo/OverviewTab.js +35 -0
- package/dist/views/seo/OverviewTab.js.map +1 -0
- package/dist/views/seo/RedirectsTab.d.ts +2 -0
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -0
- package/dist/views/seo/RedirectsTab.js +124 -0
- package/dist/views/seo/RedirectsTab.js.map +1 -0
- package/dist/views/seo/TechnicalTab.d.ts +2 -0
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -0
- package/dist/views/seo/TechnicalTab.js +102 -0
- package/dist/views/seo/TechnicalTab.js.map +1 -0
- package/dist/views/seo/useSeoResource.d.ts +8 -0
- package/dist/views/seo/useSeoResource.d.ts.map +1 -0
- package/dist/views/seo/useSeoResource.js +42 -0
- package/dist/views/seo/useSeoResource.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +9 -5
- package/src/components/seo/Drawer.tsx +61 -0
- package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
- package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
- package/src/components/seo/SeoEditorDrawer.tsx +347 -0
- package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
- package/src/components/seo/primitives.tsx +237 -0
- package/src/layout/Sidebar.tsx +3 -0
- package/src/lib/seo-service.ts +623 -0
- package/src/router/index.ts +4 -1
- package/src/views/MediaBrowser.tsx +89 -67
- package/src/views/Posts/NewPostTypeInstructions.tsx +2 -2
- package/src/views/Posts/post-type-presets.ts +15 -64
- package/src/views/SEO.tsx +186 -863
- package/src/views/seo/AuditTab.tsx +229 -0
- package/src/views/seo/ContentTab.tsx +164 -0
- package/src/views/seo/OverviewTab.tsx +257 -0
- package/src/views/seo/RedirectsTab.tsx +392 -0
- package/src/views/seo/TechnicalTab.tsx +303 -0
- package/src/views/seo/useSeoResource.ts +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TechnicalTab.js","sourceRoot":"","sources":["../../../src/views/seo/TechnicalTab.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SeoResource<T> {
|
|
2
|
+
data: T | null;
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: string | null;
|
|
5
|
+
refetch: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function useSeoResource<T>(fetcher: () => Promise<T>, deps?: ReadonlyArray<unknown>): SeoResource<T>;
|
|
8
|
+
//# sourceMappingURL=useSeoResource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeoResource.d.ts","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,IAAI,GAAE,aAAa,CAAC,OAAO,CAAM,GAChC,WAAW,CAAC,CAAC,CAAC,CAgChB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Small async-resource hook for the SEO tabs: wraps a service fetcher with
|
|
4
|
+
* loading / error / data / refetch and abort-on-unmount safety. Keeps each
|
|
5
|
+
* tab declarative without re-implementing fetch plumbing.
|
|
6
|
+
*/
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
+
export function useSeoResource(fetcher, deps = []) {
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const aliveRef = useRef(true);
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
const stableFetcher = useCallback(fetcher, deps);
|
|
15
|
+
const load = useCallback(async () => {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
setError(null);
|
|
18
|
+
try {
|
|
19
|
+
const result = await stableFetcher();
|
|
20
|
+
if (aliveRef.current)
|
|
21
|
+
setData(result);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (aliveRef.current) {
|
|
25
|
+
setError(err instanceof Error ? err.message : 'Failed to load data');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (aliveRef.current)
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}
|
|
32
|
+
}, [stableFetcher]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
aliveRef.current = true;
|
|
35
|
+
load();
|
|
36
|
+
return () => {
|
|
37
|
+
aliveRef.current = false;
|
|
38
|
+
};
|
|
39
|
+
}, [load]);
|
|
40
|
+
return { data, loading, error, refetch: load };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=useSeoResource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeoResource.js","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAShE,MAAM,UAAU,cAAc,CAC5B,OAAyB,EACzB,OAA+B,EAAE;IAEjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7B,uDAAuD;IACvD,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAEhD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAA;YACpC,IAAI,QAAQ,CAAC,OAAO;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAA;YACtE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,OAAO;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAA;QACzC,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACvB,IAAI,EAAE,CAAA;QACN,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;QAC1B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cms-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/actuate-media/actuatecms.git",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"tailwindcss": "^4.0.0",
|
|
88
88
|
"typescript": "^5.7.0",
|
|
89
89
|
"vitest": "^3.0.0",
|
|
90
|
-
"@actuate-media/cms-core": "0.
|
|
90
|
+
"@actuate-media/cms-core": "0.29.0",
|
|
91
91
|
"@actuate-media/component-blocks": "0.2.0"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
package/src/AdminRoot.tsx
CHANGED
|
@@ -379,17 +379,21 @@ function AdminShell({
|
|
|
379
379
|
return <Forms onNavigate={navigate} />
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// Legacy deep paths map onto the new five-tab IA.
|
|
382
383
|
if (matchRoute('/seo/redirects')) {
|
|
383
384
|
return <SEO onNavigate={navigate} initialTab="redirects" />
|
|
384
385
|
}
|
|
385
|
-
if (matchRoute('/seo/canonicals')) {
|
|
386
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
386
|
+
if (matchRoute('/seo/content') || matchRoute('/seo/canonicals')) {
|
|
387
|
+
return <SEO onNavigate={navigate} initialTab="content" />
|
|
387
388
|
}
|
|
388
|
-
if (matchRoute('/seo/links')) {
|
|
389
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
389
|
+
if (matchRoute('/seo/technical') || matchRoute('/seo/links')) {
|
|
390
|
+
return <SEO onNavigate={navigate} initialTab="technical" />
|
|
391
|
+
}
|
|
392
|
+
if (matchRoute('/seo/audit')) {
|
|
393
|
+
return <SEO onNavigate={navigate} initialTab="audit" />
|
|
390
394
|
}
|
|
391
395
|
if (matchRoute('/seo')) {
|
|
392
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
396
|
+
return <SEO onNavigate={navigate} initialTab="overview" />
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
if (matchRoute('/script-tags/new')) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Right-side drawer built on Radix Dialog — focus-trapped, escape-closable,
|
|
5
|
+
* token-only styling. Shared by the SEO editor / fix / copilot drawers.
|
|
6
|
+
*/
|
|
7
|
+
import * as Dialog from '@radix-ui/react-dialog'
|
|
8
|
+
import { X } from 'lucide-react'
|
|
9
|
+
import type { ReactNode } from 'react'
|
|
10
|
+
import { btnGhostIcon } from './primitives.js'
|
|
11
|
+
|
|
12
|
+
export function Drawer({
|
|
13
|
+
open,
|
|
14
|
+
onOpenChange,
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
footer,
|
|
18
|
+
children,
|
|
19
|
+
width = 'max-w-xl',
|
|
20
|
+
}: {
|
|
21
|
+
open: boolean
|
|
22
|
+
onOpenChange: (open: boolean) => void
|
|
23
|
+
title: string
|
|
24
|
+
description?: string
|
|
25
|
+
footer?: ReactNode
|
|
26
|
+
children: ReactNode
|
|
27
|
+
width?: string
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
31
|
+
<Dialog.Portal>
|
|
32
|
+
<Dialog.Overlay className="bg-foreground/40 motion-safe:animate-in motion-safe:fade-in fixed inset-0 z-50" />
|
|
33
|
+
<Dialog.Content
|
|
34
|
+
className={`bg-card text-foreground fixed inset-y-0 right-0 z-50 flex w-full ${width} motion-safe:animate-in motion-safe:slide-in-from-right flex-col shadow-xl outline-none`}
|
|
35
|
+
>
|
|
36
|
+
<header className="border-border flex items-start justify-between gap-3 border-b px-5 py-4">
|
|
37
|
+
<div className="min-w-0">
|
|
38
|
+
<Dialog.Title className="text-foreground text-lg font-medium">{title}</Dialog.Title>
|
|
39
|
+
{description && (
|
|
40
|
+
<Dialog.Description className="text-muted-foreground mt-0.5 text-sm">
|
|
41
|
+
{description}
|
|
42
|
+
</Dialog.Description>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
<Dialog.Close asChild>
|
|
46
|
+
<button className={btnGhostIcon} aria-label="Close">
|
|
47
|
+
<X size={20} aria-hidden />
|
|
48
|
+
</button>
|
|
49
|
+
</Dialog.Close>
|
|
50
|
+
</header>
|
|
51
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">{children}</div>
|
|
52
|
+
{footer && (
|
|
53
|
+
<footer className="border-border bg-card flex items-center justify-end gap-2 border-t px-5 py-4">
|
|
54
|
+
{footer}
|
|
55
|
+
</footer>
|
|
56
|
+
)}
|
|
57
|
+
</Dialog.Content>
|
|
58
|
+
</Dialog.Portal>
|
|
59
|
+
</Dialog.Root>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { toast } from 'sonner'
|
|
5
|
+
import {
|
|
6
|
+
createRedirectRule,
|
|
7
|
+
updateRedirectRule,
|
|
8
|
+
type RedirectRule,
|
|
9
|
+
type RedirectRulePayload,
|
|
10
|
+
type RedirectStatusCode,
|
|
11
|
+
} from '../../lib/seo-service.js'
|
|
12
|
+
import { Drawer } from './Drawer.js'
|
|
13
|
+
import { btnPrimary, btnSecondary } from './primitives.js'
|
|
14
|
+
|
|
15
|
+
const STATUS_CODES: { value: RedirectStatusCode; label: string }[] = [
|
|
16
|
+
{ value: 301, label: '301 — Permanent' },
|
|
17
|
+
{ value: 302, label: '302 — Temporary' },
|
|
18
|
+
{ value: 307, label: '307 — Temporary (preserve method)' },
|
|
19
|
+
{ value: 308, label: '308 — Permanent (preserve method)' },
|
|
20
|
+
{ value: 410, label: '410 — Gone (no destination)' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const inputCls =
|
|
24
|
+
'w-full rounded-md border border-border bg-input-background px-3 py-2 text-base text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'
|
|
25
|
+
|
|
26
|
+
function normalizePath(p: string): string {
|
|
27
|
+
const t = p.trim()
|
|
28
|
+
if (!t) return ''
|
|
29
|
+
if (/^https?:\/\//i.test(t)) return t
|
|
30
|
+
return t.startsWith('/') ? t : `/${t}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function RedirectEditorDrawer({
|
|
34
|
+
open,
|
|
35
|
+
onOpenChange,
|
|
36
|
+
rule,
|
|
37
|
+
onSaved,
|
|
38
|
+
}: {
|
|
39
|
+
open: boolean
|
|
40
|
+
onOpenChange: (open: boolean) => void
|
|
41
|
+
/** null = create mode */
|
|
42
|
+
rule: RedirectRule | null
|
|
43
|
+
onSaved?: () => void
|
|
44
|
+
}) {
|
|
45
|
+
const [form, setForm] = useState<RedirectRulePayload>({
|
|
46
|
+
type: 301,
|
|
47
|
+
fromPath: '',
|
|
48
|
+
toPath: '',
|
|
49
|
+
enabled: true,
|
|
50
|
+
preserveQuery: false,
|
|
51
|
+
notes: '',
|
|
52
|
+
})
|
|
53
|
+
const [saving, setSaving] = useState(false)
|
|
54
|
+
const [clientError, setClientError] = useState<string | null>(null)
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!open) return
|
|
58
|
+
if (rule) {
|
|
59
|
+
setForm({
|
|
60
|
+
type: rule.type,
|
|
61
|
+
fromPath: rule.fromPath,
|
|
62
|
+
toPath: rule.toPath,
|
|
63
|
+
enabled: rule.enabled,
|
|
64
|
+
preserveQuery: rule.preserveQuery,
|
|
65
|
+
notes: rule.notes ?? '',
|
|
66
|
+
})
|
|
67
|
+
} else {
|
|
68
|
+
setForm({
|
|
69
|
+
type: 301,
|
|
70
|
+
fromPath: '',
|
|
71
|
+
toPath: '',
|
|
72
|
+
enabled: true,
|
|
73
|
+
preserveQuery: false,
|
|
74
|
+
notes: '',
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
setClientError(null)
|
|
78
|
+
}, [open, rule])
|
|
79
|
+
|
|
80
|
+
const isGone = form.type === 410
|
|
81
|
+
const update = (patch: Partial<RedirectRulePayload>) => setForm((f) => ({ ...f, ...patch }))
|
|
82
|
+
|
|
83
|
+
async function handleSave() {
|
|
84
|
+
const fromPath = normalizePath(form.fromPath)
|
|
85
|
+
const toPath = isGone ? '' : normalizePath(form.toPath)
|
|
86
|
+
if (!fromPath) {
|
|
87
|
+
setClientError('Source path is required.')
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if (!isGone && !toPath) {
|
|
91
|
+
setClientError('Destination is required for redirect status codes.')
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (!isGone && fromPath === toPath) {
|
|
95
|
+
setClientError('Source and destination cannot be identical (would create a loop).')
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
setClientError(null)
|
|
99
|
+
setSaving(true)
|
|
100
|
+
const payload: RedirectRulePayload = { ...form, fromPath, toPath }
|
|
101
|
+
const res = rule
|
|
102
|
+
? await updateRedirectRule(rule.id, payload)
|
|
103
|
+
: await createRedirectRule(payload)
|
|
104
|
+
setSaving(false)
|
|
105
|
+
if (res.error) {
|
|
106
|
+
toast.error(res.error)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
toast.success(rule ? 'Redirect updated.' : 'Redirect created.')
|
|
110
|
+
onOpenChange(false)
|
|
111
|
+
onSaved?.()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Drawer
|
|
116
|
+
open={open}
|
|
117
|
+
onOpenChange={onOpenChange}
|
|
118
|
+
title={rule ? 'Edit redirect' : 'New redirect'}
|
|
119
|
+
width="max-w-lg"
|
|
120
|
+
footer={
|
|
121
|
+
<>
|
|
122
|
+
<button className={btnSecondary} onClick={() => onOpenChange(false)} disabled={saving}>
|
|
123
|
+
Cancel
|
|
124
|
+
</button>
|
|
125
|
+
<button className={btnPrimary} onClick={handleSave} disabled={saving}>
|
|
126
|
+
{saving ? 'Saving…' : rule ? 'Save changes' : 'Create redirect'}
|
|
127
|
+
</button>
|
|
128
|
+
</>
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
<div className="space-y-4">
|
|
132
|
+
{clientError && (
|
|
133
|
+
<p
|
|
134
|
+
className="text-destructive bg-destructive/10 rounded-md px-3 py-2 text-sm"
|
|
135
|
+
role="alert"
|
|
136
|
+
>
|
|
137
|
+
{clientError}
|
|
138
|
+
</p>
|
|
139
|
+
)}
|
|
140
|
+
<div className="space-y-1.5">
|
|
141
|
+
<label className="text-foreground text-sm font-medium" htmlFor="redir-type">
|
|
142
|
+
Status code
|
|
143
|
+
</label>
|
|
144
|
+
<select
|
|
145
|
+
id="redir-type"
|
|
146
|
+
className={inputCls}
|
|
147
|
+
value={form.type}
|
|
148
|
+
onChange={(e) => update({ type: Number(e.target.value) as RedirectStatusCode })}
|
|
149
|
+
>
|
|
150
|
+
{STATUS_CODES.map((s) => (
|
|
151
|
+
<option key={s.value} value={s.value}>
|
|
152
|
+
{s.label}
|
|
153
|
+
</option>
|
|
154
|
+
))}
|
|
155
|
+
</select>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className="space-y-1.5">
|
|
159
|
+
<label className="text-foreground text-sm font-medium" htmlFor="redir-from">
|
|
160
|
+
Source path
|
|
161
|
+
</label>
|
|
162
|
+
<input
|
|
163
|
+
id="redir-from"
|
|
164
|
+
className={inputCls}
|
|
165
|
+
value={form.fromPath}
|
|
166
|
+
onChange={(e) => update({ fromPath: e.target.value })}
|
|
167
|
+
placeholder="/old-path"
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{!isGone && (
|
|
172
|
+
<div className="space-y-1.5">
|
|
173
|
+
<label className="text-foreground text-sm font-medium" htmlFor="redir-to">
|
|
174
|
+
Destination
|
|
175
|
+
</label>
|
|
176
|
+
<input
|
|
177
|
+
id="redir-to"
|
|
178
|
+
className={inputCls}
|
|
179
|
+
value={form.toPath}
|
|
180
|
+
onChange={(e) => update({ toPath: e.target.value })}
|
|
181
|
+
placeholder="/new-path or https://example.com/page"
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<div className="space-y-1.5">
|
|
187
|
+
<label className="text-foreground text-sm font-medium" htmlFor="redir-notes">
|
|
188
|
+
Notes <span className="text-muted-foreground font-normal">(optional)</span>
|
|
189
|
+
</label>
|
|
190
|
+
<input
|
|
191
|
+
id="redir-notes"
|
|
192
|
+
className={inputCls}
|
|
193
|
+
value={form.notes ?? ''}
|
|
194
|
+
onChange={(e) => update({ notes: e.target.value })}
|
|
195
|
+
placeholder="Why this redirect exists"
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div className="border-border space-y-3 rounded-lg border p-4">
|
|
200
|
+
<label className="flex items-center justify-between gap-3">
|
|
201
|
+
<span className="text-foreground text-sm font-medium">Enabled</span>
|
|
202
|
+
<input
|
|
203
|
+
type="checkbox"
|
|
204
|
+
role="switch"
|
|
205
|
+
aria-checked={!!form.enabled}
|
|
206
|
+
checked={!!form.enabled}
|
|
207
|
+
onChange={(e) => update({ enabled: e.target.checked })}
|
|
208
|
+
className="accent-primary h-4 w-4"
|
|
209
|
+
/>
|
|
210
|
+
</label>
|
|
211
|
+
{!isGone && (
|
|
212
|
+
<label className="flex items-center justify-between gap-3">
|
|
213
|
+
<span className="min-w-0">
|
|
214
|
+
<span className="text-foreground block text-sm font-medium">
|
|
215
|
+
Preserve query string
|
|
216
|
+
</span>
|
|
217
|
+
<span className="text-muted-foreground block text-sm">
|
|
218
|
+
Append the original query params to the destination.
|
|
219
|
+
</span>
|
|
220
|
+
</span>
|
|
221
|
+
<input
|
|
222
|
+
type="checkbox"
|
|
223
|
+
role="switch"
|
|
224
|
+
aria-checked={!!form.preserveQuery}
|
|
225
|
+
checked={!!form.preserveQuery}
|
|
226
|
+
onChange={(e) => update({ preserveQuery: e.target.checked })}
|
|
227
|
+
className="accent-primary h-4 w-4 shrink-0"
|
|
228
|
+
/>
|
|
229
|
+
</label>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</Drawer>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Sparkles, Bot } from 'lucide-react'
|
|
5
|
+
import { toast } from 'sonner'
|
|
6
|
+
import { summarizeSeoAudit } from '../../lib/seo-service.js'
|
|
7
|
+
import { Drawer } from './Drawer.js'
|
|
8
|
+
import { SeoLoading, SeoEmptyState, btnPrimary } from './primitives.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AI Copilot drawer — summarizes the current SEO state and surfaces the top
|
|
12
|
+
* priorities. Degrades gracefully when AI is unconfigured. All AI text is
|
|
13
|
+
* advisory; the editor takes action via the tabs/drawers.
|
|
14
|
+
*/
|
|
15
|
+
export function SeoCopilotDrawer({
|
|
16
|
+
open,
|
|
17
|
+
onOpenChange,
|
|
18
|
+
}: {
|
|
19
|
+
open: boolean
|
|
20
|
+
onOpenChange: (open: boolean) => void
|
|
21
|
+
}) {
|
|
22
|
+
const [loading, setLoading] = useState(false)
|
|
23
|
+
const [summary, setSummary] = useState<string | null>(null)
|
|
24
|
+
const [unavailable, setUnavailable] = useState(false)
|
|
25
|
+
|
|
26
|
+
async function handleSummarize() {
|
|
27
|
+
setLoading(true)
|
|
28
|
+
setUnavailable(false)
|
|
29
|
+
const res = await summarizeSeoAudit()
|
|
30
|
+
setLoading(false)
|
|
31
|
+
if (res.unavailable) {
|
|
32
|
+
setUnavailable(true)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (res.text) setSummary(res.text)
|
|
36
|
+
else toast.error('No summary was returned.')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Drawer
|
|
41
|
+
open={open}
|
|
42
|
+
onOpenChange={onOpenChange}
|
|
43
|
+
title="SEO Copilot"
|
|
44
|
+
description="AI-assisted analysis of your site's SEO health"
|
|
45
|
+
width="max-w-md"
|
|
46
|
+
footer={
|
|
47
|
+
<button className={btnPrimary} onClick={handleSummarize} disabled={loading}>
|
|
48
|
+
<Sparkles className={`h-4 w-4 ${loading ? 'animate-pulse' : ''}`} aria-hidden />
|
|
49
|
+
{loading ? 'Analyzing…' : summary ? 'Re-analyze' : 'Analyze my SEO'}
|
|
50
|
+
</button>
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
{loading ? (
|
|
54
|
+
<SeoLoading label="Analyzing your SEO…" />
|
|
55
|
+
) : unavailable ? (
|
|
56
|
+
<SeoEmptyState
|
|
57
|
+
icon={<Bot size={24} />}
|
|
58
|
+
title="AI is not configured"
|
|
59
|
+
description="Install and configure an AI provider (plugin-ai) to enable the SEO Copilot."
|
|
60
|
+
/>
|
|
61
|
+
) : summary ? (
|
|
62
|
+
<div className="text-foreground space-y-3 text-sm leading-relaxed">
|
|
63
|
+
{summary.split(/\n+/).map((para, i) => (
|
|
64
|
+
<p key={i}>{para}</p>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
) : (
|
|
68
|
+
<SeoEmptyState
|
|
69
|
+
icon={<Bot size={24} />}
|
|
70
|
+
title="Ask the Copilot"
|
|
71
|
+
description="Get an AI summary of your open issues and the top priorities to fix first."
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</Drawer>
|
|
75
|
+
)
|
|
76
|
+
}
|