@actuate-media/cms-admin 0.65.0 → 0.67.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +7 -4
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/inspector-pane.test.d.ts +2 -0
  6. package/dist/__tests__/components/inspector-pane.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/inspector-pane.test.js +31 -0
  8. package/dist/__tests__/components/inspector-pane.test.js.map +1 -0
  9. package/dist/__tests__/lib/pages-tree.test.d.ts +2 -0
  10. package/dist/__tests__/lib/pages-tree.test.d.ts.map +1 -0
  11. package/dist/__tests__/lib/pages-tree.test.js +173 -0
  12. package/dist/__tests__/lib/pages-tree.test.js.map +1 -0
  13. package/dist/__tests__/views/dashboard.test.js +7 -3
  14. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  15. package/dist/__tests__/views/media-seo-audit.render.test.d.ts +2 -0
  16. package/dist/__tests__/views/media-seo-audit.render.test.d.ts.map +1 -0
  17. package/dist/__tests__/views/media-seo-audit.render.test.js +102 -0
  18. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -0
  19. package/dist/__tests__/views/onboarding-block.render.test.d.ts +2 -0
  20. package/dist/__tests__/views/onboarding-block.render.test.d.ts.map +1 -0
  21. package/dist/__tests__/views/onboarding-block.render.test.js +146 -0
  22. package/dist/__tests__/views/onboarding-block.render.test.js.map +1 -0
  23. package/dist/__tests__/views/pages-list-view.test.js +77 -1
  24. package/dist/__tests__/views/pages-list-view.test.js.map +1 -1
  25. package/dist/actuate-admin.css +1 -1
  26. package/dist/components/script-tags/ScriptTagEditorPane.d.ts +26 -0
  27. package/dist/components/script-tags/ScriptTagEditorPane.d.ts.map +1 -0
  28. package/dist/components/script-tags/ScriptTagEditorPane.js +160 -0
  29. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -0
  30. package/dist/components/ui/InspectorPane.d.ts +51 -0
  31. package/dist/components/ui/InspectorPane.d.ts.map +1 -0
  32. package/dist/components/ui/InspectorPane.js +42 -0
  33. package/dist/components/ui/InspectorPane.js.map +1 -0
  34. package/dist/components/ui/RowActions.d.ts +19 -0
  35. package/dist/components/ui/RowActions.d.ts.map +1 -0
  36. package/dist/components/ui/RowActions.js +16 -0
  37. package/dist/components/ui/RowActions.js.map +1 -0
  38. package/dist/components/ui/index.d.ts +4 -0
  39. package/dist/components/ui/index.d.ts.map +1 -1
  40. package/dist/components/ui/index.js +2 -0
  41. package/dist/components/ui/index.js.map +1 -1
  42. package/dist/layout/Header.js +1 -1
  43. package/dist/layout/Header.js.map +1 -1
  44. package/dist/layout/Layout.js +8 -38
  45. package/dist/layout/Layout.js.map +1 -1
  46. package/dist/lib/page-editor-service.d.ts +11 -2
  47. package/dist/lib/page-editor-service.d.ts.map +1 -1
  48. package/dist/lib/page-editor-service.js +8 -2
  49. package/dist/lib/page-editor-service.js.map +1 -1
  50. package/dist/lib/pages-service.d.ts +9 -0
  51. package/dist/lib/pages-service.d.ts.map +1 -1
  52. package/dist/lib/pages-service.js +14 -0
  53. package/dist/lib/pages-service.js.map +1 -1
  54. package/dist/lib/pages-tree.d.ts +44 -0
  55. package/dist/lib/pages-tree.d.ts.map +1 -0
  56. package/dist/lib/pages-tree.js +106 -0
  57. package/dist/lib/pages-tree.js.map +1 -0
  58. package/dist/lib/posts-service.d.ts +11 -0
  59. package/dist/lib/posts-service.d.ts.map +1 -1
  60. package/dist/lib/posts-service.js +13 -0
  61. package/dist/lib/posts-service.js.map +1 -1
  62. package/dist/lib/seo-service.d.ts +3 -0
  63. package/dist/lib/seo-service.d.ts.map +1 -1
  64. package/dist/lib/seo-service.js.map +1 -1
  65. package/dist/views/Dashboard.d.ts.map +1 -1
  66. package/dist/views/Dashboard.js +40 -3
  67. package/dist/views/Dashboard.js.map +1 -1
  68. package/dist/views/MediaBrowser.d.ts.map +1 -1
  69. package/dist/views/MediaBrowser.js +55 -2
  70. package/dist/views/MediaBrowser.js.map +1 -1
  71. package/dist/views/MediaSeoAudit.d.ts +25 -0
  72. package/dist/views/MediaSeoAudit.d.ts.map +1 -0
  73. package/dist/views/MediaSeoAudit.js +97 -0
  74. package/dist/views/MediaSeoAudit.js.map +1 -0
  75. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  76. package/dist/views/Pages/PagesListView.js +145 -16
  77. package/dist/views/Pages/PagesListView.js.map +1 -1
  78. package/dist/views/Pages/PagesTreeTable.d.ts +35 -0
  79. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -0
  80. package/dist/views/Pages/PagesTreeTable.js +28 -0
  81. package/dist/views/Pages/PagesTreeTable.js.map +1 -0
  82. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  83. package/dist/views/Posts/PostsListView.js +66 -9
  84. package/dist/views/Posts/PostsListView.js.map +1 -1
  85. package/dist/views/ScriptTags.d.ts +5 -1
  86. package/dist/views/ScriptTags.d.ts.map +1 -1
  87. package/dist/views/ScriptTags.js +32 -7
  88. package/dist/views/ScriptTags.js.map +1 -1
  89. package/dist/views/dashboard/OnboardingBlock.d.ts +29 -0
  90. package/dist/views/dashboard/OnboardingBlock.d.ts.map +1 -0
  91. package/dist/views/dashboard/OnboardingBlock.js +71 -0
  92. package/dist/views/dashboard/OnboardingBlock.js.map +1 -0
  93. package/dist/views/page-editor/PageSectionEditor.d.ts +7 -1
  94. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  95. package/dist/views/page-editor/PageSectionEditor.js +3 -3
  96. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  97. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  98. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  99. package/dist/views/page-editor/PageSettingsModal.js +42 -3
  100. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  101. package/dist/views/seo/TechnicalTab.d.ts.map +1 -1
  102. package/dist/views/seo/TechnicalTab.js +15 -0
  103. package/dist/views/seo/TechnicalTab.js.map +1 -1
  104. package/package.json +3 -3
  105. package/src/AdminRoot.tsx +7 -3
  106. package/src/__tests__/components/inspector-pane.test.tsx +64 -0
  107. package/src/__tests__/lib/pages-tree.test.ts +197 -0
  108. package/src/__tests__/views/dashboard.test.tsx +7 -3
  109. package/src/__tests__/views/media-seo-audit.render.test.tsx +117 -0
  110. package/src/__tests__/views/onboarding-block.render.test.tsx +193 -0
  111. package/src/__tests__/views/pages-list-view.test.tsx +93 -1
  112. package/src/components/script-tags/ScriptTagEditorPane.tsx +474 -0
  113. package/src/components/ui/InspectorPane.tsx +129 -0
  114. package/src/components/ui/RowActions.tsx +50 -0
  115. package/src/components/ui/index.ts +12 -0
  116. package/src/layout/Header.tsx +2 -2
  117. package/src/layout/Layout.tsx +8 -80
  118. package/src/lib/page-editor-service.ts +16 -2
  119. package/src/lib/pages-service.ts +18 -0
  120. package/src/lib/pages-tree.ts +145 -0
  121. package/src/lib/posts-service.ts +26 -0
  122. package/src/lib/seo-service.ts +3 -0
  123. package/src/views/Dashboard.tsx +286 -214
  124. package/src/views/MediaBrowser.tsx +673 -512
  125. package/src/views/MediaSeoAudit.tsx +320 -0
  126. package/src/views/Pages/PagesListView.tsx +459 -185
  127. package/src/views/Pages/PagesTreeTable.tsx +339 -0
  128. package/src/views/Posts/PostsListView.tsx +135 -10
  129. package/src/views/ScriptTags.tsx +204 -129
  130. package/src/views/dashboard/OnboardingBlock.tsx +233 -0
  131. package/src/views/page-editor/PageSectionEditor.tsx +9 -2
  132. package/src/views/page-editor/PageSettingsModal.tsx +75 -2
  133. package/src/views/seo/TechnicalTab.tsx +15 -0
  134. package/dist/views/ScriptTagEditor.d.ts +0 -6
  135. package/dist/views/ScriptTagEditor.d.ts.map +0 -1
  136. package/dist/views/ScriptTagEditor.js +0 -111
  137. package/dist/views/ScriptTagEditor.js.map +0 -1
  138. package/src/views/ScriptTagEditor.tsx +0 -353
@@ -5,6 +5,7 @@ import { normalizeSlug, validateSlug } from '@actuate-media/cms-core/client'
5
5
  import { Modal } from '../../components/ui/Modal.js'
6
6
  import { Button } from '../../components/ui/Button.js'
7
7
  import { SeoCharCount } from '../../components/seo/SeoCharCount.js'
8
+ import { cmsApi } from '../../lib/api.js'
8
9
  import type { EditorPage } from '../../lib/page-editor-service.js'
9
10
 
10
11
  const SEO_TITLE_MIN = 30
@@ -26,7 +27,12 @@ const INPUT =
26
27
 
27
28
  const slugify = normalizeSlug
28
29
 
29
- /** Edits page-level fields: title, slug, path, and SEO. */
30
+ interface ParentOption {
31
+ id: string
32
+ title: string
33
+ }
34
+
35
+ /** Edits page-level fields: title, slug, path, parent page, and SEO. */
30
36
  export function PageSettingsModal({
31
37
  open,
32
38
  page,
@@ -37,21 +43,52 @@ export function PageSettingsModal({
37
43
  const [title, setTitle] = useState(page.title)
38
44
  const [slug, setSlug] = useState(page.slug)
39
45
  const [path, setPath] = useState(page.path)
46
+ const [parentPageId, setParentPageId] = useState(page.parentPageId ?? '')
40
47
  const [seoTitle, setSeoTitle] = useState(page.seoTitle)
41
48
  const [seoDescription, setSeoDescription] = useState(page.seoDescription)
42
49
  // Auto-derive slug/path from title until the user edits slug manually.
43
50
  const [slugTouched, setSlugTouched] = useState(Boolean(page.slug))
51
+ const [parentOptions, setParentOptions] = useState<ParentOption[] | null>(null)
44
52
 
45
53
  useEffect(() => {
46
54
  if (!open) return
47
55
  setTitle(page.title)
48
56
  setSlug(page.slug)
49
57
  setPath(page.path)
58
+ setParentPageId(page.parentPageId ?? '')
50
59
  setSeoTitle(page.seoTitle)
51
60
  setSeoDescription(page.seoDescription)
52
61
  setSlugTouched(Boolean(page.slug))
53
62
  }, [open, page])
54
63
 
64
+ // Load the parent-page choices lazily the first time the modal opens.
65
+ // The page itself is excluded (a page can't be its own parent); deeper
66
+ // cycle protection lives in the tree builder, which breaks bad chains.
67
+ useEffect(() => {
68
+ if (!open || parentOptions !== null) return
69
+ let cancelled = false
70
+ cmsApi<{ docs: Array<{ id: string; title: string | null }> }>(
71
+ '/collections/pages?pageSize=500&sort=updatedAt&order=desc',
72
+ )
73
+ .then((res) => {
74
+ if (cancelled) return
75
+ const docs = res.data?.docs ?? []
76
+ const options = docs
77
+ .filter((d) => d.id !== page.id)
78
+ .map((d) => ({ id: d.id, title: d.title ?? '(untitled)' }))
79
+ .sort((a, b) => a.title.localeCompare(b.title))
80
+ setParentOptions(options)
81
+ })
82
+ .catch(() => {
83
+ // Leave the picker in its loading state note; the rest of the
84
+ // settings form stays fully usable.
85
+ if (!cancelled) setParentOptions([])
86
+ })
87
+ return () => {
88
+ cancelled = true
89
+ }
90
+ }, [open, parentOptions, page.id])
91
+
55
92
  function handleTitle(next: string) {
56
93
  setTitle(next)
57
94
  if (!slugTouched) {
@@ -65,7 +102,14 @@ export function PageSettingsModal({
65
102
 
66
103
  function handleSave() {
67
104
  if (slugError) return
68
- onSave({ title, slug, path: path || (slug ? `/${slug}` : '/'), seoTitle, seoDescription })
105
+ onSave({
106
+ title,
107
+ slug,
108
+ path: path || (slug ? `/${slug}` : '/'),
109
+ parentPageId: parentPageId || null,
110
+ seoTitle,
111
+ seoDescription,
112
+ })
69
113
  onClose()
70
114
  }
71
115
 
@@ -135,6 +179,35 @@ export function PageSettingsModal({
135
179
  />
136
180
  </div>
137
181
  </div>
182
+ <div>
183
+ <label className={LABEL} htmlFor="ps-parent">
184
+ Parent page
185
+ </label>
186
+ <select
187
+ id="ps-parent"
188
+ className={INPUT}
189
+ value={parentPageId}
190
+ disabled={!canEdit || parentOptions === null}
191
+ onChange={(e) => setParentPageId(e.target.value)}
192
+ >
193
+ <option value="">None — top level</option>
194
+ {/* Keep a preselected parent (e.g. from "Add sub-page") valid
195
+ while the options load or if it's missing from the list. */}
196
+ {parentPageId && !(parentOptions ?? []).some((p) => p.id === parentPageId) && (
197
+ <option value={parentPageId}>
198
+ {parentOptions === null ? 'Loading…' : 'Current parent'}
199
+ </option>
200
+ )}
201
+ {(parentOptions ?? []).map((p) => (
202
+ <option key={p.id} value={p.id}>
203
+ {p.title}
204
+ </option>
205
+ ))}
206
+ </select>
207
+ <p className="text-muted-foreground mt-1 text-xs">
208
+ Nests this page under a parent in the Pages tree view.
209
+ </p>
210
+ </div>
138
211
  <div>
139
212
  <div className="mb-1 flex items-center justify-between">
140
213
  <label className={`${LABEL} mb-0`} htmlFor="ps-seo-title">
@@ -40,6 +40,21 @@ const CRAWL_TOGGLES: { key: keyof CrawlSettings; label: string; help: string }[]
40
40
  label: 'Include posts in sitemap',
41
41
  help: 'Add published posts to the sitemap.',
42
42
  },
43
+ {
44
+ key: 'sitemapEnabled',
45
+ label: 'Enable XML sitemap',
46
+ help: 'Serve the sitemap at /sitemap.xml.',
47
+ },
48
+ {
49
+ key: 'autoRegenerateSitemapOnPublish',
50
+ label: 'Auto-regenerate sitemap',
51
+ help: 'Rebuild the sitemap automatically when content is published.',
52
+ },
53
+ {
54
+ key: 'pingSearchEnginesOnPublish',
55
+ label: 'Ping search engines on publish',
56
+ help: 'Notify Google and Bing when the sitemap changes (production only).',
57
+ },
43
58
  ]
44
59
 
45
60
  function Toggle({
@@ -1,6 +0,0 @@
1
- export interface ScriptTagEditorProps {
2
- tagId?: string;
3
- onNavigate?: (path: string) => void;
4
- }
5
- export declare function ScriptTagEditor({ tagId, onNavigate }: ScriptTagEditorProps): import("react/jsx-runtime").JSX.Element;
6
- //# sourceMappingURL=ScriptTagEditor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ScriptTagEditor.d.ts","sourceRoot":"","sources":["../../src/views/ScriptTagEditor.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,oBAAoB,2CAkV1E"}
@@ -1,111 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { ArrowLeft, Loader2, AlertTriangle, Trash2, X, Plus } from 'lucide-react';
5
- import { toast } from 'sonner';
6
- import { EnabledSwitch } from '../components/EnabledSwitch.js';
7
- import { useApiData } from '../lib/useApiData.js';
8
- import { cmsApi } from '../lib/api.js';
9
- export function ScriptTagEditor({ tagId, onNavigate }) {
10
- const isNew = !tagId;
11
- const { data, loading, error } = useApiData(tagId ? `/script-tags` : null);
12
- const [name, setName] = useState('');
13
- const [code, setCode] = useState('');
14
- const [placement, setPlacement] = useState('head');
15
- const [scope, setScope] = useState('site');
16
- const [targetPaths, setTargetPaths] = useState([]);
17
- const [pathInput, setPathInput] = useState('');
18
- const [priority, setPriority] = useState(100);
19
- const [enabled, setEnabled] = useState(true);
20
- const [saving, setSaving] = useState(false);
21
- const [deleting, setDeleting] = useState(false);
22
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
23
- useEffect(() => {
24
- if (data && tagId) {
25
- const tags = Array.isArray(data) ? data : [];
26
- const tag = tags.find((t) => t.id === tagId);
27
- if (tag) {
28
- setName(tag.name ?? '');
29
- setCode(tag.code ?? '');
30
- setPlacement(tag.placement ?? 'head');
31
- setScope(tag.scope ?? 'site');
32
- setTargetPaths(tag.targetPaths ?? []);
33
- setPriority(tag.priority ?? 100);
34
- setEnabled(tag.enabled ?? true);
35
- }
36
- }
37
- }, [data, tagId]);
38
- const addPath = () => {
39
- const trimmed = pathInput.trim();
40
- if (!trimmed)
41
- return;
42
- const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
43
- if (!targetPaths.includes(normalized)) {
44
- setTargetPaths([...targetPaths, normalized]);
45
- }
46
- setPathInput('');
47
- };
48
- const removePath = (path) => {
49
- setTargetPaths(targetPaths.filter((p) => p !== path));
50
- };
51
- const handleSave = async () => {
52
- if (!name.trim()) {
53
- toast.error('Name is required');
54
- return;
55
- }
56
- if (!code.trim()) {
57
- toast.error('Code snippet is required');
58
- return;
59
- }
60
- if (scope !== 'site' && targetPaths.length === 0) {
61
- toast.error('At least one target path is required for this scope');
62
- return;
63
- }
64
- setSaving(true);
65
- const payload = {
66
- name: name.trim(),
67
- code,
68
- placement,
69
- scope,
70
- targetPaths: scope === 'site' ? [] : targetPaths,
71
- priority,
72
- enabled,
73
- };
74
- const res = isNew
75
- ? await cmsApi('/script-tags', { method: 'POST', body: JSON.stringify(payload) })
76
- : await cmsApi(`/script-tags/${tagId}`, { method: 'PUT', body: JSON.stringify(payload) });
77
- setSaving(false);
78
- if (res.error) {
79
- toast.error(res.error);
80
- }
81
- else {
82
- toast.success(isNew ? 'Tag created' : 'Tag updated');
83
- onNavigate?.('/script-tags');
84
- }
85
- };
86
- const handleDelete = async () => {
87
- if (!tagId)
88
- return;
89
- setDeleting(true);
90
- const res = await cmsApi(`/script-tags/${tagId}`, { method: 'DELETE' });
91
- setDeleting(false);
92
- if (res.error) {
93
- toast.error(res.error);
94
- }
95
- else {
96
- toast.success('Tag deleted');
97
- onNavigate?.('/script-tags');
98
- }
99
- };
100
- if (loading && !isNew) {
101
- return (_jsx("div", { className: "flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8", children: _jsx(Loader2, { className: "text-primary h-6 w-6 animate-spin" }) }));
102
- }
103
- return (_jsxs("div", { className: "max-w-3xl p-3 pr-6 sm:p-4 sm:pr-8", children: [error && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 mb-4 flex items-center gap-3 rounded-lg border p-3", children: [_jsx(AlertTriangle, { className: "text-destructive h-5 w-5 shrink-0" }), _jsx("span", { className: "text-destructive flex-1 text-sm", children: error })] })), _jsxs("div", { className: "mb-6", children: [_jsxs("button", { type: "button", onClick: () => onNavigate?.('/script-tags'), className: "text-muted-foreground hover:text-foreground mb-3 flex items-center gap-1.5 text-sm transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Script Tags"] }), _jsx("h1", { className: "text-foreground", children: isNew ? 'New Script Tag' : 'Edit Script Tag' })] }), _jsx("div", { className: "border-warning/30 bg-warning/10 mb-6 rounded-lg border p-3", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertTriangle, { className: "text-warning mt-0.5 h-4 w-4 shrink-0" }), _jsx("p", { className: "text-warning text-sm", children: "This code will run on public pages matching the scope below. Only add trusted code from verified sources (e.g. Google Analytics, Meta Pixel)." })] }) }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "border-border bg-card space-y-4 rounded-lg border p-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Name" }), _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "e.g. Google Tag Manager, Facebook Pixel", className: "border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Code" }), _jsx("textarea", { value: code, onChange: (e) => setCode(e.target.value), placeholder: "Paste your HTML/JavaScript snippet here...", rows: 10, className: "border-input focus:ring-ring w-full rounded-lg border px-3 py-2 font-mono text-sm focus:ring-2 focus:outline-none", spellCheck: false }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: ["Paste the full code snippet including ", '<script>', " tags."] })] })] }), _jsxs("div", { className: "border-border bg-card space-y-4 rounded-lg border p-4", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: "Placement & Scope" }), _jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Placement" }), _jsxs("select", { value: placement, onChange: (e) => setPlacement(e.target.value), className: "border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none", children: [_jsxs("option", { value: "head", children: ["Head \u2014 inside ", '<head>'] }), _jsxs("option", { value: "body_open", children: ["Body Open \u2014 right after ", '<body>'] }), _jsxs("option", { value: "body_close", children: ["Body Close \u2014 before ", '</body>'] })] }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: [placement === 'head' && 'Best for analytics scripts, meta tags, and custom CSS.', placement === 'body_open' && 'Best for GTM noscript tags and early-loading scripts.', placement === 'body_close' &&
104
- 'Best for chat widgets, deferred scripts, and tracking pixels.'] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Scope" }), _jsxs("select", { value: scope, onChange: (e) => setScope(e.target.value), className: "border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none", children: [_jsx("option", { value: "site", children: "Entire Website" }), _jsx("option", { value: "parents", children: "Specific Parent Pages (includes child pages)" }), _jsx("option", { value: "urls", children: "Specific URLs (exact match)" })] })] }), scope !== 'site' && (_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: scope === 'parents' ? 'Parent Paths' : 'URL Paths' }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", value: pathInput, onChange: (e) => setPathInput(e.target.value), onKeyDown: (e) => {
105
- if (e.key === 'Enter') {
106
- e.preventDefault();
107
- addPath();
108
- }
109
- }, placeholder: "/services", className: "border-input focus:ring-ring flex-1 rounded-lg border px-3 py-2 font-mono text-sm focus:ring-2 focus:outline-none" }), _jsx("button", { type: "button", onClick: addPath, className: "border-input hover:bg-muted rounded-lg border px-3 py-2 text-sm transition-colors", children: _jsx(Plus, { className: "h-4 w-4" }) })] }), scope === 'parents' && (_jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "This tag will also apply to all child pages under each path." })), targetPaths.length > 0 && (_jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: targetPaths.map((p) => (_jsxs("span", { className: "bg-muted text-foreground inline-flex items-center gap-1 rounded-full px-3 py-1 font-mono text-xs", children: [p, _jsx("button", { type: "button", onClick: () => removePath(p), className: "text-muted-foreground hover:text-muted-foreground ml-0.5", children: _jsx(X, { className: "h-3 w-3" }) })] }, p))) }))] })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Priority" }), _jsx("input", { type: "number", value: priority, onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), min: 0, className: "border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none" }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "Lower numbers load first (e.g. 1 = first, 100 = default)" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-sm font-medium", children: "Enabled" }), _jsx("div", { className: "pt-2", children: _jsx(EnabledSwitch, { checked: enabled, onCheckedChange: setEnabled, "aria-label": enabled ? 'Disable script tag' : 'Enable script tag' }) })] })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { children: !isNew && (_jsx(_Fragment, { children: showDeleteConfirm ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-destructive text-sm", children: "Delete this tag?" }), _jsx("button", { type: "button", onClick: handleDelete, disabled: deleting, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-lg px-3 py-1.5 text-sm disabled:opacity-50", children: deleting ? 'Deleting...' : 'Confirm' }), _jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(false), className: "border-input hover:bg-muted rounded-lg border px-3 py-1.5 text-sm", children: "Cancel" })] })) : (_jsxs("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "text-destructive hover:text-destructive flex items-center gap-1.5 text-sm transition-colors", children: [_jsx(Trash2, { className: "h-4 w-4" }), "Delete"] })) })) }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("button", { type: "button", onClick: () => onNavigate?.('/script-tags'), className: "border-input text-foreground hover:bg-muted rounded-lg border px-4 py-2 text-sm transition-colors", children: "Cancel" }), _jsx("button", { type: "button", onClick: handleSave, disabled: saving, className: "bg-primary text-primary-foreground hover:bg-primary/90 rounded-lg px-6 py-2 text-sm transition-colors disabled:opacity-50", children: saving ? 'Saving...' : isNew ? 'Create Tag' : 'Save Changes' })] })] })] })] }));
110
- }
111
- //# sourceMappingURL=ScriptTagEditor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ScriptTagEditor.js","sourceRoot":"","sources":["../../src/views/ScriptTagEditor.tsx"],"names":[],"mappings":"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"}
@@ -1,353 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useEffect } from 'react'
4
- import { ArrowLeft, Loader2, AlertTriangle, Trash2, X, Plus } from 'lucide-react'
5
- import { toast } from 'sonner'
6
- import { EnabledSwitch } from '../components/EnabledSwitch.js'
7
- import { useApiData } from '../lib/useApiData.js'
8
- import { cmsApi } from '../lib/api.js'
9
-
10
- export interface ScriptTagEditorProps {
11
- tagId?: string
12
- onNavigate?: (path: string) => void
13
- }
14
-
15
- export function ScriptTagEditor({ tagId, onNavigate }: ScriptTagEditorProps) {
16
- const isNew = !tagId
17
- const { data, loading, error } = useApiData<any>(tagId ? `/script-tags` : null)
18
-
19
- const [name, setName] = useState('')
20
- const [code, setCode] = useState('')
21
- const [placement, setPlacement] = useState('head')
22
- const [scope, setScope] = useState('site')
23
- const [targetPaths, setTargetPaths] = useState<string[]>([])
24
- const [pathInput, setPathInput] = useState('')
25
- const [priority, setPriority] = useState(100)
26
- const [enabled, setEnabled] = useState(true)
27
- const [saving, setSaving] = useState(false)
28
- const [deleting, setDeleting] = useState(false)
29
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
30
-
31
- useEffect(() => {
32
- if (data && tagId) {
33
- const tags = Array.isArray(data) ? data : []
34
- const tag = tags.find((t: any) => t.id === tagId)
35
- if (tag) {
36
- setName(tag.name ?? '')
37
- setCode(tag.code ?? '')
38
- setPlacement(tag.placement ?? 'head')
39
- setScope(tag.scope ?? 'site')
40
- setTargetPaths(tag.targetPaths ?? [])
41
- setPriority(tag.priority ?? 100)
42
- setEnabled(tag.enabled ?? true)
43
- }
44
- }
45
- }, [data, tagId])
46
-
47
- const addPath = () => {
48
- const trimmed = pathInput.trim()
49
- if (!trimmed) return
50
- const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
51
- if (!targetPaths.includes(normalized)) {
52
- setTargetPaths([...targetPaths, normalized])
53
- }
54
- setPathInput('')
55
- }
56
-
57
- const removePath = (path: string) => {
58
- setTargetPaths(targetPaths.filter((p) => p !== path))
59
- }
60
-
61
- const handleSave = async () => {
62
- if (!name.trim()) {
63
- toast.error('Name is required')
64
- return
65
- }
66
- if (!code.trim()) {
67
- toast.error('Code snippet is required')
68
- return
69
- }
70
- if (scope !== 'site' && targetPaths.length === 0) {
71
- toast.error('At least one target path is required for this scope')
72
- return
73
- }
74
-
75
- setSaving(true)
76
- const payload = {
77
- name: name.trim(),
78
- code,
79
- placement,
80
- scope,
81
- targetPaths: scope === 'site' ? [] : targetPaths,
82
- priority,
83
- enabled,
84
- }
85
-
86
- const res = isNew
87
- ? await cmsApi('/script-tags', { method: 'POST', body: JSON.stringify(payload) })
88
- : await cmsApi(`/script-tags/${tagId}`, { method: 'PUT', body: JSON.stringify(payload) })
89
-
90
- setSaving(false)
91
- if (res.error) {
92
- toast.error(res.error)
93
- } else {
94
- toast.success(isNew ? 'Tag created' : 'Tag updated')
95
- onNavigate?.('/script-tags')
96
- }
97
- }
98
-
99
- const handleDelete = async () => {
100
- if (!tagId) return
101
- setDeleting(true)
102
- const res = await cmsApi(`/script-tags/${tagId}`, { method: 'DELETE' })
103
- setDeleting(false)
104
- if (res.error) {
105
- toast.error(res.error)
106
- } else {
107
- toast.success('Tag deleted')
108
- onNavigate?.('/script-tags')
109
- }
110
- }
111
-
112
- if (loading && !isNew) {
113
- return (
114
- <div className="flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8">
115
- <Loader2 className="text-primary h-6 w-6 animate-spin" />
116
- </div>
117
- )
118
- }
119
-
120
- return (
121
- <div className="max-w-3xl p-3 pr-6 sm:p-4 sm:pr-8">
122
- {error && (
123
- <div className="border-destructive/30 bg-destructive/10 mb-4 flex items-center gap-3 rounded-lg border p-3">
124
- <AlertTriangle className="text-destructive h-5 w-5 shrink-0" />
125
- <span className="text-destructive flex-1 text-sm">{error}</span>
126
- </div>
127
- )}
128
-
129
- <div className="mb-6">
130
- <button
131
- type="button"
132
- onClick={() => onNavigate?.('/script-tags')}
133
- className="text-muted-foreground hover:text-foreground mb-3 flex items-center gap-1.5 text-sm transition-colors"
134
- >
135
- <ArrowLeft className="h-4 w-4" />
136
- Back to Script Tags
137
- </button>
138
- <h1 className="text-foreground">{isNew ? 'New Script Tag' : 'Edit Script Tag'}</h1>
139
- </div>
140
-
141
- <div className="border-warning/30 bg-warning/10 mb-6 rounded-lg border p-3">
142
- <div className="flex items-start gap-2">
143
- <AlertTriangle className="text-warning mt-0.5 h-4 w-4 shrink-0" />
144
- <p className="text-warning text-sm">
145
- This code will run on public pages matching the scope below. Only add trusted code from
146
- verified sources (e.g. Google Analytics, Meta Pixel).
147
- </p>
148
- </div>
149
- </div>
150
-
151
- <div className="space-y-6">
152
- <div className="border-border bg-card space-y-4 rounded-lg border p-4">
153
- <div>
154
- <label className="text-foreground mb-1 block text-sm font-medium">Name</label>
155
- <input
156
- type="text"
157
- value={name}
158
- onChange={(e) => setName(e.target.value)}
159
- placeholder="e.g. Google Tag Manager, Facebook Pixel"
160
- className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
161
- />
162
- </div>
163
-
164
- <div>
165
- <label className="text-foreground mb-1 block text-sm font-medium">Code</label>
166
- <textarea
167
- value={code}
168
- onChange={(e) => setCode(e.target.value)}
169
- placeholder="Paste your HTML/JavaScript snippet here..."
170
- rows={10}
171
- className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 font-mono text-sm focus:ring-2 focus:outline-none"
172
- spellCheck={false}
173
- />
174
- <p className="text-muted-foreground mt-1 text-xs">
175
- Paste the full code snippet including {'<script>'} tags.
176
- </p>
177
- </div>
178
- </div>
179
-
180
- <div className="border-border bg-card space-y-4 rounded-lg border p-4">
181
- <h3 className="text-foreground text-sm font-medium">Placement & Scope</h3>
182
-
183
- <div>
184
- <label className="text-foreground mb-1 block text-sm font-medium">Placement</label>
185
- <select
186
- value={placement}
187
- onChange={(e) => setPlacement(e.target.value)}
188
- className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
189
- >
190
- <option value="head">Head — inside {'<head>'}</option>
191
- <option value="body_open">Body Open — right after {'<body>'}</option>
192
- <option value="body_close">Body Close — before {'</body>'}</option>
193
- </select>
194
- <p className="text-muted-foreground mt-1 text-xs">
195
- {placement === 'head' && 'Best for analytics scripts, meta tags, and custom CSS.'}
196
- {placement === 'body_open' && 'Best for GTM noscript tags and early-loading scripts.'}
197
- {placement === 'body_close' &&
198
- 'Best for chat widgets, deferred scripts, and tracking pixels.'}
199
- </p>
200
- </div>
201
-
202
- <div>
203
- <label className="text-foreground mb-1 block text-sm font-medium">Scope</label>
204
- <select
205
- value={scope}
206
- onChange={(e) => setScope(e.target.value)}
207
- className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
208
- >
209
- <option value="site">Entire Website</option>
210
- <option value="parents">Specific Parent Pages (includes child pages)</option>
211
- <option value="urls">Specific URLs (exact match)</option>
212
- </select>
213
- </div>
214
-
215
- {scope !== 'site' && (
216
- <div>
217
- <label className="text-foreground mb-1 block text-sm font-medium">
218
- {scope === 'parents' ? 'Parent Paths' : 'URL Paths'}
219
- </label>
220
- <div className="flex gap-2">
221
- <input
222
- type="text"
223
- value={pathInput}
224
- onChange={(e) => setPathInput(e.target.value)}
225
- onKeyDown={(e) => {
226
- if (e.key === 'Enter') {
227
- e.preventDefault()
228
- addPath()
229
- }
230
- }}
231
- placeholder="/services"
232
- className="border-input focus:ring-ring flex-1 rounded-lg border px-3 py-2 font-mono text-sm focus:ring-2 focus:outline-none"
233
- />
234
- <button
235
- type="button"
236
- onClick={addPath}
237
- className="border-input hover:bg-muted rounded-lg border px-3 py-2 text-sm transition-colors"
238
- >
239
- <Plus className="h-4 w-4" />
240
- </button>
241
- </div>
242
- {scope === 'parents' && (
243
- <p className="text-muted-foreground mt-1 text-xs">
244
- This tag will also apply to all child pages under each path.
245
- </p>
246
- )}
247
- {targetPaths.length > 0 && (
248
- <div className="mt-2 flex flex-wrap gap-2">
249
- {targetPaths.map((p) => (
250
- <span
251
- key={p}
252
- className="bg-muted text-foreground inline-flex items-center gap-1 rounded-full px-3 py-1 font-mono text-xs"
253
- >
254
- {p}
255
- <button
256
- type="button"
257
- onClick={() => removePath(p)}
258
- className="text-muted-foreground hover:text-muted-foreground ml-0.5"
259
- >
260
- <X className="h-3 w-3" />
261
- </button>
262
- </span>
263
- ))}
264
- </div>
265
- )}
266
- </div>
267
- )}
268
-
269
- <div className="grid grid-cols-2 gap-4">
270
- <div>
271
- <label className="text-foreground mb-1 block text-sm font-medium">Priority</label>
272
- <input
273
- type="number"
274
- value={priority}
275
- onChange={(e) => setPriority(parseInt(e.target.value, 10) || 0)}
276
- min={0}
277
- className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
278
- />
279
- <p className="text-muted-foreground mt-1 text-xs">
280
- Lower numbers load first (e.g. 1 = first, 100 = default)
281
- </p>
282
- </div>
283
- <div>
284
- <label className="text-foreground mb-1 block text-sm font-medium">Enabled</label>
285
- <div className="pt-2">
286
- <EnabledSwitch
287
- checked={enabled}
288
- onCheckedChange={setEnabled}
289
- aria-label={enabled ? 'Disable script tag' : 'Enable script tag'}
290
- />
291
- </div>
292
- </div>
293
- </div>
294
- </div>
295
-
296
- <div className="flex items-center justify-between">
297
- <div>
298
- {!isNew && (
299
- <>
300
- {showDeleteConfirm ? (
301
- <div className="flex items-center gap-2">
302
- <span className="text-destructive text-sm">Delete this tag?</span>
303
- <button
304
- type="button"
305
- onClick={handleDelete}
306
- disabled={deleting}
307
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-lg px-3 py-1.5 text-sm disabled:opacity-50"
308
- >
309
- {deleting ? 'Deleting...' : 'Confirm'}
310
- </button>
311
- <button
312
- type="button"
313
- onClick={() => setShowDeleteConfirm(false)}
314
- className="border-input hover:bg-muted rounded-lg border px-3 py-1.5 text-sm"
315
- >
316
- Cancel
317
- </button>
318
- </div>
319
- ) : (
320
- <button
321
- type="button"
322
- onClick={() => setShowDeleteConfirm(true)}
323
- className="text-destructive hover:text-destructive flex items-center gap-1.5 text-sm transition-colors"
324
- >
325
- <Trash2 className="h-4 w-4" />
326
- Delete
327
- </button>
328
- )}
329
- </>
330
- )}
331
- </div>
332
- <div className="flex items-center gap-3">
333
- <button
334
- type="button"
335
- onClick={() => onNavigate?.('/script-tags')}
336
- className="border-input text-foreground hover:bg-muted rounded-lg border px-4 py-2 text-sm transition-colors"
337
- >
338
- Cancel
339
- </button>
340
- <button
341
- type="button"
342
- onClick={handleSave}
343
- disabled={saving}
344
- className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-lg px-6 py-2 text-sm transition-colors disabled:opacity-50"
345
- >
346
- {saving ? 'Saving...' : isNew ? 'Create Tag' : 'Save Changes'}
347
- </button>
348
- </div>
349
- </div>
350
- </div>
351
- </div>
352
- )
353
- }