@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.
- package/CHANGELOG.md +6 -0
- package/dist/__tests__/views/form-entries.render.test.js +14 -4
- package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
- package/dist/__tests__/views/form-submissions.render.test.js +14 -4
- package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
- package/dist/__tests__/views/media-seo-audit.render.test.js +26 -0
- package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -1
- package/dist/__tests__/views/redirects-pane.render.test.d.ts +2 -0
- package/dist/__tests__/views/redirects-pane.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/redirects-pane.render.test.js +121 -0
- package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/SEOPanel.js +3 -3
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/collections/EntryDetailPane.js +1 -1
- package/dist/components/collections/EntryDetailPane.js.map +1 -1
- package/dist/components/forms/EntriesTable.d.ts +3 -1
- package/dist/components/forms/EntriesTable.d.ts.map +1 -1
- package/dist/components/forms/EntriesTable.js +5 -2
- package/dist/components/forms/EntriesTable.js.map +1 -1
- package/dist/components/forms/FormEntryDetailPane.d.ts +10 -0
- package/dist/components/forms/FormEntryDetailPane.d.ts.map +1 -0
- package/dist/components/forms/FormEntryDetailPane.js +137 -0
- package/dist/components/forms/FormEntryDetailPane.js.map +1 -0
- package/dist/components/forms/FormSchemaPane.js +1 -1
- package/dist/components/forms/FormSchemaPane.js.map +1 -1
- package/dist/components/script-tags/ScriptTagEditorPane.js +1 -1
- package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -1
- package/dist/components/seo/RedirectEditorPane.d.ts +12 -0
- package/dist/components/seo/RedirectEditorPane.d.ts.map +1 -0
- package/dist/components/seo/RedirectEditorPane.js +89 -0
- package/dist/components/seo/RedirectEditorPane.js.map +1 -0
- package/dist/components/seo/SeoEditorPane.js +1 -1
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/ui/DataTable.js +2 -2
- package/dist/components/ui/DataTable.js.map +1 -1
- package/dist/components/ui/Skeleton.js +3 -3
- package/dist/components/ui/Skeleton.js.map +1 -1
- package/dist/components/ui/Toggle.js +1 -1
- package/dist/components/ui/Toggle.js.map +1 -1
- package/dist/fields/BlockBuilderField.js +1 -1
- package/dist/fields/BlockBuilderField.js.map +1 -1
- package/dist/fields/MediaField.js +1 -1
- package/dist/fields/MediaField.js.map +1 -1
- package/dist/fields/PropInput.js +1 -1
- package/dist/fields/PropInput.js.map +1 -1
- package/dist/layout/Header.js +1 -1
- package/dist/layout/Header.js.map +1 -1
- package/dist/views/DocumentEdit.js +1 -1
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/FormEntries.d.ts.map +1 -1
- package/dist/views/FormEntries.js +18 -14
- package/dist/views/FormEntries.js.map +1 -1
- package/dist/views/FormSubmissions.d.ts.map +1 -1
- package/dist/views/FormSubmissions.js +18 -15
- package/dist/views/FormSubmissions.js.map +1 -1
- package/dist/views/Forms.js +5 -5
- package/dist/views/Forms.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +1 -1
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/MediaSeoAudit.d.ts +4 -1
- package/dist/views/MediaSeoAudit.d.ts.map +1 -1
- package/dist/views/MediaSeoAudit.js +12 -2
- package/dist/views/MediaSeoAudit.js.map +1 -1
- package/dist/views/Pages/PagesListView.js +14 -14
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -1
- package/dist/views/Pages/PagesTreeTable.js +3 -3
- package/dist/views/Pages/PagesTreeTable.js.map +1 -1
- package/dist/views/ScriptTags.d.ts.map +1 -1
- package/dist/views/ScriptTags.js +3 -3
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/seo/ContentTab.js +6 -6
- package/dist/views/seo/ContentTab.js.map +1 -1
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
- package/dist/views/seo/RedirectsTab.js +23 -8
- package/dist/views/seo/RedirectsTab.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/views/form-entries.render.test.tsx +16 -4
- package/src/__tests__/views/form-submissions.render.test.tsx +16 -4
- package/src/__tests__/views/media-seo-audit.render.test.tsx +34 -0
- package/src/__tests__/views/redirects-pane.render.test.tsx +156 -0
- package/src/components/SEOPanel.tsx +9 -9
- package/src/components/collections/EntryDetailPane.tsx +1 -1
- package/src/components/forms/EntriesTable.tsx +12 -3
- package/src/components/forms/{EntryDetailDrawer.tsx → FormEntryDetailPane.tsx} +112 -102
- package/src/components/forms/FormSchemaPane.tsx +1 -1
- package/src/components/script-tags/ScriptTagEditorPane.tsx +1 -1
- package/src/components/seo/RedirectEditorPane.tsx +271 -0
- package/src/components/seo/SeoEditorPane.tsx +1 -1
- package/src/components/ui/DataTable.tsx +3 -3
- package/src/components/ui/Skeleton.tsx +8 -8
- package/src/components/ui/Toggle.tsx +1 -1
- package/src/fields/BlockBuilderField.tsx +1 -1
- package/src/fields/MediaField.tsx +1 -1
- package/src/fields/PropInput.tsx +1 -1
- package/src/layout/Header.tsx +2 -2
- package/src/styles/actuate-tokens.css +4 -0
- package/src/styles/theme.css +9 -3
- package/src/views/DocumentEdit.tsx +4 -4
- package/src/views/FormEntries.tsx +81 -66
- package/src/views/FormSubmissions.tsx +78 -64
- package/src/views/Forms.tsx +16 -16
- package/src/views/MediaBrowser.tsx +6 -1
- package/src/views/MediaSeoAudit.tsx +39 -2
- package/src/views/Pages/PagesListView.tsx +46 -46
- package/src/views/Pages/PagesTreeTable.tsx +11 -8
- package/src/views/ScriptTags.tsx +26 -36
- package/src/views/seo/ContentTab.tsx +10 -10
- package/src/views/seo/RedirectsTab.tsx +145 -109
- package/dist/components/forms/EntryDetailDrawer.d.ts +0 -11
- package/dist/components/forms/EntryDetailDrawer.d.ts.map +0 -1
- package/dist/components/forms/EntryDetailDrawer.js +0 -145
- package/dist/components/forms/EntryDetailDrawer.js.map +0 -1
- package/dist/components/seo/RedirectEditorDrawer.d.ts +0 -9
- package/dist/components/seo/RedirectEditorDrawer.d.ts.map +0 -1
- package/dist/components/seo/RedirectEditorDrawer.js +0 -95
- package/dist/components/seo/RedirectEditorDrawer.js.map +0 -1
- 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
|
-
}
|