@actuate-media/cms-admin 0.69.0 → 0.69.1

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 (120) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/__tests__/views/form-entries.render.test.js +14 -4
  3. package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
  4. package/dist/__tests__/views/form-submissions.render.test.js +14 -4
  5. package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
  6. package/dist/__tests__/views/media-seo-audit.render.test.js +26 -0
  7. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -1
  8. package/dist/__tests__/views/redirects-pane.render.test.d.ts +2 -0
  9. package/dist/__tests__/views/redirects-pane.render.test.d.ts.map +1 -0
  10. package/dist/__tests__/views/redirects-pane.render.test.js +121 -0
  11. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -0
  12. package/dist/actuate-admin.css +1 -1
  13. package/dist/components/SEOPanel.js +3 -3
  14. package/dist/components/SEOPanel.js.map +1 -1
  15. package/dist/components/collections/EntryDetailPane.js +1 -1
  16. package/dist/components/collections/EntryDetailPane.js.map +1 -1
  17. package/dist/components/forms/EntriesTable.d.ts +3 -1
  18. package/dist/components/forms/EntriesTable.d.ts.map +1 -1
  19. package/dist/components/forms/EntriesTable.js +5 -2
  20. package/dist/components/forms/EntriesTable.js.map +1 -1
  21. package/dist/components/forms/FormEntryDetailPane.d.ts +10 -0
  22. package/dist/components/forms/FormEntryDetailPane.d.ts.map +1 -0
  23. package/dist/components/forms/FormEntryDetailPane.js +137 -0
  24. package/dist/components/forms/FormEntryDetailPane.js.map +1 -0
  25. package/dist/components/forms/FormSchemaPane.js +1 -1
  26. package/dist/components/forms/FormSchemaPane.js.map +1 -1
  27. package/dist/components/script-tags/ScriptTagEditorPane.js +1 -1
  28. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -1
  29. package/dist/components/seo/RedirectEditorPane.d.ts +12 -0
  30. package/dist/components/seo/RedirectEditorPane.d.ts.map +1 -0
  31. package/dist/components/seo/RedirectEditorPane.js +89 -0
  32. package/dist/components/seo/RedirectEditorPane.js.map +1 -0
  33. package/dist/components/seo/SeoEditorPane.js +1 -1
  34. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  35. package/dist/components/ui/DataTable.js +2 -2
  36. package/dist/components/ui/DataTable.js.map +1 -1
  37. package/dist/components/ui/Skeleton.js +3 -3
  38. package/dist/components/ui/Skeleton.js.map +1 -1
  39. package/dist/components/ui/Toggle.js +1 -1
  40. package/dist/components/ui/Toggle.js.map +1 -1
  41. package/dist/fields/BlockBuilderField.js +1 -1
  42. package/dist/fields/BlockBuilderField.js.map +1 -1
  43. package/dist/fields/MediaField.js +1 -1
  44. package/dist/fields/MediaField.js.map +1 -1
  45. package/dist/fields/PropInput.js +1 -1
  46. package/dist/fields/PropInput.js.map +1 -1
  47. package/dist/layout/Header.js +1 -1
  48. package/dist/layout/Header.js.map +1 -1
  49. package/dist/views/DocumentEdit.js +1 -1
  50. package/dist/views/DocumentEdit.js.map +1 -1
  51. package/dist/views/FormEntries.d.ts.map +1 -1
  52. package/dist/views/FormEntries.js +18 -14
  53. package/dist/views/FormEntries.js.map +1 -1
  54. package/dist/views/FormSubmissions.d.ts.map +1 -1
  55. package/dist/views/FormSubmissions.js +18 -15
  56. package/dist/views/FormSubmissions.js.map +1 -1
  57. package/dist/views/Forms.js +5 -5
  58. package/dist/views/Forms.js.map +1 -1
  59. package/dist/views/MediaBrowser.d.ts.map +1 -1
  60. package/dist/views/MediaBrowser.js +1 -1
  61. package/dist/views/MediaBrowser.js.map +1 -1
  62. package/dist/views/MediaSeoAudit.d.ts +4 -1
  63. package/dist/views/MediaSeoAudit.d.ts.map +1 -1
  64. package/dist/views/MediaSeoAudit.js +12 -2
  65. package/dist/views/MediaSeoAudit.js.map +1 -1
  66. package/dist/views/Pages/PagesListView.js +14 -14
  67. package/dist/views/Pages/PagesListView.js.map +1 -1
  68. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -1
  69. package/dist/views/Pages/PagesTreeTable.js +3 -3
  70. package/dist/views/Pages/PagesTreeTable.js.map +1 -1
  71. package/dist/views/ScriptTags.d.ts.map +1 -1
  72. package/dist/views/ScriptTags.js +3 -3
  73. package/dist/views/ScriptTags.js.map +1 -1
  74. package/dist/views/seo/ContentTab.js +6 -6
  75. package/dist/views/seo/ContentTab.js.map +1 -1
  76. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  77. package/dist/views/seo/RedirectsTab.js +23 -8
  78. package/dist/views/seo/RedirectsTab.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/__tests__/views/form-entries.render.test.tsx +16 -4
  81. package/src/__tests__/views/form-submissions.render.test.tsx +16 -4
  82. package/src/__tests__/views/media-seo-audit.render.test.tsx +34 -0
  83. package/src/__tests__/views/redirects-pane.render.test.tsx +156 -0
  84. package/src/components/SEOPanel.tsx +9 -9
  85. package/src/components/collections/EntryDetailPane.tsx +1 -1
  86. package/src/components/forms/EntriesTable.tsx +12 -3
  87. package/src/components/forms/{EntryDetailDrawer.tsx → FormEntryDetailPane.tsx} +112 -102
  88. package/src/components/forms/FormSchemaPane.tsx +1 -1
  89. package/src/components/script-tags/ScriptTagEditorPane.tsx +1 -1
  90. package/src/components/seo/RedirectEditorPane.tsx +271 -0
  91. package/src/components/seo/SeoEditorPane.tsx +1 -1
  92. package/src/components/ui/DataTable.tsx +3 -3
  93. package/src/components/ui/Skeleton.tsx +8 -8
  94. package/src/components/ui/Toggle.tsx +1 -1
  95. package/src/fields/BlockBuilderField.tsx +1 -1
  96. package/src/fields/MediaField.tsx +1 -1
  97. package/src/fields/PropInput.tsx +1 -1
  98. package/src/layout/Header.tsx +2 -2
  99. package/src/styles/actuate-tokens.css +4 -0
  100. package/src/styles/theme.css +9 -3
  101. package/src/views/DocumentEdit.tsx +4 -4
  102. package/src/views/FormEntries.tsx +81 -66
  103. package/src/views/FormSubmissions.tsx +78 -64
  104. package/src/views/Forms.tsx +16 -16
  105. package/src/views/MediaBrowser.tsx +6 -1
  106. package/src/views/MediaSeoAudit.tsx +39 -2
  107. package/src/views/Pages/PagesListView.tsx +46 -46
  108. package/src/views/Pages/PagesTreeTable.tsx +11 -8
  109. package/src/views/ScriptTags.tsx +26 -36
  110. package/src/views/seo/ContentTab.tsx +10 -10
  111. package/src/views/seo/RedirectsTab.tsx +145 -109
  112. package/dist/components/forms/EntryDetailDrawer.d.ts +0 -11
  113. package/dist/components/forms/EntryDetailDrawer.d.ts.map +0 -1
  114. package/dist/components/forms/EntryDetailDrawer.js +0 -145
  115. package/dist/components/forms/EntryDetailDrawer.js.map +0 -1
  116. package/dist/components/seo/RedirectEditorDrawer.d.ts +0 -9
  117. package/dist/components/seo/RedirectEditorDrawer.d.ts.map +0 -1
  118. package/dist/components/seo/RedirectEditorDrawer.js +0 -95
  119. package/dist/components/seo/RedirectEditorDrawer.js.map +0 -1
  120. package/src/components/seo/RedirectEditorDrawer.tsx +0 -231
@@ -1,231 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState } from 'react'
4
- import { toast } from 'sonner'
5
- import {
6
- createRedirectRule,
7
- updateRedirectRule,
8
- type RedirectRule,
9
- type RedirectRulePayload,
10
- type RedirectStatusCode,
11
- } from '../../lib/seo-service.js'
12
- import { Drawer } from './Drawer.js'
13
- import { btnPrimary, btnSecondary } from './primitives.js'
14
- import { Toggle } from '../ui/Toggle.js'
15
-
16
- const STATUS_CODES: { value: RedirectStatusCode; label: string }[] = [
17
- { value: 301, label: '301 — Permanent' },
18
- { value: 302, label: '302 — Temporary' },
19
- { value: 307, label: '307 — Temporary (preserve method)' },
20
- { value: 308, label: '308 — Permanent (preserve method)' },
21
- { value: 410, label: '410 — Gone (no destination)' },
22
- ]
23
-
24
- const inputCls =
25
- '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'
26
-
27
- function normalizePath(p: string): string {
28
- const t = p.trim()
29
- if (!t) return ''
30
- if (/^https?:\/\//i.test(t)) return t
31
- return t.startsWith('/') ? t : `/${t}`
32
- }
33
-
34
- export function RedirectEditorDrawer({
35
- open,
36
- onOpenChange,
37
- rule,
38
- onSaved,
39
- }: {
40
- open: boolean
41
- onOpenChange: (open: boolean) => void
42
- /** null = create mode */
43
- rule: RedirectRule | null
44
- onSaved?: () => void
45
- }) {
46
- const [form, setForm] = useState<RedirectRulePayload>({
47
- type: 301,
48
- fromPath: '',
49
- toPath: '',
50
- enabled: true,
51
- preserveQuery: false,
52
- notes: '',
53
- })
54
- const [saving, setSaving] = useState(false)
55
- const [clientError, setClientError] = useState<string | null>(null)
56
-
57
- useEffect(() => {
58
- if (!open) return
59
- if (rule) {
60
- setForm({
61
- type: rule.type,
62
- fromPath: rule.fromPath,
63
- toPath: rule.toPath,
64
- enabled: rule.enabled,
65
- preserveQuery: rule.preserveQuery,
66
- notes: rule.notes ?? '',
67
- })
68
- } else {
69
- setForm({
70
- type: 301,
71
- fromPath: '',
72
- toPath: '',
73
- enabled: true,
74
- preserveQuery: false,
75
- notes: '',
76
- })
77
- }
78
- setClientError(null)
79
- }, [open, rule])
80
-
81
- const isGone = form.type === 410
82
- const update = (patch: Partial<RedirectRulePayload>) => setForm((f) => ({ ...f, ...patch }))
83
-
84
- async function handleSave() {
85
- const fromPath = normalizePath(form.fromPath)
86
- const toPath = isGone ? '' : normalizePath(form.toPath)
87
- if (!fromPath) {
88
- setClientError('Source path is required.')
89
- return
90
- }
91
- if (!isGone && !toPath) {
92
- setClientError('Destination is required for redirect status codes.')
93
- return
94
- }
95
- if (!isGone && fromPath === toPath) {
96
- setClientError('Source and destination cannot be identical (would create a loop).')
97
- return
98
- }
99
- setClientError(null)
100
- setSaving(true)
101
- const payload: RedirectRulePayload = { ...form, fromPath, toPath }
102
- const res = rule
103
- ? await updateRedirectRule(rule.id, payload)
104
- : await createRedirectRule(payload)
105
- setSaving(false)
106
- if (res.error) {
107
- toast.error(res.error)
108
- return
109
- }
110
- toast.success(rule ? 'Redirect updated.' : 'Redirect created.')
111
- onOpenChange(false)
112
- onSaved?.()
113
- }
114
-
115
- return (
116
- <Drawer
117
- open={open}
118
- onOpenChange={onOpenChange}
119
- title={rule ? 'Edit redirect' : 'New redirect'}
120
- width="max-w-lg"
121
- footer={
122
- <>
123
- <button className={btnSecondary} onClick={() => onOpenChange(false)} disabled={saving}>
124
- Cancel
125
- </button>
126
- <button className={btnPrimary} onClick={handleSave} disabled={saving}>
127
- {saving ? 'Saving…' : rule ? 'Save changes' : 'Create redirect'}
128
- </button>
129
- </>
130
- }
131
- >
132
- <div className="space-y-4">
133
- {clientError && (
134
- <p
135
- className="text-destructive bg-destructive/10 rounded-md px-3 py-2 text-sm"
136
- role="alert"
137
- >
138
- {clientError}
139
- </p>
140
- )}
141
- <div className="space-y-1.5">
142
- <label className="text-foreground text-sm font-medium" htmlFor="redir-type">
143
- Status code
144
- </label>
145
- <select
146
- id="redir-type"
147
- className={inputCls}
148
- value={form.type}
149
- onChange={(e) => update({ type: Number(e.target.value) as RedirectStatusCode })}
150
- >
151
- {STATUS_CODES.map((s) => (
152
- <option key={s.value} value={s.value}>
153
- {s.label}
154
- </option>
155
- ))}
156
- </select>
157
- </div>
158
-
159
- <div className="space-y-1.5">
160
- <label className="text-foreground text-sm font-medium" htmlFor="redir-from">
161
- Source path
162
- </label>
163
- <input
164
- id="redir-from"
165
- className={inputCls}
166
- value={form.fromPath}
167
- onChange={(e) => update({ fromPath: e.target.value })}
168
- placeholder="/old-path"
169
- />
170
- </div>
171
-
172
- {!isGone && (
173
- <div className="space-y-1.5">
174
- <label className="text-foreground text-sm font-medium" htmlFor="redir-to">
175
- Destination
176
- </label>
177
- <input
178
- id="redir-to"
179
- className={inputCls}
180
- value={form.toPath}
181
- onChange={(e) => update({ toPath: e.target.value })}
182
- placeholder="/new-path or https://example.com/page"
183
- />
184
- </div>
185
- )}
186
-
187
- <div className="space-y-1.5">
188
- <label className="text-foreground text-sm font-medium" htmlFor="redir-notes">
189
- Notes <span className="text-muted-foreground font-normal">(optional)</span>
190
- </label>
191
- <input
192
- id="redir-notes"
193
- className={inputCls}
194
- value={form.notes ?? ''}
195
- onChange={(e) => update({ notes: e.target.value })}
196
- placeholder="Why this redirect exists"
197
- />
198
- </div>
199
-
200
- <div className="border-border space-y-3 rounded-lg border p-4">
201
- <div className="flex items-center justify-between gap-3">
202
- <span className="text-foreground text-sm font-medium">Enabled</span>
203
- <Toggle
204
- checked={!!form.enabled}
205
- onCheckedChange={(v) => update({ enabled: v })}
206
- variant="enabled"
207
- aria-label="Enabled"
208
- />
209
- </div>
210
- {!isGone && (
211
- <div className="flex items-center justify-between gap-3">
212
- <span className="min-w-0">
213
- <span className="text-foreground block text-sm font-medium">
214
- Preserve query string
215
- </span>
216
- <span className="text-muted-foreground block text-sm">
217
- Append the original query params to the destination.
218
- </span>
219
- </span>
220
- <Toggle
221
- checked={!!form.preserveQuery}
222
- onCheckedChange={(v) => update({ preserveQuery: v })}
223
- aria-label="Preserve query string"
224
- />
225
- </div>
226
- )}
227
- </div>
228
- </div>
229
- </Drawer>
230
- )
231
- }