@actuate-media/cms-admin 0.66.0 → 0.68.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 (244) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +36 -9
  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__/hooks/useIdleLogout.test.d.ts +2 -0
  10. package/dist/__tests__/hooks/useIdleLogout.test.d.ts.map +1 -0
  11. package/dist/__tests__/hooks/useIdleLogout.test.js +64 -0
  12. package/dist/__tests__/hooks/useIdleLogout.test.js.map +1 -0
  13. package/dist/__tests__/lib/pages-tree.test.d.ts +2 -0
  14. package/dist/__tests__/lib/pages-tree.test.d.ts.map +1 -0
  15. package/dist/__tests__/lib/pages-tree.test.js +173 -0
  16. package/dist/__tests__/lib/pages-tree.test.js.map +1 -0
  17. package/dist/__tests__/lib/post-types-service.test.d.ts +2 -0
  18. package/dist/__tests__/lib/post-types-service.test.d.ts.map +1 -0
  19. package/dist/__tests__/lib/post-types-service.test.js +88 -0
  20. package/dist/__tests__/lib/post-types-service.test.js.map +1 -0
  21. package/dist/__tests__/views/collection-list-nav.render.test.js +43 -5
  22. package/dist/__tests__/views/collection-list-nav.render.test.js.map +1 -1
  23. package/dist/__tests__/views/dashboard.test.js +7 -3
  24. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  25. package/dist/__tests__/views/forms-list.render.test.js +25 -6
  26. package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
  27. package/dist/__tests__/views/media-seo-audit.render.test.d.ts +2 -0
  28. package/dist/__tests__/views/media-seo-audit.render.test.d.ts.map +1 -0
  29. package/dist/__tests__/views/media-seo-audit.render.test.js +102 -0
  30. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -0
  31. package/dist/__tests__/views/onboarding-block.render.test.d.ts +2 -0
  32. package/dist/__tests__/views/onboarding-block.render.test.d.ts.map +1 -0
  33. package/dist/__tests__/views/onboarding-block.render.test.js +146 -0
  34. package/dist/__tests__/views/onboarding-block.render.test.js.map +1 -0
  35. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +2 -0
  36. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +121 -0
  38. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +1 -0
  39. package/dist/__tests__/views/page-templates.render.test.d.ts +2 -0
  40. package/dist/__tests__/views/page-templates.render.test.d.ts.map +1 -0
  41. package/dist/__tests__/views/page-templates.render.test.js +105 -0
  42. package/dist/__tests__/views/page-templates.render.test.js.map +1 -0
  43. package/dist/__tests__/views/pages-list-view.test.js +77 -1
  44. package/dist/__tests__/views/pages-list-view.test.js.map +1 -1
  45. package/dist/__tests__/views/post-type-editor.render.test.d.ts +2 -0
  46. package/dist/__tests__/views/post-type-editor.render.test.d.ts.map +1 -0
  47. package/dist/__tests__/views/post-type-editor.render.test.js +102 -0
  48. package/dist/__tests__/views/post-type-editor.render.test.js.map +1 -0
  49. package/dist/__tests__/views/security-settings.render.test.js +85 -4
  50. package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
  51. package/dist/__tests__/views/seo-content-pane.render.test.d.ts +2 -0
  52. package/dist/__tests__/views/seo-content-pane.render.test.d.ts.map +1 -0
  53. package/dist/__tests__/views/seo-content-pane.render.test.js +117 -0
  54. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -0
  55. package/dist/actuate-admin.css +1 -1
  56. package/dist/components/collections/EntryDetailPane.d.ts +10 -0
  57. package/dist/components/collections/EntryDetailPane.d.ts.map +1 -0
  58. package/dist/components/collections/EntryDetailPane.js +92 -0
  59. package/dist/components/collections/EntryDetailPane.js.map +1 -0
  60. package/dist/components/forms/FieldsPanel.js +1 -1
  61. package/dist/components/forms/FieldsPanel.js.map +1 -1
  62. package/dist/components/forms/FormSchemaPane.d.ts +8 -0
  63. package/dist/components/forms/FormSchemaPane.d.ts.map +1 -0
  64. package/dist/components/forms/FormSchemaPane.js +95 -0
  65. package/dist/components/forms/FormSchemaPane.js.map +1 -0
  66. package/dist/components/script-tags/ScriptTagEditorPane.d.ts +26 -0
  67. package/dist/components/script-tags/ScriptTagEditorPane.d.ts.map +1 -0
  68. package/dist/components/script-tags/ScriptTagEditorPane.js +160 -0
  69. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -0
  70. package/dist/components/seo/SeoEditorPane.d.ts +9 -0
  71. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -0
  72. package/dist/components/seo/SeoEditorPane.js +175 -0
  73. package/dist/components/seo/SeoEditorPane.js.map +1 -0
  74. package/dist/components/ui/Badge.d.ts +1 -1
  75. package/dist/components/ui/Button.d.ts +1 -1
  76. package/dist/components/ui/InspectorPane.d.ts +51 -0
  77. package/dist/components/ui/InspectorPane.d.ts.map +1 -0
  78. package/dist/components/ui/InspectorPane.js +42 -0
  79. package/dist/components/ui/InspectorPane.js.map +1 -0
  80. package/dist/components/ui/StatusBadge.d.ts +1 -1
  81. package/dist/components/ui/index.d.ts +2 -0
  82. package/dist/components/ui/index.d.ts.map +1 -1
  83. package/dist/components/ui/index.js +1 -0
  84. package/dist/components/ui/index.js.map +1 -1
  85. package/dist/hooks/useIdleLogout.d.ts +18 -0
  86. package/dist/hooks/useIdleLogout.d.ts.map +1 -0
  87. package/dist/hooks/useIdleLogout.js +66 -0
  88. package/dist/hooks/useIdleLogout.js.map +1 -0
  89. package/dist/lib/api.d.ts +1 -0
  90. package/dist/lib/api.d.ts.map +1 -1
  91. package/dist/lib/api.js +7 -1
  92. package/dist/lib/api.js.map +1 -1
  93. package/dist/lib/page-editor-service.d.ts +11 -2
  94. package/dist/lib/page-editor-service.d.ts.map +1 -1
  95. package/dist/lib/page-editor-service.js +8 -2
  96. package/dist/lib/page-editor-service.js.map +1 -1
  97. package/dist/lib/pages-tree.d.ts +44 -0
  98. package/dist/lib/pages-tree.d.ts.map +1 -0
  99. package/dist/lib/pages-tree.js +106 -0
  100. package/dist/lib/pages-tree.js.map +1 -0
  101. package/dist/lib/post-types-service.d.ts +44 -0
  102. package/dist/lib/post-types-service.d.ts.map +1 -0
  103. package/dist/lib/post-types-service.js +83 -0
  104. package/dist/lib/post-types-service.js.map +1 -0
  105. package/dist/views/CollectionList.d.ts.map +1 -1
  106. package/dist/views/CollectionList.js +23 -4
  107. package/dist/views/CollectionList.js.map +1 -1
  108. package/dist/views/Dashboard.d.ts.map +1 -1
  109. package/dist/views/Dashboard.js +31 -2
  110. package/dist/views/Dashboard.js.map +1 -1
  111. package/dist/views/Forms.d.ts.map +1 -1
  112. package/dist/views/Forms.js +25 -21
  113. package/dist/views/Forms.js.map +1 -1
  114. package/dist/views/MediaBrowser.d.ts.map +1 -1
  115. package/dist/views/MediaBrowser.js +28 -2
  116. package/dist/views/MediaBrowser.js.map +1 -1
  117. package/dist/views/MediaSeoAudit.d.ts +25 -0
  118. package/dist/views/MediaSeoAudit.d.ts.map +1 -0
  119. package/dist/views/MediaSeoAudit.js +97 -0
  120. package/dist/views/MediaSeoAudit.js.map +1 -0
  121. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  122. package/dist/views/Pages/PagesListView.js +90 -10
  123. package/dist/views/Pages/PagesListView.js.map +1 -1
  124. package/dist/views/Pages/PagesTreeTable.d.ts +35 -0
  125. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -0
  126. package/dist/views/Pages/PagesTreeTable.js +28 -0
  127. package/dist/views/Pages/PagesTreeTable.js.map +1 -0
  128. package/dist/views/Posts/PostTypeEditor.d.ts +16 -0
  129. package/dist/views/Posts/PostTypeEditor.d.ts.map +1 -0
  130. package/dist/views/Posts/PostTypeEditor.js +159 -0
  131. package/dist/views/Posts/PostTypeEditor.js.map +1 -0
  132. package/dist/views/Posts/PostTypesView.d.ts +3 -2
  133. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  134. package/dist/views/Posts/PostTypesView.js +1 -1
  135. package/dist/views/Posts/PostTypesView.js.map +1 -1
  136. package/dist/views/ScriptTags.d.ts +5 -1
  137. package/dist/views/ScriptTags.d.ts.map +1 -1
  138. package/dist/views/ScriptTags.js +32 -7
  139. package/dist/views/ScriptTags.js.map +1 -1
  140. package/dist/views/dashboard/OnboardingBlock.d.ts +29 -0
  141. package/dist/views/dashboard/OnboardingBlock.d.ts.map +1 -0
  142. package/dist/views/dashboard/OnboardingBlock.js +71 -0
  143. package/dist/views/dashboard/OnboardingBlock.js.map +1 -0
  144. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +20 -0
  145. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +1 -0
  146. package/dist/views/page-builder/BuilderSectionsPanel.js +105 -0
  147. package/dist/views/page-builder/BuilderSectionsPanel.js.map +1 -0
  148. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  149. package/dist/views/page-builder/PageBuilder.js +51 -2
  150. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  151. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  152. package/dist/views/page-builder/PageTemplates.js +113 -6
  153. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  154. package/dist/views/page-editor/PageSectionEditor.d.ts +7 -1
  155. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  156. package/dist/views/page-editor/PageSectionEditor.js +3 -3
  157. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  158. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  159. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  160. package/dist/views/page-editor/PageSettingsModal.js +42 -3
  161. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  162. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  163. package/dist/views/seo/ContentTab.js +22 -6
  164. package/dist/views/seo/ContentTab.js.map +1 -1
  165. package/dist/views/settings/AccessProtectionCard.d.ts +5 -1
  166. package/dist/views/settings/AccessProtectionCard.d.ts.map +1 -1
  167. package/dist/views/settings/AccessProtectionCard.js +13 -10
  168. package/dist/views/settings/AccessProtectionCard.js.map +1 -1
  169. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  170. package/dist/views/settings/SecurityTab.js +3 -3
  171. package/dist/views/settings/SecurityTab.js.map +1 -1
  172. package/dist/views/settings/SessionPolicyCard.d.ts +1 -2
  173. package/dist/views/settings/SessionPolicyCard.d.ts.map +1 -1
  174. package/dist/views/settings/SessionPolicyCard.js +12 -5
  175. package/dist/views/settings/SessionPolicyCard.js.map +1 -1
  176. package/dist/views/settings/useSecuritySettings.d.ts +13 -1
  177. package/dist/views/settings/useSecuritySettings.d.ts.map +1 -1
  178. package/dist/views/settings/useSecuritySettings.js +47 -4
  179. package/dist/views/settings/useSecuritySettings.js.map +1 -1
  180. package/package.json +2 -2
  181. package/src/AdminRoot.tsx +43 -10
  182. package/src/__tests__/components/inspector-pane.test.tsx +64 -0
  183. package/src/__tests__/hooks/useIdleLogout.test.tsx +86 -0
  184. package/src/__tests__/lib/pages-tree.test.ts +197 -0
  185. package/src/__tests__/lib/post-types-service.test.ts +112 -0
  186. package/src/__tests__/views/collection-list-nav.render.test.tsx +51 -5
  187. package/src/__tests__/views/dashboard.test.tsx +7 -3
  188. package/src/__tests__/views/forms-list.render.test.tsx +34 -6
  189. package/src/__tests__/views/media-seo-audit.render.test.tsx +117 -0
  190. package/src/__tests__/views/onboarding-block.render.test.tsx +193 -0
  191. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +138 -0
  192. package/src/__tests__/views/page-templates.render.test.tsx +142 -0
  193. package/src/__tests__/views/pages-list-view.test.tsx +93 -1
  194. package/src/__tests__/views/post-type-editor.render.test.tsx +116 -0
  195. package/src/__tests__/views/security-settings.render.test.tsx +111 -4
  196. package/src/__tests__/views/seo-content-pane.render.test.tsx +142 -0
  197. package/src/components/collections/EntryDetailPane.tsx +182 -0
  198. package/src/components/forms/FieldsPanel.tsx +3 -1
  199. package/src/components/forms/FormSchemaPane.tsx +192 -0
  200. package/src/components/script-tags/ScriptTagEditorPane.tsx +474 -0
  201. package/src/components/seo/SeoEditorPane.tsx +533 -0
  202. package/src/components/ui/InspectorPane.tsx +129 -0
  203. package/src/components/ui/index.ts +10 -0
  204. package/src/hooks/useIdleLogout.ts +77 -0
  205. package/src/lib/api.ts +8 -2
  206. package/src/lib/page-editor-service.ts +16 -2
  207. package/src/lib/pages-tree.ts +145 -0
  208. package/src/lib/post-types-service.ts +129 -0
  209. package/src/views/CollectionList.tsx +158 -110
  210. package/src/views/Dashboard.tsx +238 -196
  211. package/src/views/Forms.tsx +154 -126
  212. package/src/views/MediaBrowser.tsx +641 -511
  213. package/src/views/MediaSeoAudit.tsx +320 -0
  214. package/src/views/Pages/PagesListView.tsx +352 -183
  215. package/src/views/Pages/PagesTreeTable.tsx +339 -0
  216. package/src/views/Posts/PostTypeEditor.tsx +568 -0
  217. package/src/views/Posts/PostTypesView.tsx +5 -4
  218. package/src/views/ScriptTags.tsx +204 -129
  219. package/src/views/dashboard/OnboardingBlock.tsx +233 -0
  220. package/src/views/page-builder/BuilderSectionsPanel.tsx +304 -0
  221. package/src/views/page-builder/PageBuilder.tsx +69 -2
  222. package/src/views/page-builder/PageTemplates.tsx +340 -57
  223. package/src/views/page-editor/PageSectionEditor.tsx +9 -2
  224. package/src/views/page-editor/PageSettingsModal.tsx +75 -2
  225. package/src/views/seo/ContentTab.tsx +113 -80
  226. package/src/views/settings/AccessProtectionCard.tsx +83 -53
  227. package/src/views/settings/SecurityTab.tsx +23 -2
  228. package/src/views/settings/SessionPolicyCard.tsx +45 -9
  229. package/src/views/settings/useSecuritySettings.ts +78 -22
  230. package/dist/components/forms/FormSchemaDrawer.d.ts +0 -9
  231. package/dist/components/forms/FormSchemaDrawer.d.ts.map +0 -1
  232. package/dist/components/forms/FormSchemaDrawer.js +0 -99
  233. package/dist/components/forms/FormSchemaDrawer.js.map +0 -1
  234. package/dist/components/seo/SeoEditorDrawer.d.ts +0 -8
  235. package/dist/components/seo/SeoEditorDrawer.d.ts.map +0 -1
  236. package/dist/components/seo/SeoEditorDrawer.js +0 -172
  237. package/dist/components/seo/SeoEditorDrawer.js.map +0 -1
  238. package/dist/views/ScriptTagEditor.d.ts +0 -6
  239. package/dist/views/ScriptTagEditor.d.ts.map +0 -1
  240. package/dist/views/ScriptTagEditor.js +0 -111
  241. package/dist/views/ScriptTagEditor.js.map +0 -1
  242. package/src/components/forms/FormSchemaDrawer.tsx +0 -192
  243. package/src/components/seo/SeoEditorDrawer.tsx +0 -529
  244. package/src/views/ScriptTagEditor.tsx +0 -353
@@ -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
- }