@actuate-media/cms-admin 0.19.0 → 0.21.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 +13 -9
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/layout/sidebar-posts.test.js +6 -4
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
- package/dist/__tests__/lib/seo-service-errors.test.d.ts +2 -0
- package/dist/__tests__/lib/seo-service-errors.test.d.ts.map +1 -0
- package/dist/__tests__/lib/seo-service-errors.test.js +46 -0
- package/dist/__tests__/lib/seo-service-errors.test.js.map +1 -0
- package/dist/__tests__/views/use-seo-resource.test.d.ts +2 -0
- package/dist/__tests__/views/use-seo-resource.test.d.ts.map +1 -0
- package/dist/__tests__/views/use-seo-resource.test.js +56 -0
- package/dist/__tests__/views/use-seo-resource.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +4 -1
- package/dist/components/Breadcrumbs.js.map +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 +17 -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 +120 -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 +101 -0
- package/dist/components/seo/primitives.js.map +1 -0
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +8 -5
- 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 +331 -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/ApiKeys.d.ts +4 -1
- package/dist/views/ApiKeys.d.ts.map +1 -1
- package/dist/views/ApiKeys.js +5 -3
- package/dist/views/ApiKeys.js.map +1 -1
- package/dist/views/Dashboard.js +2 -2
- package/dist/views/Dashboard.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/Settings.d.ts +4 -1
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +45 -6
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/Users.d.ts +4 -1
- package/dist/views/Users.d.ts.map +1 -1
- package/dist/views/Users.js +5 -3
- package/dist/views/Users.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 +130 -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 +105 -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 +55 -0
- package/dist/views/seo/useSeoResource.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +13 -9
- package/src/__tests__/layout/sidebar-posts.test.ts +6 -4
- package/src/__tests__/lib/seo-service-errors.test.ts +60 -0
- package/src/__tests__/views/use-seo-resource.test.tsx +68 -0
- package/src/components/Breadcrumbs.tsx +4 -1
- package/src/components/seo/Drawer.tsx +65 -0
- package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
- package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
- package/src/components/seo/SeoEditorDrawer.tsx +359 -0
- package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
- package/src/components/seo/primitives.tsx +240 -0
- package/src/layout/Sidebar.tsx +7 -10
- package/src/lib/seo-service.ts +660 -0
- package/src/router/index.ts +4 -1
- package/src/views/ApiKeys.tsx +10 -5
- package/src/views/Dashboard.tsx +2 -2
- 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/Settings.tsx +93 -40
- package/src/views/Users.tsx +10 -4
- 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 +398 -0
- package/src/views/seo/TechnicalTab.tsx +315 -0
- package/src/views/seo/useSeoResource.ts +66 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
import { createRedirectRule, updateRedirectRule, } from '../../lib/seo-service.js';
|
|
6
|
+
import { Drawer } from './Drawer.js';
|
|
7
|
+
import { btnPrimary, btnSecondary } from './primitives.js';
|
|
8
|
+
const STATUS_CODES = [
|
|
9
|
+
{ value: 301, label: '301 — Permanent' },
|
|
10
|
+
{ value: 302, label: '302 — Temporary' },
|
|
11
|
+
{ value: 307, label: '307 — Temporary (preserve method)' },
|
|
12
|
+
{ value: 308, label: '308 — Permanent (preserve method)' },
|
|
13
|
+
{ value: 410, label: '410 — Gone (no destination)' },
|
|
14
|
+
];
|
|
15
|
+
const inputCls = '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';
|
|
16
|
+
function normalizePath(p) {
|
|
17
|
+
const t = p.trim();
|
|
18
|
+
if (!t)
|
|
19
|
+
return '';
|
|
20
|
+
if (/^https?:\/\//i.test(t))
|
|
21
|
+
return t;
|
|
22
|
+
return t.startsWith('/') ? t : `/${t}`;
|
|
23
|
+
}
|
|
24
|
+
export function RedirectEditorDrawer({ open, onOpenChange, rule, onSaved, }) {
|
|
25
|
+
const [form, setForm] = useState({
|
|
26
|
+
type: 301,
|
|
27
|
+
fromPath: '',
|
|
28
|
+
toPath: '',
|
|
29
|
+
enabled: true,
|
|
30
|
+
preserveQuery: false,
|
|
31
|
+
notes: '',
|
|
32
|
+
});
|
|
33
|
+
const [saving, setSaving] = useState(false);
|
|
34
|
+
const [clientError, setClientError] = useState(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!open)
|
|
37
|
+
return;
|
|
38
|
+
if (rule) {
|
|
39
|
+
setForm({
|
|
40
|
+
type: rule.type,
|
|
41
|
+
fromPath: rule.fromPath,
|
|
42
|
+
toPath: rule.toPath,
|
|
43
|
+
enabled: rule.enabled,
|
|
44
|
+
preserveQuery: rule.preserveQuery,
|
|
45
|
+
notes: rule.notes ?? '',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
setForm({
|
|
50
|
+
type: 301,
|
|
51
|
+
fromPath: '',
|
|
52
|
+
toPath: '',
|
|
53
|
+
enabled: true,
|
|
54
|
+
preserveQuery: false,
|
|
55
|
+
notes: '',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
setClientError(null);
|
|
59
|
+
}, [open, rule]);
|
|
60
|
+
const isGone = form.type === 410;
|
|
61
|
+
const update = (patch) => setForm((f) => ({ ...f, ...patch }));
|
|
62
|
+
async function handleSave() {
|
|
63
|
+
const fromPath = normalizePath(form.fromPath);
|
|
64
|
+
const toPath = isGone ? '' : normalizePath(form.toPath);
|
|
65
|
+
if (!fromPath) {
|
|
66
|
+
setClientError('Source path is required.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!isGone && !toPath) {
|
|
70
|
+
setClientError('Destination is required for redirect status codes.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!isGone && fromPath === toPath) {
|
|
74
|
+
setClientError('Source and destination cannot be identical (would create a loop).');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
setClientError(null);
|
|
78
|
+
setSaving(true);
|
|
79
|
+
const payload = { ...form, fromPath, toPath };
|
|
80
|
+
const res = rule
|
|
81
|
+
? await updateRedirectRule(rule.id, payload)
|
|
82
|
+
: await createRedirectRule(payload);
|
|
83
|
+
setSaving(false);
|
|
84
|
+
if (res.error) {
|
|
85
|
+
toast.error(res.error);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
toast.success(rule ? 'Redirect updated.' : 'Redirect created.');
|
|
89
|
+
onOpenChange(false);
|
|
90
|
+
onSaved?.();
|
|
91
|
+
}
|
|
92
|
+
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: rule ? 'Edit redirect' : 'New redirect', width: "max-w-lg", footer: _jsxs(_Fragment, { children: [_jsx("button", { className: btnSecondary, onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }), _jsx("button", { className: btnPrimary, onClick: handleSave, disabled: saving, children: saving ? 'Saving…' : rule ? 'Save changes' : 'Create redirect' })] }), children: _jsxs("div", { className: "space-y-4", children: [clientError && (_jsx("p", { className: "text-destructive bg-destructive/10 rounded-md px-3 py-2 text-sm", role: "alert", children: clientError })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-type", children: "Status code" }), _jsx("select", { id: "redir-type", className: inputCls, value: form.type, onChange: (e) => update({ type: Number(e.target.value) }), children: STATUS_CODES.map((s) => (_jsx("option", { value: s.value, children: s.label }, s.value))) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-from", children: "Source path" }), _jsx("input", { id: "redir-from", className: inputCls, value: form.fromPath, onChange: (e) => update({ fromPath: e.target.value }), placeholder: "/old-path" })] }), !isGone && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-to", children: "Destination" }), _jsx("input", { id: "redir-to", className: inputCls, value: form.toPath, onChange: (e) => update({ toPath: e.target.value }), placeholder: "/new-path or https://example.com/page" })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsxs("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-notes", children: ["Notes ", _jsx("span", { className: "text-muted-foreground font-normal", children: "(optional)" })] }), _jsx("input", { id: "redir-notes", className: inputCls, value: form.notes ?? '', onChange: (e) => update({ notes: e.target.value }), placeholder: "Why this redirect exists" })] }), _jsxs("div", { className: "border-border space-y-3 rounded-lg border p-4", children: [_jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: "Enabled" }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": !!form.enabled, checked: !!form.enabled, onChange: (e) => update({ enabled: e.target.checked }), className: "accent-primary h-4 w-4" })] }), !isGone && (_jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: "Preserve query string" }), _jsx("span", { className: "text-muted-foreground block text-sm", children: "Append the original query params to the destination." })] }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": !!form.preserveQuery, checked: !!form.preserveQuery, onChange: (e) => update({ preserveQuery: e.target.checked }), className: "accent-primary h-4 w-4 shrink-0" })] }))] })] }) }));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=RedirectEditorDrawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedirectEditorDrawer.js","sourceRoot":"","sources":["../../../src/components/seo/RedirectEditorDrawer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Copilot drawer — summarizes the current SEO state and surfaces the top
|
|
3
|
+
* priorities. Degrades gracefully when AI is unconfigured. All AI text is
|
|
4
|
+
* advisory; the editor takes action via the tabs/drawers.
|
|
5
|
+
*/
|
|
6
|
+
export declare function SeoCopilotDrawer({ open, onOpenChange, }: {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onOpenChange: (open: boolean) => void;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=SeoCopilotDrawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoCopilotDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/seo/SeoCopilotDrawer.tsx"],"names":[],"mappings":"AASA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,GACb,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtC,2CAuDA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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
|
+
* AI Copilot drawer — summarizes the current SEO state and surfaces the top
|
|
11
|
+
* priorities. Degrades gracefully when AI is unconfigured. All AI text is
|
|
12
|
+
* advisory; the editor takes action via the tabs/drawers.
|
|
13
|
+
*/
|
|
14
|
+
export function SeoCopilotDrawer({ open, onOpenChange, }) {
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [summary, setSummary] = useState(null);
|
|
17
|
+
const [unavailable, setUnavailable] = useState(false);
|
|
18
|
+
async function handleSummarize() {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setUnavailable(false);
|
|
21
|
+
const res = await summarizeSeoAudit();
|
|
22
|
+
setLoading(false);
|
|
23
|
+
if (res.unavailable) {
|
|
24
|
+
setUnavailable(true);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (res.text)
|
|
28
|
+
setSummary(res.text);
|
|
29
|
+
else
|
|
30
|
+
toast.error('No summary was returned.');
|
|
31
|
+
}
|
|
32
|
+
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: "SEO Copilot", description: "AI-assisted analysis of your site's SEO health", width: "max-w-md", footer: _jsxs("button", { className: btnPrimary, onClick: handleSummarize, disabled: loading, children: [_jsx(Sparkles, { className: `h-4 w-4 ${loading ? 'animate-pulse' : ''}`, "aria-hidden": true }), loading ? 'Analyzing…' : summary ? 'Re-analyze' : 'Analyze my SEO'] }), children: loading ? (_jsx(SeoLoading, { label: "Analyzing your SEO\u2026" })) : unavailable ? (_jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "AI is not configured", description: "Install and configure an AI provider (plugin-ai) to enable the SEO Copilot." })) : summary ? (_jsx("div", { className: "text-foreground space-y-3 text-sm leading-relaxed", children: summary.split(/\n+/).map((para, i) => (_jsx("p", { children: para }, i))) })) : (_jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "Ask the Copilot", description: "Get an AI summary of your open issues and the top priorities to fix first." })) }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=SeoCopilotDrawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoCopilotDrawer.js","sourceRoot":"","sources":["../../../src/components/seo/SeoCopilotDrawer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEvE;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,GAIb;IACC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC3D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAErD,KAAK,UAAU,eAAe;QAC5B,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,cAAc,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,GAAG,GAAG,MAAM,iBAAiB,EAAE,CAAA;QACrC,UAAU,CAAC,KAAK,CAAC,CAAA;QACjB,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,cAAc,CAAC,IAAI,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,IAAI,GAAG,CAAC,IAAI;YAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;;YAC7B,KAAK,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAC9C,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAC,aAAa,EACnB,WAAW,EAAC,gDAAgD,EAC5D,KAAK,EAAC,UAAU,EAChB,MAAM,EACJ,kBAAQ,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,aACxE,KAAC,QAAQ,IAAC,SAAS,EAAE,WAAW,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,wBAAgB,EAC/E,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,IAC5D,YAGV,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,UAAU,IAAC,KAAK,EAAC,0BAAqB,GAAG,CAC3C,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAChB,KAAC,aAAa,IACZ,IAAI,EAAE,KAAC,GAAG,IAAC,IAAI,EAAE,EAAE,GAAI,EACvB,KAAK,EAAC,sBAAsB,EAC5B,WAAW,EAAC,6EAA6E,GACzF,CACH,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CACZ,cAAK,SAAS,EAAC,mDAAmD,YAC/D,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CACrC,sBAAY,IAAI,IAAR,CAAC,CAAY,CACtB,CAAC,GACE,CACP,CAAC,CAAC,CAAC,CACF,KAAC,aAAa,IACZ,IAAI,EAAE,KAAC,GAAG,IAAC,IAAI,EAAE,EAAE,GAAI,EACvB,KAAK,EAAC,iBAAiB,EACvB,WAAW,EAAC,4EAA4E,GACxF,CACH,GACM,CACV,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function SeoEditorDrawer({ open, onOpenChange, entityType, entityId, onSaved, }: {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (open: boolean) => void;
|
|
4
|
+
entityType: string | null;
|
|
5
|
+
entityId: string | null;
|
|
6
|
+
onSaved?: () => void;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=SeoEditorDrawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoEditorDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/seo/SeoEditorDrawer.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,2CA0RA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { cloneElement, isValidElement, useEffect, useId, useState } from 'react';
|
|
4
|
+
import { Sparkles, Check } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { fetchSeoContentRecord, updateSeoFields, generateSeoField, } from '../../lib/seo-service.js';
|
|
7
|
+
import { Drawer } from './Drawer.js';
|
|
8
|
+
import { SeoLoading, SeoErrorState, btnPrimary, btnSecondary, gradeMeta } from './primitives.js';
|
|
9
|
+
const META_TITLE_MAX = 60;
|
|
10
|
+
const META_DESC_MAX = 160;
|
|
11
|
+
function CharCount({ value, max }) {
|
|
12
|
+
const over = value.length > max;
|
|
13
|
+
return (_jsxs("span", { className: `text-xs ${over ? 'text-destructive' : 'text-muted-foreground'}`, children: [value.length, "/", max] }));
|
|
14
|
+
}
|
|
15
|
+
function Field({ label, children, hint, }) {
|
|
16
|
+
// Associate the visible label with its control for screen readers. We inject
|
|
17
|
+
// a generated id onto the single child input/textarea (unless it already has
|
|
18
|
+
// one) and point the <label htmlFor> at it.
|
|
19
|
+
const generatedId = useId();
|
|
20
|
+
let control = children;
|
|
21
|
+
if (isValidElement(children) && children.props.id == null) {
|
|
22
|
+
control = cloneElement(children, { id: generatedId });
|
|
23
|
+
}
|
|
24
|
+
const controlId = isValidElement(control) ? control.props.id : undefined;
|
|
25
|
+
return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { htmlFor: controlId, className: "text-foreground text-sm font-medium", children: label }), hint] }), control] }));
|
|
26
|
+
}
|
|
27
|
+
const inputCls = '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';
|
|
28
|
+
export function SeoEditorDrawer({ open, onOpenChange, entityType, entityId, onSaved, }) {
|
|
29
|
+
const [record, setRecord] = useState(null);
|
|
30
|
+
const [loading, setLoading] = useState(false);
|
|
31
|
+
const [error, setError] = useState(null);
|
|
32
|
+
const [saving, setSaving] = useState(false);
|
|
33
|
+
const [aiBusy, setAiBusy] = useState(null);
|
|
34
|
+
const [form, setForm] = useState({});
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!open || !entityType || !entityId)
|
|
37
|
+
return;
|
|
38
|
+
let alive = true;
|
|
39
|
+
setLoading(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
fetchSeoContentRecord(entityType, entityId)
|
|
42
|
+
.then((rec) => {
|
|
43
|
+
if (!alive)
|
|
44
|
+
return;
|
|
45
|
+
if (!rec) {
|
|
46
|
+
setError('Could not load this record.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setRecord(rec);
|
|
50
|
+
setForm({
|
|
51
|
+
metaTitle: rec.metaTitle ?? '',
|
|
52
|
+
metaDescription: rec.metaDescription ?? '',
|
|
53
|
+
canonicalUrl: rec.canonicalUrl ?? '',
|
|
54
|
+
focusKeyword: rec.focusKeyword ?? '',
|
|
55
|
+
ogTitle: rec.ogTitle ?? '',
|
|
56
|
+
ogDescription: rec.ogDescription ?? '',
|
|
57
|
+
noindex: rec.noindex,
|
|
58
|
+
nofollow: rec.nofollow,
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
.catch((e) => alive && setError(e instanceof Error ? e.message : 'Failed to load'))
|
|
62
|
+
.finally(() => alive && setLoading(false));
|
|
63
|
+
return () => {
|
|
64
|
+
alive = false;
|
|
65
|
+
};
|
|
66
|
+
}, [open, entityType, entityId]);
|
|
67
|
+
const update = (patch) => setForm((f) => ({ ...f, ...patch }));
|
|
68
|
+
async function handleAiGenerate(field) {
|
|
69
|
+
if (!entityType || !entityId)
|
|
70
|
+
return;
|
|
71
|
+
setAiBusy(field);
|
|
72
|
+
try {
|
|
73
|
+
const result = await generateSeoField(field, { entityType, entityId });
|
|
74
|
+
if (result.unavailable) {
|
|
75
|
+
toast.error('AI is not configured. Add an AI provider to enable suggestions.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (result.text) {
|
|
79
|
+
update({ [field]: result.text });
|
|
80
|
+
toast.success('AI suggestion applied — review before saving.');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
toast.error('No suggestion was returned.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
toast.error('AI request failed.');
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
setAiBusy(null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function handleSave() {
|
|
94
|
+
if (!entityType || !entityId)
|
|
95
|
+
return;
|
|
96
|
+
setSaving(true);
|
|
97
|
+
const res = await updateSeoFields(entityType, entityId, form);
|
|
98
|
+
setSaving(false);
|
|
99
|
+
if (res.error) {
|
|
100
|
+
toast.error(res.error);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
toast.success('SEO fields saved.');
|
|
104
|
+
onOpenChange(false);
|
|
105
|
+
onSaved?.();
|
|
106
|
+
}
|
|
107
|
+
const title = form.metaTitle || record?.title || 'Untitled';
|
|
108
|
+
const desc = form.metaDescription || 'No meta description set. Search engines may generate one.';
|
|
109
|
+
const url = record?.url ?? '';
|
|
110
|
+
function aiButton(field) {
|
|
111
|
+
return (_jsxs("button", { type: "button", onClick: () => handleAiGenerate(field), disabled: aiBusy !== null, className: "text-primary hover:bg-accent focus-visible:ring-ring inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", children: [_jsx(Sparkles, { className: `h-3.5 w-3.5 ${aiBusy === field ? 'animate-pulse' : ''}`, "aria-hidden": true }), aiBusy === field ? 'Generating…' : 'AI Generate'] }));
|
|
112
|
+
}
|
|
113
|
+
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: "Edit SEO", description: record?.title, footer: _jsxs(_Fragment, { children: [_jsx("button", { className: btnSecondary, onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }), _jsx("button", { className: btnPrimary, onClick: handleSave, disabled: saving || loading, children: saving ? 'Saving…' : 'Save changes' })] }), children: loading ? (_jsx(SeoLoading, {})) : error ? (_jsx(SeoErrorState, { message: error })) : record ? (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "border-border rounded-lg border p-4", children: [_jsx("p", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase", children: "Google preview" }), _jsx("p", { className: "text-success truncate text-sm", children: url }), _jsx("p", { className: "text-primary text-lg leading-snug", children: title }), _jsx("p", { className: "text-muted-foreground line-clamp-2 text-sm", children: desc })] }), _jsx(Field, { label: "Meta title", hint: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CharCount, { value: form.metaTitle ?? '', max: META_TITLE_MAX }), aiButton('metaTitle')] }), children: _jsx("input", { className: inputCls, value: form.metaTitle ?? '', onChange: (e) => update({ metaTitle: e.target.value }), placeholder: "Page title for search results" }) }), _jsx(Field, { label: "Meta description", hint: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CharCount, { value: form.metaDescription ?? '', max: META_DESC_MAX }), aiButton('metaDescription')] }), children: _jsx("textarea", { className: `${inputCls} min-h-20 resize-y`, value: form.metaDescription ?? '', onChange: (e) => update({ metaDescription: e.target.value }), placeholder: "Short summary shown under the title in search results" }) }), _jsx(Field, { label: "Focus keyword", hint: aiButton('focusKeyword'), children: _jsx("input", { className: inputCls, value: form.focusKeyword ?? '', onChange: (e) => update({ focusKeyword: e.target.value }), placeholder: "Primary keyword this page targets" }) }), _jsx(Field, { label: "Canonical URL", children: _jsx("input", { className: inputCls, value: form.canonicalUrl ?? '', onChange: (e) => update({ canonicalUrl: e.target.value }), placeholder: url || 'https://example.com/page' }) }), _jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: "Social sharing" }), _jsx(Field, { label: "Open Graph title", hint: aiButton('ogTitle'), children: _jsx("input", { className: inputCls, value: form.ogTitle ?? '', onChange: (e) => update({ ogTitle: e.target.value }), placeholder: form.metaTitle || 'Title when shared on social media' }) }), _jsx(Field, { label: "Open Graph description", hint: aiButton('ogDescription'), children: _jsx("textarea", { className: `${inputCls} min-h-16 resize-y`, value: form.ogDescription ?? '', onChange: (e) => update({ ogDescription: e.target.value }), placeholder: form.metaDescription || 'Description when shared on social media' }) }), _jsxs("div", { className: "border-border overflow-hidden rounded-lg border", children: [record.ogImage ? (
|
|
114
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
115
|
+
_jsx("img", { src: record.ogImage, alt: `Social preview for ${record.title}`, className: "bg-muted aspect-[1200/630] w-full object-cover" })) : (_jsx("div", { className: "bg-muted text-muted-foreground flex aspect-[1200/630] w-full items-center justify-center text-sm", children: "No OG image set" })), _jsxs("div", { className: "p-3", children: [_jsx("p", { className: "text-muted-foreground truncate text-xs uppercase", children: url.replace(/^https?:\/\//, '').split('/')[0] }), _jsx("p", { className: "text-foreground truncate text-sm font-medium", children: form.ogTitle || title }), _jsx("p", { className: "text-muted-foreground line-clamp-1 text-sm", children: form.ogDescription || desc })] })] })] }), _jsxs("div", { className: "border-border space-y-3 rounded-lg border p-4", children: [_jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: "No-index" }), _jsx("span", { className: "text-muted-foreground block text-sm", children: "Hide this page from search engines." })] }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": !!form.noindex, checked: !!form.noindex, onChange: (e) => update({ noindex: e.target.checked }), className: "accent-primary h-4 w-4" })] }), _jsxs("label", { className: "flex items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: "No-follow" }), _jsx("span", { className: "text-muted-foreground block text-sm", children: "Tell crawlers not to follow links on this page." })] }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": !!form.nofollow, checked: !!form.nofollow, onChange: (e) => update({ nofollow: e.target.checked }), className: "accent-primary h-4 w-4" })] })] }), (() => {
|
|
116
|
+
const meta = gradeMeta(record.seoGrade);
|
|
117
|
+
return (_jsxs("div", { className: `flex items-center gap-2 rounded-lg px-3 py-2 text-sm ${meta.bg} ${meta.text}`, children: [_jsx(Check, { className: "h-4 w-4", "aria-hidden": true }), "Current SEO score: ", _jsx("span", { className: "font-medium", children: record.seoScore }), " (", meta.label, ")"] }));
|
|
118
|
+
})()] })) : null }));
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=SeoEditorDrawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoEditorDrawer.js","sourceRoot":"","sources":["../../../src/components/seo/SeoEditorDrawer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAqB,MAAM,OAAO,CAAA;AACnG,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GAGjB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhG,MAAM,cAAc,GAAG,EAAE,CAAA;AACzB,MAAM,aAAa,GAAG,GAAG,CAAA;AAIzB,SAAS,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAkC;IAC/D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAA;IAC/B,OAAO,CACL,gBAAM,SAAS,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,uBAAuB,EAAE,aAC9E,KAAK,CAAC,MAAM,OAAG,GAAG,IACd,CACR,CAAA;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EACb,KAAK,EACL,QAAQ,EACR,IAAI,GAKL;IACC,6EAA6E;IAC7E,6EAA6E;IAC7E,4CAA4C;IAC5C,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,IAAI,OAAO,GAAG,QAAQ,CAAA;IACtB,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAK,QAAQ,CAAC,KAAyB,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QAC/E,OAAO,GAAG,YAAY,CAAC,QAAyC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;IACxF,CAAC;IACD,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,KAAyB,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAE7F,OAAO,CACL,eAAK,SAAS,EAAC,aAAa,aAC1B,eAAK,SAAS,EAAC,mCAAmC,aAChD,gBAAO,OAAO,EAAE,SAAS,EAAE,SAAS,EAAC,qCAAqC,YACvE,KAAK,GACA,EACP,IAAI,IACD,EACL,OAAO,IACJ,CACP,CAAA;AACH,CAAC;AAED,MAAM,QAAQ,GACZ,0MAA0M,CAAA;AAE5M,MAAM,UAAU,eAAe,CAAC,EAC9B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,OAAO,GAOR;IACC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA0B,IAAI,CAAC,CAAA;IACnE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAA;IAC1D,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAkB,EAAE,CAAC,CAAA;IAErD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ;YAAE,OAAM;QAC7C,IAAI,KAAK,GAAG,IAAI,CAAA;QAChB,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC;aACxC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACZ,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,QAAQ,CAAC,6BAA6B,CAAC,CAAA;gBACvC,OAAM;YACR,CAAC;YACD,SAAS,CAAC,GAAG,CAAC,CAAA;YACd,OAAO,CAAC;gBACN,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE;gBAC9B,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,EAAE;gBAC1C,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;gBACpC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;gBACpC,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;gBAC1B,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE;gBACtC,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;aACvB,CAAC,CAAA;QACJ,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;aAClF,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5C,OAAO,GAAG,EAAE;YACV,KAAK,GAAG,KAAK,CAAA;QACf,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEhC,MAAM,MAAM,GAAG,CAAC,KAA+B,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAA;IAExF,KAAK,UAAU,gBAAgB,CAAC,KAAc;QAC5C,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ;YAAE,OAAM;QACpC,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAA;YACtE,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACvB,KAAK,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAA;gBAC9E,OAAM;YACR,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChB,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,EAA8B,CAAC,CAAA;gBAC5D,KAAK,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAA;YAChE,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAA;YAC5C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAA;QACnC,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,IAAI,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;IAED,KAAK,UAAU,UAAU;QACvB,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ;YAAE,OAAM;QACpC,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC7D,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACtB,OAAM;QACR,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;QAClC,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,OAAO,EAAE,EAAE,CAAA;IACb,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,KAAK,IAAI,UAAU,CAAA;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,IAAI,2DAA2D,CAAA;IAChG,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,CAAA;IAE7B,SAAS,QAAQ,CAAC,KAAc;QAC9B,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EACtC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACzB,SAAS,EAAC,oMAAoM,aAE9M,KAAC,QAAQ,IACP,SAAS,EAAE,eAAe,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,wBAEnE,EACD,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,IAC1C,CACV,CAAA;IACH,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAC,UAAU,EAChB,WAAW,EAAE,MAAM,EAAE,KAAK,EAC1B,MAAM,EACJ,8BACE,iBAAQ,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,uBAE5E,EACT,iBAAQ,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,OAAO,YAC5E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,GAC7B,IACR,YAGJ,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,UAAU,KAAG,CACf,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACV,KAAC,aAAa,IAAC,OAAO,EAAE,KAAK,GAAI,CAClC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CACX,eAAK,SAAS,EAAC,WAAW,aAExB,eAAK,SAAS,EAAC,qCAAqC,aAClD,YAAG,SAAS,EAAC,wEAAwE,+BAEjF,EACJ,YAAG,SAAS,EAAC,+BAA+B,YAAE,GAAG,GAAK,EACtD,YAAG,SAAS,EAAC,mCAAmC,YAAE,KAAK,GAAK,EAC5D,YAAG,SAAS,EAAC,4CAA4C,YAAE,IAAI,GAAK,IAChE,EAEN,KAAC,KAAK,IACJ,KAAK,EAAC,YAAY,EAClB,IAAI,EACF,gBAAM,SAAS,EAAC,yBAAyB,aACvC,KAAC,SAAS,IAAC,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,cAAc,GAAI,EAC9D,QAAQ,CAAC,WAAW,CAAC,IACjB,YAGT,gBACE,SAAS,EAAE,QAAQ,EACnB,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,EAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EACtD,WAAW,EAAC,+BAA+B,GAC3C,GACI,EAER,KAAC,KAAK,IACJ,KAAK,EAAC,kBAAkB,EACxB,IAAI,EACF,gBAAM,SAAS,EAAC,yBAAyB,aACvC,KAAC,SAAS,IAAC,KAAK,EAAE,IAAI,CAAC,eAAe,IAAI,EAAE,EAAE,GAAG,EAAE,aAAa,GAAI,EACnE,QAAQ,CAAC,iBAAiB,CAAC,IACvB,YAGT,mBACE,SAAS,EAAE,GAAG,QAAQ,oBAAoB,EAC1C,KAAK,EAAE,IAAI,CAAC,eAAe,IAAI,EAAE,EACjC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAC5D,WAAW,EAAC,uDAAuD,GACnE,GACI,EAER,KAAC,KAAK,IAAC,KAAK,EAAC,eAAe,EAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC,YACzD,gBACE,SAAS,EAAE,QAAQ,EACnB,KAAK,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE,EAC9B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EACzD,WAAW,EAAC,mCAAmC,GAC/C,GACI,EAER,KAAC,KAAK,IAAC,KAAK,EAAC,eAAe,YAC1B,gBACE,SAAS,EAAE,QAAQ,EACnB,KAAK,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE,EAC9B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EACzD,WAAW,EAAE,GAAG,IAAI,0BAA0B,GAC9C,GACI,EAGR,eAAK,SAAS,EAAC,WAAW,aACxB,YAAG,SAAS,EAAC,qCAAqC,+BAAmB,EACrE,KAAC,KAAK,IAAC,KAAK,EAAC,kBAAkB,EAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,YACvD,gBACE,SAAS,EAAE,QAAQ,EACnB,KAAK,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,EACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EACpD,WAAW,EAAE,IAAI,CAAC,SAAS,IAAI,mCAAmC,GAClE,GACI,EACR,KAAC,KAAK,IAAC,KAAK,EAAC,wBAAwB,EAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,YACnE,mBACE,SAAS,EAAE,GAAG,QAAQ,oBAAoB,EAC1C,KAAK,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAC1D,WAAW,EAAE,IAAI,CAAC,eAAe,IAAI,yCAAyC,GAC9E,GACI,EACR,eAAK,SAAS,EAAC,iDAAiD,aAC7D,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;gCAChB,qDAAqD;gCACrD,cACE,GAAG,EAAE,MAAM,CAAC,OAAO,EACnB,GAAG,EAAE,sBAAsB,MAAM,CAAC,KAAK,EAAE,EACzC,SAAS,EAAC,gDAAgD,GAC1D,CACH,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,kGAAkG,gCAE3G,CACP,EACD,eAAK,SAAS,EAAC,KAAK,aAClB,YAAG,SAAS,EAAC,kDAAkD,YAC5D,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAC5C,EACJ,YAAG,SAAS,EAAC,8CAA8C,YACxD,IAAI,CAAC,OAAO,IAAI,KAAK,GACpB,EACJ,YAAG,SAAS,EAAC,4CAA4C,YACtD,IAAI,CAAC,aAAa,IAAI,IAAI,GACzB,IACA,IACF,IACF,EAGN,eAAK,SAAS,EAAC,+CAA+C,aAC5D,iBAAO,SAAS,EAAC,yCAAyC,aACxD,gBAAM,SAAS,EAAC,SAAS,aACvB,eAAM,SAAS,EAAC,2CAA2C,yBAAgB,EAC3E,eAAM,SAAS,EAAC,qCAAqC,oDAE9C,IACF,EACP,gBACE,IAAI,EAAC,UAAU,EACf,IAAI,EAAC,QAAQ,kBACC,CAAC,CAAC,IAAI,CAAC,OAAO,EAC5B,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EACvB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EACtD,SAAS,EAAC,wBAAwB,GAClC,IACI,EACR,iBAAO,SAAS,EAAC,yCAAyC,aACxD,gBAAM,SAAS,EAAC,SAAS,aACvB,eAAM,SAAS,EAAC,2CAA2C,0BAAiB,EAC5E,eAAM,SAAS,EAAC,qCAAqC,gEAE9C,IACF,EACP,gBACE,IAAI,EAAC,UAAU,EACf,IAAI,EAAC,QAAQ,kBACC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAC7B,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EACxB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EACvD,SAAS,EAAC,wBAAwB,GAClC,IACI,IACJ,EAGL,CAAC,GAAG,EAAE;oBACL,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;oBACvC,OAAO,CACL,eACE,SAAS,EAAE,wDAAwD,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,aAEzF,KAAC,KAAK,IAAC,SAAS,EAAC,SAAS,wBAAe,yBACtB,eAAM,SAAS,EAAC,aAAa,YAAE,MAAM,CAAC,QAAQ,GAAQ,QACxE,IAAI,CAAC,KAAK,SACP,CACP,CAAA;gBACH,CAAC,CAAC,EAAE,IACA,CACP,CAAC,CAAC,CAAC,IAAI,GACD,CACV,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function SeoIssueFixDrawer({ open, onOpenChange, issueId, onChanged, onNavigate, }: {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (open: boolean) => void;
|
|
4
|
+
issueId: string | null;
|
|
5
|
+
onChanged?: () => void;
|
|
6
|
+
onNavigate?: (path: string) => void;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=SeoIssueFixDrawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoIssueFixDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/seo/SeoIssueFixDrawer.tsx"],"names":[],"mappings":"AAyDA,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,UAAU,GACX,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,2CA8KA"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Sparkles, ArrowRight, ExternalLink, Image as ImageIcon, Gauge } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { fetchSeoIssue, resolveSeoIssue, ignoreSeoIssue, reopenSeoIssue, explainSeoIssue, } from '../../lib/seo-service.js';
|
|
7
|
+
import { Drawer } from './Drawer.js';
|
|
8
|
+
import { SeoLoading, SeoErrorState, severityMeta, btnPrimary, btnSecondary } from './primitives.js';
|
|
9
|
+
/** Map a fix action to a destination route within the admin. */
|
|
10
|
+
function fixRoute(issue) {
|
|
11
|
+
const ent = issue.entityType;
|
|
12
|
+
const id = issue.entityId;
|
|
13
|
+
switch (issue.fixActionType) {
|
|
14
|
+
case 'edit-metadata':
|
|
15
|
+
case 'edit-canonical':
|
|
16
|
+
case 'edit-og':
|
|
17
|
+
case 'edit-schema':
|
|
18
|
+
if (ent && id) {
|
|
19
|
+
return {
|
|
20
|
+
label: 'Edit content SEO',
|
|
21
|
+
path: `/seo?tab=content&entityType=${ent}&entityId=${encodeURIComponent(id)}`,
|
|
22
|
+
icon: _jsx(ArrowRight, { className: "h-4 w-4", "aria-hidden": true }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
case 'fix-broken-link':
|
|
27
|
+
case 'create-redirect':
|
|
28
|
+
return {
|
|
29
|
+
label: 'Open redirects',
|
|
30
|
+
path: '/seo?tab=redirects',
|
|
31
|
+
icon: _jsx(ArrowRight, { className: "h-4 w-4", "aria-hidden": true }),
|
|
32
|
+
};
|
|
33
|
+
case 'optimize-media':
|
|
34
|
+
return {
|
|
35
|
+
label: 'Open Media Center',
|
|
36
|
+
path: '/media',
|
|
37
|
+
icon: _jsx(ImageIcon, { className: "h-4 w-4", "aria-hidden": true }),
|
|
38
|
+
};
|
|
39
|
+
case 'improve-speed':
|
|
40
|
+
return {
|
|
41
|
+
label: 'Technical settings',
|
|
42
|
+
path: '/seo?tab=technical',
|
|
43
|
+
icon: _jsx(Gauge, { className: "h-4 w-4", "aria-hidden": true }),
|
|
44
|
+
};
|
|
45
|
+
default:
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function SeoIssueFixDrawer({ open, onOpenChange, issueId, onChanged, onNavigate, }) {
|
|
50
|
+
const [issue, setIssue] = useState(null);
|
|
51
|
+
const [loading, setLoading] = useState(false);
|
|
52
|
+
const [error, setError] = useState(null);
|
|
53
|
+
const [busy, setBusy] = useState(false);
|
|
54
|
+
const [explanation, setExplanation] = useState(null);
|
|
55
|
+
const [explaining, setExplaining] = useState(false);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!open || !issueId)
|
|
58
|
+
return;
|
|
59
|
+
let alive = true;
|
|
60
|
+
setLoading(true);
|
|
61
|
+
setError(null);
|
|
62
|
+
setExplanation(null);
|
|
63
|
+
fetchSeoIssue(issueId)
|
|
64
|
+
.then((i) => {
|
|
65
|
+
if (!alive)
|
|
66
|
+
return;
|
|
67
|
+
if (!i)
|
|
68
|
+
setError('Issue not found.');
|
|
69
|
+
else
|
|
70
|
+
setIssue(i);
|
|
71
|
+
})
|
|
72
|
+
.catch((e) => alive && setError(e instanceof Error ? e.message : 'Failed to load'))
|
|
73
|
+
.finally(() => alive && setLoading(false));
|
|
74
|
+
return () => {
|
|
75
|
+
alive = false;
|
|
76
|
+
};
|
|
77
|
+
}, [open, issueId]);
|
|
78
|
+
async function mutate(fn, successMsg) {
|
|
79
|
+
if (!issue)
|
|
80
|
+
return;
|
|
81
|
+
setBusy(true);
|
|
82
|
+
const res = await fn(issue.id);
|
|
83
|
+
setBusy(false);
|
|
84
|
+
if (res.error)
|
|
85
|
+
return toast.error(res.error);
|
|
86
|
+
toast.success(successMsg);
|
|
87
|
+
onOpenChange(false);
|
|
88
|
+
onChanged?.();
|
|
89
|
+
}
|
|
90
|
+
async function handleExplain() {
|
|
91
|
+
if (!issue)
|
|
92
|
+
return;
|
|
93
|
+
setExplaining(true);
|
|
94
|
+
const res = await explainSeoIssue(issue.id);
|
|
95
|
+
setExplaining(false);
|
|
96
|
+
if (res.unavailable) {
|
|
97
|
+
toast.error('AI is not configured.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setExplanation(res.text ?? 'No explanation available.');
|
|
101
|
+
}
|
|
102
|
+
const route = issue ? fixRoute(issue) : null;
|
|
103
|
+
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: "Issue detail", width: "max-w-lg", footer: issue ? (_jsxs(_Fragment, { children: [issue.status === 'ignored' ? (_jsx("button", { className: btnSecondary, onClick: () => mutate(reopenSeoIssue, 'Issue reopened.'), disabled: busy, children: "Reopen" })) : (_jsx("button", { className: btnSecondary, onClick: () => mutate(ignoreSeoIssue, 'Issue ignored.'), disabled: busy, children: "Ignore" })), route && onNavigate ? (_jsxs("button", { className: btnPrimary, onClick: () => {
|
|
104
|
+
onNavigate(route.path);
|
|
105
|
+
onOpenChange(false);
|
|
106
|
+
}, children: [route.icon, route.label] })) : (_jsx("button", { className: btnPrimary, onClick: () => mutate(resolveSeoIssue, 'Issue resolved.'), disabled: busy, children: "Mark resolved" }))] })) : null, children: loading ? (_jsx(SeoLoading, {})) : error ? (_jsx(SeoErrorState, { message: error })) : issue ? (_jsxs("div", { className: "space-y-4", children: [(() => {
|
|
107
|
+
const meta = severityMeta(issue.severity);
|
|
108
|
+
return (_jsxs("span", { className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${meta.bg} ${meta.text}`, children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${meta.dot}`, "aria-hidden": true }), meta.label, " \u00B7 ", issue.category] }));
|
|
109
|
+
})(), _jsxs("div", { children: [_jsx("h3", { className: "text-foreground text-base font-medium", children: issue.title }), issue.entityTitle && (_jsx("p", { className: "text-muted-foreground mt-0.5 text-sm", children: issue.entityTitle }))] }), issue.url && (_jsxs("a", { href: issue.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary inline-flex items-center gap-1 text-sm hover:underline", children: [issue.url, _jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true })] })), issue.description && _jsx("p", { className: "text-foreground text-sm", children: issue.description }), issue.impact && (_jsxs("div", { className: "border-border rounded-md border p-3", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium tracking-wide uppercase", children: "Impact" }), _jsx("p", { className: "text-foreground mt-1 text-sm", children: issue.impact })] })), issue.recommendation && (_jsxs("div", { className: "border-border rounded-md border p-3", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium tracking-wide uppercase", children: "Recommendation" }), _jsx("p", { className: "text-foreground mt-1 text-sm", children: issue.recommendation })] })), _jsxs("div", { className: "border-border rounded-md border p-3", children: [_jsxs("button", { onClick: handleExplain, disabled: explaining, className: "text-primary focus-visible:ring-ring inline-flex items-center gap-1.5 text-sm font-medium focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", children: [_jsx(Sparkles, { className: `h-4 w-4 ${explaining ? 'animate-pulse' : ''}`, "aria-hidden": true }), explaining ? 'Asking AI…' : 'Explain with AI'] }), explanation && _jsx("p", { className: "text-foreground mt-2 text-sm", children: explanation })] })] })) : null }));
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=SeoIssueFixDrawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoIssueFixDrawer.js","sourceRoot":"","sources":["../../../src/components/seo/SeoIssueFixDrawer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SEO UI primitives — token-only, accessible building blocks used across
|
|
3
|
+
* the five SEO tabs. No raw palette colours: status uses the theme's
|
|
4
|
+
* success / warning / destructive / muted tokens.
|
|
5
|
+
*/
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import type { SeoGrade, SeoSeverity } from '../../lib/seo-service.js';
|
|
8
|
+
export interface GradeMeta {
|
|
9
|
+
label: string;
|
|
10
|
+
text: string;
|
|
11
|
+
bg: string;
|
|
12
|
+
dot: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function gradeMeta(grade: SeoGrade): GradeMeta;
|
|
15
|
+
/** Grade band from a numeric 0-100 score. */
|
|
16
|
+
export declare function gradeFromScore(score: number): SeoGrade;
|
|
17
|
+
export declare function severityMeta(severity: SeoSeverity): GradeMeta;
|
|
18
|
+
export declare function SeoStatusBadge({ label, tone, }: {
|
|
19
|
+
label: string;
|
|
20
|
+
tone: 'good' | 'fair' | 'poor' | 'critical' | 'neutral';
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
/** A horizontal score bar (0-100), coloured by grade. */
|
|
23
|
+
export declare function SeoScoreBar({ score, className }: {
|
|
24
|
+
score: number;
|
|
25
|
+
className?: string;
|
|
26
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export declare function ScorePill({ score }: {
|
|
28
|
+
score: number;
|
|
29
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function SectionCard({ title, description, action, children, className, }: {
|
|
31
|
+
title?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
action?: ReactNode;
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export declare function SeoLoading({ label }: {
|
|
38
|
+
label?: string;
|
|
39
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function SeoEmptyState({ icon, title, description, action, }: {
|
|
41
|
+
icon?: ReactNode;
|
|
42
|
+
title: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
action?: ReactNode;
|
|
45
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
export declare function SeoErrorState({ message, onRetry }: {
|
|
47
|
+
message: string;
|
|
48
|
+
onRetry?: () => void;
|
|
49
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare const btnPrimary = "inline-flex items-center justify-center gap-2 rounded-md bg-primary px-4 py-2 text-base font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-60";
|
|
51
|
+
export declare const btnSecondary = "inline-flex items-center justify-center gap-2 rounded-md border border-border bg-card px-4 py-2 text-base font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-60";
|
|
52
|
+
export declare const btnGhostIcon = "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
53
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/components/seo/primitives.tsx"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAIrE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CAqBpD;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAKtD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,CAmB7D;AAID,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,GACL,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;CACxD,2CA4BA;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,SAAc,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,2CAoB3F;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,2CAUrD;AAID,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAc,GACf,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,2CAeA;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAkB,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,2CAOpE;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,2CAaA;AAED,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,2CAkB5F;AAID,eAAO,MAAM,UAAU,kQAC0O,CAAA;AAEjQ,eAAO,MAAM,YAAY,wQAC8O,CAAA;AAEvQ,eAAO,MAAM,YAAY,iNACuL,CAAA"}
|