@actuate-media/cms-admin 0.68.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 (166) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +31 -11
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/hooks/useIdleLogout.test.js +57 -2
  6. package/dist/__tests__/hooks/useIdleLogout.test.js.map +1 -1
  7. package/dist/__tests__/router/query-string.test.d.ts +2 -0
  8. package/dist/__tests__/router/query-string.test.d.ts.map +1 -0
  9. package/dist/__tests__/router/query-string.test.js +44 -0
  10. package/dist/__tests__/router/query-string.test.js.map +1 -0
  11. package/dist/__tests__/views/form-entries.render.test.js +14 -4
  12. package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
  13. package/dist/__tests__/views/form-submissions.render.test.js +14 -4
  14. package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
  15. package/dist/__tests__/views/media-seo-audit.render.test.js +26 -0
  16. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -1
  17. package/dist/__tests__/views/redirects-pane.render.test.d.ts +2 -0
  18. package/dist/__tests__/views/redirects-pane.render.test.d.ts.map +1 -0
  19. package/dist/__tests__/views/redirects-pane.render.test.js +121 -0
  20. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -0
  21. package/dist/__tests__/views/users-selected-deeplink.render.test.d.ts +2 -0
  22. package/dist/__tests__/views/users-selected-deeplink.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/views/users-selected-deeplink.render.test.js +91 -0
  24. package/dist/__tests__/views/users-selected-deeplink.render.test.js.map +1 -0
  25. package/dist/actuate-admin.css +1 -1
  26. package/dist/components/SEOPanel.js +3 -3
  27. package/dist/components/SEOPanel.js.map +1 -1
  28. package/dist/components/collections/EntryDetailPane.js +1 -1
  29. package/dist/components/collections/EntryDetailPane.js.map +1 -1
  30. package/dist/components/forms/EntriesTable.d.ts +3 -1
  31. package/dist/components/forms/EntriesTable.d.ts.map +1 -1
  32. package/dist/components/forms/EntriesTable.js +5 -2
  33. package/dist/components/forms/EntriesTable.js.map +1 -1
  34. package/dist/components/forms/FormEntryDetailPane.d.ts +10 -0
  35. package/dist/components/forms/FormEntryDetailPane.d.ts.map +1 -0
  36. package/dist/components/forms/FormEntryDetailPane.js +137 -0
  37. package/dist/components/forms/FormEntryDetailPane.js.map +1 -0
  38. package/dist/components/forms/FormSchemaPane.js +1 -1
  39. package/dist/components/forms/FormSchemaPane.js.map +1 -1
  40. package/dist/components/script-tags/ScriptTagEditorPane.js +1 -1
  41. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -1
  42. package/dist/components/seo/RedirectEditorPane.d.ts +12 -0
  43. package/dist/components/seo/RedirectEditorPane.d.ts.map +1 -0
  44. package/dist/components/seo/RedirectEditorPane.js +89 -0
  45. package/dist/components/seo/RedirectEditorPane.js.map +1 -0
  46. package/dist/components/seo/SeoEditorPane.js +1 -1
  47. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  48. package/dist/components/ui/DataTable.js +2 -2
  49. package/dist/components/ui/DataTable.js.map +1 -1
  50. package/dist/components/ui/Skeleton.js +3 -3
  51. package/dist/components/ui/Skeleton.js.map +1 -1
  52. package/dist/components/ui/Toggle.js +1 -1
  53. package/dist/components/ui/Toggle.js.map +1 -1
  54. package/dist/fields/BlockBuilderField.js +1 -1
  55. package/dist/fields/BlockBuilderField.js.map +1 -1
  56. package/dist/fields/MediaField.js +1 -1
  57. package/dist/fields/MediaField.js.map +1 -1
  58. package/dist/fields/PropInput.js +1 -1
  59. package/dist/fields/PropInput.js.map +1 -1
  60. package/dist/hooks/useIdleLogout.d.ts +21 -6
  61. package/dist/hooks/useIdleLogout.d.ts.map +1 -1
  62. package/dist/hooks/useIdleLogout.js +41 -8
  63. package/dist/hooks/useIdleLogout.js.map +1 -1
  64. package/dist/layout/Header.js +1 -1
  65. package/dist/layout/Header.js.map +1 -1
  66. package/dist/router/index.d.ts +7 -0
  67. package/dist/router/index.d.ts.map +1 -1
  68. package/dist/router/index.js +23 -4
  69. package/dist/router/index.js.map +1 -1
  70. package/dist/views/DocumentEdit.js +1 -1
  71. package/dist/views/DocumentEdit.js.map +1 -1
  72. package/dist/views/FormEntries.d.ts.map +1 -1
  73. package/dist/views/FormEntries.js +18 -14
  74. package/dist/views/FormEntries.js.map +1 -1
  75. package/dist/views/FormSubmissions.d.ts.map +1 -1
  76. package/dist/views/FormSubmissions.js +18 -15
  77. package/dist/views/FormSubmissions.js.map +1 -1
  78. package/dist/views/Forms.js +5 -5
  79. package/dist/views/Forms.js.map +1 -1
  80. package/dist/views/MediaBrowser.d.ts +4 -1
  81. package/dist/views/MediaBrowser.d.ts.map +1 -1
  82. package/dist/views/MediaBrowser.js +28 -6
  83. package/dist/views/MediaBrowser.js.map +1 -1
  84. package/dist/views/MediaSeoAudit.d.ts +4 -1
  85. package/dist/views/MediaSeoAudit.d.ts.map +1 -1
  86. package/dist/views/MediaSeoAudit.js +12 -2
  87. package/dist/views/MediaSeoAudit.js.map +1 -1
  88. package/dist/views/Pages/PagesListView.js +14 -14
  89. package/dist/views/Pages/PagesListView.js.map +1 -1
  90. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -1
  91. package/dist/views/Pages/PagesTreeTable.js +3 -3
  92. package/dist/views/Pages/PagesTreeTable.js.map +1 -1
  93. package/dist/views/SEO.d.ts.map +1 -1
  94. package/dist/views/SEO.js +2 -1
  95. package/dist/views/SEO.js.map +1 -1
  96. package/dist/views/ScriptTags.d.ts.map +1 -1
  97. package/dist/views/ScriptTags.js +3 -3
  98. package/dist/views/ScriptTags.js.map +1 -1
  99. package/dist/views/Settings.d.ts +4 -1
  100. package/dist/views/Settings.d.ts.map +1 -1
  101. package/dist/views/Settings.js +4 -3
  102. package/dist/views/Settings.js.map +1 -1
  103. package/dist/views/Users.d.ts +4 -1
  104. package/dist/views/Users.d.ts.map +1 -1
  105. package/dist/views/Users.js +22 -2
  106. package/dist/views/Users.js.map +1 -1
  107. package/dist/views/seo/ContentTab.js +6 -6
  108. package/dist/views/seo/ContentTab.js.map +1 -1
  109. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  110. package/dist/views/seo/RedirectsTab.js +23 -8
  111. package/dist/views/seo/RedirectsTab.js.map +1 -1
  112. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  113. package/dist/views/settings/SecurityTab.js +2 -1
  114. package/dist/views/settings/SecurityTab.js.map +1 -1
  115. package/package.json +2 -2
  116. package/src/AdminRoot.tsx +53 -12
  117. package/src/__tests__/hooks/useIdleLogout.test.tsx +103 -1
  118. package/src/__tests__/router/query-string.test.ts +54 -0
  119. package/src/__tests__/views/form-entries.render.test.tsx +16 -4
  120. package/src/__tests__/views/form-submissions.render.test.tsx +16 -4
  121. package/src/__tests__/views/media-seo-audit.render.test.tsx +34 -0
  122. package/src/__tests__/views/redirects-pane.render.test.tsx +156 -0
  123. package/src/__tests__/views/users-selected-deeplink.render.test.tsx +102 -0
  124. package/src/components/SEOPanel.tsx +9 -9
  125. package/src/components/collections/EntryDetailPane.tsx +1 -1
  126. package/src/components/forms/EntriesTable.tsx +12 -3
  127. package/src/components/forms/{EntryDetailDrawer.tsx → FormEntryDetailPane.tsx} +112 -102
  128. package/src/components/forms/FormSchemaPane.tsx +1 -1
  129. package/src/components/script-tags/ScriptTagEditorPane.tsx +1 -1
  130. package/src/components/seo/RedirectEditorPane.tsx +271 -0
  131. package/src/components/seo/SeoEditorPane.tsx +1 -1
  132. package/src/components/ui/DataTable.tsx +3 -3
  133. package/src/components/ui/Skeleton.tsx +8 -8
  134. package/src/components/ui/Toggle.tsx +1 -1
  135. package/src/fields/BlockBuilderField.tsx +1 -1
  136. package/src/fields/MediaField.tsx +1 -1
  137. package/src/fields/PropInput.tsx +1 -1
  138. package/src/hooks/useIdleLogout.ts +65 -13
  139. package/src/layout/Header.tsx +2 -2
  140. package/src/router/index.ts +25 -4
  141. package/src/styles/actuate-tokens.css +4 -0
  142. package/src/styles/theme.css +9 -3
  143. package/src/views/DocumentEdit.tsx +4 -4
  144. package/src/views/FormEntries.tsx +81 -66
  145. package/src/views/FormSubmissions.tsx +78 -64
  146. package/src/views/Forms.tsx +16 -16
  147. package/src/views/MediaBrowser.tsx +34 -6
  148. package/src/views/MediaSeoAudit.tsx +39 -2
  149. package/src/views/Pages/PagesListView.tsx +46 -46
  150. package/src/views/Pages/PagesTreeTable.tsx +11 -8
  151. package/src/views/SEO.tsx +2 -1
  152. package/src/views/ScriptTags.tsx +26 -36
  153. package/src/views/Settings.tsx +7 -2
  154. package/src/views/Users.tsx +23 -2
  155. package/src/views/seo/ContentTab.tsx +10 -10
  156. package/src/views/seo/RedirectsTab.tsx +145 -109
  157. package/src/views/settings/SecurityTab.tsx +2 -1
  158. package/dist/components/forms/EntryDetailDrawer.d.ts +0 -11
  159. package/dist/components/forms/EntryDetailDrawer.d.ts.map +0 -1
  160. package/dist/components/forms/EntryDetailDrawer.js +0 -145
  161. package/dist/components/forms/EntryDetailDrawer.js.map +0 -1
  162. package/dist/components/seo/RedirectEditorDrawer.d.ts +0 -9
  163. package/dist/components/seo/RedirectEditorDrawer.d.ts.map +0 -1
  164. package/dist/components/seo/RedirectEditorDrawer.js +0 -95
  165. package/dist/components/seo/RedirectEditorDrawer.js.map +0 -1
  166. package/src/components/seo/RedirectEditorDrawer.tsx +0 -231
@@ -32,7 +32,7 @@ const FILTERS: { id: ContentSeoFilter; label: string }[] = [
32
32
  ]
33
33
 
34
34
  const inputCls =
35
- 'w-full rounded-md border border-border bg-input-background py-2 pl-9 pr-3 text-base text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'
35
+ 'w-full rounded-md border border-[var(--bdr)] bg-input-background py-2 pl-9 pr-3 text-base text-[var(--txt)] placeholder:text-[var(--sub)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]'
36
36
 
37
37
  type Selection = { type: string; id: string } | null
38
38
 
@@ -64,7 +64,7 @@ export function ContentTab({
64
64
  <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
65
65
  <div className="relative max-w-sm flex-1">
66
66
  <Search
67
- className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2"
67
+ className="pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--sub)]"
68
68
  aria-hidden
69
69
  />
70
70
  <input
@@ -82,10 +82,10 @@ export function ContentTab({
82
82
  type="button"
83
83
  aria-pressed={filter === f.id}
84
84
  onClick={() => setFilter(f.id)}
85
- className={`focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none ${
85
+ className={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none ${
86
86
  filter === f.id
87
87
  ? 'bg-primary text-primary-foreground'
88
- : 'text-muted-foreground hover:bg-accent hover:text-foreground'
88
+ : 'text-[var(--sub)] hover:bg-[var(--acc-l)] hover:text-[var(--txt)]'
89
89
  }`}
90
90
  >
91
91
  {f.label}
@@ -118,14 +118,14 @@ export function ContentTab({
118
118
  <div className="overflow-x-auto">
119
119
  <table className="w-full text-sm" aria-label="Content SEO">
120
120
  <thead>
121
- <tr className="text-muted-foreground border-border border-b text-left">
121
+ <tr className="border-b border-[var(--bdr)] text-left text-[var(--sub)]">
122
122
  <th className="py-2 pr-4 font-medium">Title</th>
123
123
  <th className="py-2 pr-4 font-medium">Type</th>
124
124
  <th className="py-2 pr-4 font-medium">Meta</th>
125
125
  <th className="py-2 text-right font-medium">Score</th>
126
126
  </tr>
127
127
  </thead>
128
- <tbody className="divide-border divide-y">
128
+ <tbody className="divide-y divide-[var(--bdr)]">
129
129
  {rows.map((r) => {
130
130
  const isSelected =
131
131
  selection?.type === r.entityType && selection.id === r.entityId
@@ -133,7 +133,7 @@ export function ContentTab({
133
133
  <tr
134
134
  key={`${r.entityType}-${r.entityId}`}
135
135
  className={`cursor-pointer transition-colors ${
136
- isSelected ? 'bg-[var(--acc-l)]' : 'hover:bg-accent/40'
136
+ isSelected ? 'bg-[var(--acc-l)]' : 'hover:bg-[var(--acc-l)]'
137
137
  }`}
138
138
  onClick={() => toggleRow(r.entityType, r.entityId)}
139
139
  >
@@ -147,12 +147,12 @@ export function ContentTab({
147
147
  aria-expanded={isSelected}
148
148
  className="text-left"
149
149
  >
150
- <span className="text-foreground block font-medium">{r.title}</span>
151
- <span className="text-muted-foreground block truncate">{r.url}</span>
150
+ <span className="block font-medium text-[var(--txt)]">{r.title}</span>
151
+ <span className="block truncate text-[var(--sub)]">{r.url}</span>
152
152
  </button>
153
153
  </td>
154
154
  <td className="py-2.5 pr-4 capitalize">
155
- <span className="text-muted-foreground">{r.entityType}</span>
155
+ <span className="text-[var(--sub)]">{r.entityType}</span>
156
156
  </td>
157
157
  <td className="py-2.5 pr-4">
158
158
  <span className="flex flex-wrap gap-1.5">
@@ -3,7 +3,6 @@
3
3
  import { useRef, useState } from 'react'
4
4
  import {
5
5
  Plus,
6
- Pencil,
7
6
  Trash2,
8
7
  Upload,
9
8
  Download,
@@ -39,7 +38,8 @@ import {
39
38
  btnSecondary,
40
39
  btnGhostIcon,
41
40
  } from '../../components/seo/primitives.js'
42
- import { RedirectEditorDrawer } from '../../components/seo/RedirectEditorDrawer.js'
41
+ import { RedirectEditorPane } from '../../components/seo/RedirectEditorPane.js'
42
+ import { InspectorPane, SplitPaneLayout, SplitPaneList } from '../../components/ui/InspectorPane.js'
43
43
  import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
44
44
  import { useSeoResource } from './useSeoResource.js'
45
45
 
@@ -227,10 +227,14 @@ export function RedirectsTab() {
227
227
  bump()
228
228
  }
229
229
 
230
+ const toggleRow = (rule: RedirectRule) =>
231
+ setEditor((prev) => (prev?.rule?.id === rule.id ? null : { rule }))
232
+
230
233
  async function handleDelete(rule: RedirectRule) {
231
234
  const res = await deleteRedirectRule(rule.id)
232
235
  if (res.error) return toast.error(res.error)
233
236
  toast.success('Redirect deleted.')
237
+ setEditor((cur) => (cur?.rule?.id === rule.id ? null : cur))
234
238
  refetchAll()
235
239
  }
236
240
 
@@ -272,117 +276,149 @@ export function RedirectsTab() {
272
276
  <ChainMapCard onFlattened={refetchAll} />
273
277
  </div>
274
278
 
275
- <SectionCard
276
- title="Redirect Rules"
277
- action={
278
- <div className="flex flex-wrap gap-2">
279
- <input
280
- ref={fileRef}
281
- type="file"
282
- accept=".csv,text/csv"
283
- className="sr-only"
284
- aria-hidden
285
- tabIndex={-1}
286
- onChange={(e) => {
287
- const f = e.target.files?.[0]
288
- if (f) handleImportFile(f)
289
- e.target.value = ''
290
- }}
291
- />
292
- <button className={btnSecondary} onClick={() => fileRef.current?.click()}>
293
- <Upload className="h-4 w-4" aria-hidden /> Import
294
- </button>
295
- <button className={btnSecondary} onClick={handleExport}>
296
- <Download className="h-4 w-4" aria-hidden /> Export
297
- </button>
298
- <button className={btnPrimary} onClick={() => setEditor({ rule: null })}>
299
- <Plus className="h-4 w-4" aria-hidden /> New redirect
300
- </button>
301
- </div>
302
- }
303
- >
304
- {loading ? (
305
- <SeoLoading label="Loading redirects…" />
306
- ) : error ? (
307
- <SeoErrorState message={error} onRetry={refetch} />
308
- ) : (data ?? []).length === 0 ? (
309
- <SeoEmptyState
310
- icon={<Link2 size={24} />}
311
- title="No redirects yet"
312
- description="Create a redirect or import a CSV to get started."
279
+ {/* The SEO page scrolls (tabbed layout), so the split area takes a
280
+ viewport-derived height to keep the pane's body/footer independently
281
+ scrollable, matching the Content tab's inline editor pane. */}
282
+ <SplitPaneLayout className="h-[calc(100vh-280px)] min-h-[440px]">
283
+ <SplitPaneList>
284
+ <SectionCard
285
+ title="Redirect Rules"
313
286
  action={
314
- <button className={btnPrimary} onClick={() => setEditor({ rule: null })}>
315
- <Plus className="h-4 w-4" aria-hidden /> New redirect
316
- </button>
287
+ <div className="flex flex-wrap gap-2">
288
+ <input
289
+ ref={fileRef}
290
+ type="file"
291
+ accept=".csv,text/csv"
292
+ className="sr-only"
293
+ aria-hidden
294
+ tabIndex={-1}
295
+ onChange={(e) => {
296
+ const f = e.target.files?.[0]
297
+ if (f) handleImportFile(f)
298
+ e.target.value = ''
299
+ }}
300
+ />
301
+ <button className={btnSecondary} onClick={() => fileRef.current?.click()}>
302
+ <Upload className="h-4 w-4" aria-hidden /> Import
303
+ </button>
304
+ <button className={btnSecondary} onClick={handleExport}>
305
+ <Download className="h-4 w-4" aria-hidden /> Export
306
+ </button>
307
+ <button className={btnPrimary} onClick={() => setEditor({ rule: null })}>
308
+ <Plus className="h-4 w-4" aria-hidden /> New redirect
309
+ </button>
310
+ </div>
317
311
  }
318
- />
319
- ) : (
320
- <div className="overflow-x-auto">
321
- <table className="w-full text-sm" aria-label="Redirect rules">
322
- <thead>
323
- <tr className="text-muted-foreground border-border border-b text-left">
324
- <th className="py-2 pr-4 font-medium">Source</th>
325
- <th className="py-2 pr-4 font-medium">Destination</th>
326
- <th className="py-2 pr-4 font-medium">Type</th>
327
- <th className="py-2 pr-4 text-right font-medium">Hits</th>
328
- <th className="py-2 pr-4 font-medium">Status</th>
329
- <th className="py-2 text-right font-medium">
330
- <span className="sr-only">Actions</span>
331
- </th>
332
- </tr>
333
- </thead>
334
- <tbody className="divide-border divide-y">
335
- {data!.map((r) => (
336
- <tr key={r.id} className="hover:bg-accent/40">
337
- <td className="text-foreground py-2.5 pr-4 font-mono">{r.fromPath}</td>
338
- <td className="text-muted-foreground py-2.5 pr-4 font-mono">
339
- {r.type === 410 ? <span className="italic">— (gone)</span> : r.toPath}
340
- </td>
341
- <td className="text-foreground py-2.5 pr-4 tabular-nums">{r.type}</td>
342
- <td className="text-foreground py-2.5 pr-4 text-right tabular-nums">
343
- {r.hits}
344
- </td>
345
- <td className="py-2.5 pr-4">
346
- <span className="flex flex-wrap gap-1.5">
347
- <SeoStatusBadge
348
- label={r.enabled ? 'Enabled' : 'Disabled'}
349
- tone={r.enabled ? 'good' : 'neutral'}
350
- />
351
- {r.stale && <SeoStatusBadge label="Stale" tone="fair" />}
352
- </span>
353
- </td>
354
- <td className="py-2.5 text-right">
355
- <div className="flex justify-end gap-1">
356
- <button
357
- className={btnGhostIcon}
358
- onClick={() => setEditor({ rule: r })}
359
- aria-label={`Edit redirect ${r.fromPath}`}
360
- >
361
- <Pencil className="h-4 w-4" aria-hidden />
362
- </button>
363
- <button
364
- className={btnGhostIcon}
365
- onClick={() => setConfirmDelete(r)}
366
- aria-label={`Delete redirect ${r.fromPath}`}
312
+ >
313
+ {loading ? (
314
+ <SeoLoading label="Loading redirects…" />
315
+ ) : error ? (
316
+ <SeoErrorState message={error} onRetry={refetch} />
317
+ ) : (data ?? []).length === 0 ? (
318
+ <SeoEmptyState
319
+ icon={<Link2 size={24} />}
320
+ title="No redirects yet"
321
+ description="Create a redirect or import a CSV to get started."
322
+ action={
323
+ <button className={btnPrimary} onClick={() => setEditor({ rule: null })}>
324
+ <Plus className="h-4 w-4" aria-hidden /> New redirect
325
+ </button>
326
+ }
327
+ />
328
+ ) : (
329
+ <div className="overflow-x-auto">
330
+ <table className="w-full text-sm" aria-label="Redirect rules">
331
+ <thead>
332
+ <tr className="text-muted-foreground border-border border-b text-left">
333
+ <th className="py-2 pr-4 font-medium">Source</th>
334
+ <th className="py-2 pr-4 font-medium">Destination</th>
335
+ <th className="py-2 pr-4 font-medium">Type</th>
336
+ <th className="py-2 pr-4 text-right font-medium">Hits</th>
337
+ <th className="py-2 pr-4 font-medium">Status</th>
338
+ <th className="py-2 text-right font-medium">
339
+ <span className="sr-only">Actions</span>
340
+ </th>
341
+ </tr>
342
+ </thead>
343
+ <tbody className="divide-border divide-y">
344
+ {data!.map((r) => {
345
+ const isSelected = editor?.rule?.id === r.id
346
+ return (
347
+ <tr
348
+ key={r.id}
349
+ className={`cursor-pointer transition-colors ${
350
+ isSelected ? 'bg-[var(--acc-l)]' : 'hover:bg-accent/40'
351
+ }`}
352
+ onClick={() => toggleRow(r)}
367
353
  >
368
- <Trash2 className="h-4 w-4" aria-hidden />
369
- </button>
370
- </div>
371
- </td>
372
- </tr>
373
- ))}
374
- </tbody>
375
- </table>
376
- </div>
377
- )}
378
- </SectionCard>
354
+ <td className="py-2.5 pr-4">
355
+ <button
356
+ type="button"
357
+ onClick={(e) => {
358
+ e.stopPropagation()
359
+ toggleRow(r)
360
+ }}
361
+ aria-expanded={isSelected}
362
+ className="text-foreground focus-visible:ring-ring rounded text-left font-mono hover:underline focus-visible:ring-2 focus-visible:outline-none"
363
+ >
364
+ {r.fromPath}
365
+ </button>
366
+ </td>
367
+ <td className="text-muted-foreground py-2.5 pr-4 font-mono">
368
+ {r.type === 410 ? <span className="italic">— (gone)</span> : r.toPath}
369
+ </td>
370
+ <td className="text-foreground py-2.5 pr-4 tabular-nums">{r.type}</td>
371
+ <td className="text-foreground py-2.5 pr-4 text-right tabular-nums">
372
+ {r.hits}
373
+ </td>
374
+ <td className="py-2.5 pr-4">
375
+ <span className="flex flex-wrap gap-1.5">
376
+ <SeoStatusBadge
377
+ label={r.enabled ? 'Enabled' : 'Disabled'}
378
+ tone={r.enabled ? 'good' : 'neutral'}
379
+ />
380
+ {r.stale && <SeoStatusBadge label="Stale" tone="fair" />}
381
+ </span>
382
+ </td>
383
+ <td className="py-2.5 text-right">
384
+ <div className="flex justify-end gap-1">
385
+ <button
386
+ className={btnGhostIcon}
387
+ onClick={(e) => {
388
+ e.stopPropagation()
389
+ setConfirmDelete(r)
390
+ }}
391
+ aria-label={`Delete redirect ${r.fromPath}`}
392
+ >
393
+ <Trash2 className="h-4 w-4" aria-hidden />
394
+ </button>
395
+ </div>
396
+ </td>
397
+ </tr>
398
+ )
399
+ })}
400
+ </tbody>
401
+ </table>
402
+ </div>
403
+ )}
404
+ </SectionCard>
405
+ </SplitPaneList>
379
406
 
380
- <RedirectEditorDrawer
381
- open={editor !== null}
382
- onOpenChange={(o) => !o && setEditor(null)}
383
- rule={editor?.rule ?? null}
384
- onSaved={refetchAll}
385
- />
407
+ <InspectorPane open={editor !== null} width={420} aria-label="Redirect editor">
408
+ {editor && (
409
+ <RedirectEditorPane
410
+ key={editor.rule?.id ?? 'new'}
411
+ rule={editor.rule}
412
+ onClose={() => setEditor(null)}
413
+ onSaved={() => {
414
+ setEditor(null)
415
+ refetchAll()
416
+ }}
417
+ onDelete={(rule) => setConfirmDelete(rule)}
418
+ />
419
+ )}
420
+ </InspectorPane>
421
+ </SplitPaneLayout>
386
422
 
387
423
  <ConfirmDialog
388
424
  open={confirmDelete !== null}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect } from 'react'
4
4
  import { Loader2, ShieldAlert } from 'lucide-react'
5
+ import { parseRouteQuery } from '../../router/index.js'
5
6
  import {
6
7
  ConfirmDangerousSettingDialog,
7
8
  SettingsSaveBar,
@@ -56,7 +57,7 @@ export function SecurityTab({
56
57
  // Deep-link support: /settings?tab=security&section=sessions|ip-allowlist|audit
57
58
  useEffect(() => {
58
59
  if (loading || typeof window === 'undefined') return
59
- const section = new URLSearchParams(window.location.search).get('section')
60
+ const section = parseRouteQuery(window.location.search).get('section')
60
61
  if (!section) return
61
62
  const el = document.getElementById(`security-${section}`)
62
63
  el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
@@ -1,11 +0,0 @@
1
- export interface EntryDetailDrawerProps {
2
- entryId: string | null;
3
- open: boolean;
4
- onOpenChange: (open: boolean) => void;
5
- /** Called after a mutation (star/archive/read/delete) so the list refreshes. */
6
- onChanged?: () => void;
7
- /** Called specifically after a delete so the caller can drop the row/selection. */
8
- onDeleted?: (id: string) => void;
9
- }
10
- export declare function EntryDetailDrawer({ entryId, open, onOpenChange, onChanged, onDeleted, }: EntryDetailDrawerProps): import("react/jsx-runtime").JSX.Element;
11
- //# sourceMappingURL=EntryDetailDrawer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"EntryDetailDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/forms/EntryDetailDrawer.tsx"],"names":[],"mappings":"AA6DA,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,mFAAmF;IACnF,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,sBAAsB,2CAyMxB"}
@@ -1,145 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /**
4
- * Entry detail drawer — shows a single submission rendered against the schema
5
- * version it was captured with, plus sender, attribution, and per-entry
6
- * actions (star, archive, mark unread, delete). Opening an unread entry marks
7
- * it read. Token-only styling; built on the shared `Drawer`.
8
- */
9
- import { useCallback, useEffect, useState } from 'react';
10
- import { Archive, ArchiveRestore, Loader2, Mail, Star, Trash2 } from 'lucide-react';
11
- import { toast } from 'sonner';
12
- import { deleteEntry, fetchEntry, updateEntry, } from '../../lib/forms-service.js';
13
- import { emitFormsChanged } from '../../lib/forms-events.js';
14
- import { ConfirmDialog } from '../ui/ConfirmDialog.js';
15
- import { Drawer } from './Drawer.js';
16
- import { FormsErrorState, btnGhostIcon } from './primitives.js';
17
- function formatDateTime(iso) {
18
- if (!iso)
19
- return '—';
20
- const d = new Date(iso);
21
- if (Number.isNaN(d.getTime()))
22
- return '—';
23
- return d.toLocaleString(undefined, {
24
- month: 'short',
25
- day: 'numeric',
26
- year: 'numeric',
27
- hour: 'numeric',
28
- minute: '2-digit',
29
- });
30
- }
31
- /** Render a stored value for display, coping with arrays/objects/booleans. */
32
- function displayValue(value) {
33
- if (value == null || value === '')
34
- return '—';
35
- if (typeof value === 'boolean')
36
- return value ? 'Yes' : 'No';
37
- if (Array.isArray(value))
38
- return value.map((v) => String(v)).join(', ');
39
- if (typeof value === 'object')
40
- return JSON.stringify(value);
41
- return String(value);
42
- }
43
- function attributionRows(entry) {
44
- return [
45
- { label: 'Source', value: entry.utmSource },
46
- { label: 'Medium', value: entry.utmMedium },
47
- { label: 'Campaign', value: entry.utmCampaign },
48
- { label: 'Page', value: entry.pageUrl },
49
- { label: 'Referrer', value: entry.referrer },
50
- ].filter((r) => r.value);
51
- }
52
- export function EntryDetailDrawer({ entryId, open, onOpenChange, onChanged, onDeleted, }) {
53
- const [detail, setDetail] = useState(null);
54
- const [loading, setLoading] = useState(false);
55
- const [error, setError] = useState(null);
56
- const [busy, setBusy] = useState(false);
57
- const [confirmDelete, setConfirmDelete] = useState(false);
58
- const notifyChanged = useCallback(() => {
59
- emitFormsChanged();
60
- onChanged?.();
61
- }, [onChanged]);
62
- // Load the entry (and its schema) when the drawer opens, marking it read.
63
- useEffect(() => {
64
- if (!open || !entryId)
65
- return;
66
- let cancelled = false;
67
- setLoading(true);
68
- setError(null);
69
- setDetail(null);
70
- void (async () => {
71
- try {
72
- const data = await fetchEntry(entryId);
73
- if (cancelled)
74
- return;
75
- if (!data) {
76
- setError('Entry not found');
77
- return;
78
- }
79
- setDetail(data);
80
- // Mark read on open (fire-and-forget; refresh the list counts).
81
- if (data.entry.unread) {
82
- const res = await updateEntry(entryId, { unread: false });
83
- if (!cancelled && !res.error)
84
- notifyChanged();
85
- }
86
- }
87
- catch (e) {
88
- if (!cancelled)
89
- setError(e instanceof Error ? e.message : 'Failed to load entry');
90
- }
91
- finally {
92
- if (!cancelled)
93
- setLoading(false);
94
- }
95
- })();
96
- return () => {
97
- cancelled = true;
98
- };
99
- }, [open, entryId, notifyChanged]);
100
- const mutate = useCallback(async (patch) => {
101
- if (!entryId)
102
- return;
103
- setBusy(true);
104
- const res = await updateEntry(entryId, patch);
105
- setBusy(false);
106
- if (res.error) {
107
- toast.error(res.error);
108
- return;
109
- }
110
- setDetail((cur) => (cur ? { ...cur, entry: { ...cur.entry, ...patch } } : cur));
111
- notifyChanged();
112
- }, [entryId, notifyChanged]);
113
- const handleDelete = useCallback(async () => {
114
- if (!entryId)
115
- return;
116
- setBusy(true);
117
- const res = await deleteEntry(entryId);
118
- setBusy(false);
119
- if (res.error) {
120
- toast.error(res.error);
121
- return;
122
- }
123
- toast.success('Submission deleted');
124
- notifyChanged();
125
- onDeleted?.(entryId);
126
- onOpenChange(false);
127
- }, [entryId, notifyChanged, onDeleted, onOpenChange]);
128
- const entry = detail?.entry;
129
- const senderName = entry?.senderName || 'Anonymous';
130
- const attrRows = entry ? attributionRows(entry) : [];
131
- return (_jsxs(_Fragment, { children: [_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: entry ? `Entry${entry.entryNumber ? ` #${entry.entryNumber}` : ''}` : 'Entry', description: entry ? formatDateTime(entry.submittedAt) : undefined, headerExtra: entry ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: btnGhostIcon, "aria-pressed": entry.starred, "aria-label": entry.starred ? 'Unstar entry' : 'Star entry', disabled: busy, onClick: () => void mutate({ starred: !entry.starred }), children: _jsx(Star, { size: 18, "aria-hidden": true, className: entry.starred ? 'fill-warning text-warning' : '' }) }), _jsx("button", { type: "button", className: btnGhostIcon, "aria-pressed": entry.archived, "aria-label": entry.archived ? 'Unarchive entry' : 'Archive entry', disabled: busy, onClick: () => void mutate({ archived: !entry.archived }), children: entry.archived ? (_jsx(ArchiveRestore, { size: 18, "aria-hidden": true })) : (_jsx(Archive, { size: 18, "aria-hidden": true })) })] })) : undefined, footer: entry ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "text-destructive hover:bg-destructive/10 focus-visible:ring-ring inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", disabled: busy, onClick: () => setConfirmDelete(true), children: [_jsx(Trash2, { size: 16, "aria-hidden": true }), "Delete"] }), _jsxs("button", { type: "button", className: "border-border text-foreground hover:bg-accent focus-visible:ring-ring inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", disabled: busy, onClick: () => void mutate({ unread: !entry.unread }), children: [_jsx(Mail, { size: 16, "aria-hidden": true }), "Mark ", entry.unread ? 'read' : 'unread'] })] })) : undefined, children: loading ? (_jsxs("div", { className: "flex h-40 items-center justify-center", role: "status", "aria-live": "polite", children: [_jsx(Loader2, { className: "text-muted-foreground h-5 w-5 animate-spin", "aria-hidden": true }), _jsx("span", { className: "sr-only", children: "Loading entry\u2026" })] })) : error ? (_jsx(FormsErrorState, { message: error })) : entry ? (_jsxs("div", { className: "space-y-6", children: [_jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: senderName }), entry.senderEmail && (_jsx("a", { href: `mailto:${entry.senderEmail}`, className: "text-primary block text-sm hover:underline", children: entry.senderEmail })), entry.senderPhone && (_jsx("span", { className: "text-muted-foreground block text-sm", children: entry.senderPhone }))] }), _jsx(FieldValues, { entry: entry, fields: detail.fields }), attrRows.length > 0 && (_jsxs("section", { children: [_jsx("h4", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase", children: "Attribution" }), _jsx("dl", { className: "divide-border divide-y text-sm", children: attrRows.map((row) => (_jsxs("div", { className: "flex justify-between gap-4 py-1.5", children: [_jsx("dt", { className: "text-muted-foreground shrink-0", children: row.label }), _jsx("dd", { className: "text-foreground min-w-0 truncate text-right", children: row.value })] }, row.label))) })] }))] })) : null }), _jsx(ConfirmDialog, { open: confirmDelete, onClose: () => setConfirmDelete(false), onConfirm: () => void handleDelete(), title: "Delete submission?", description: "This permanently removes the submission and any attached files. This cannot be undone.", confirmLabel: "Delete", destructive: true })] }));
132
- }
133
- /** Render the captured field values against the entry's schema. */
134
- function FieldValues({ entry, fields }) {
135
- // Prefer the schema order; fall back to whatever keys exist on the data.
136
- const inputFields = fields.filter((f) => f.type !== 'divider' && f.type !== 'richtext' && f.type !== 'honeypot');
137
- const rows = inputFields.length
138
- ? inputFields.map((f) => ({ key: f.key, label: f.label }))
139
- : Object.keys(entry.data ?? {}).map((k) => ({ key: k, label: k }));
140
- if (rows.length === 0) {
141
- return _jsx("p", { className: "text-muted-foreground text-sm", children: "No field data captured." });
142
- }
143
- return (_jsxs("section", { children: [_jsx("h4", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase", children: "Submission" }), _jsx("dl", { className: "space-y-3", children: rows.map((row) => (_jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground text-xs", children: row.label }), _jsx("dd", { className: "text-foreground mt-0.5 text-sm wrap-break-word whitespace-pre-wrap", children: displayValue(entry.data?.[row.key]) })] }, row.key))) })] }));
144
- }
145
- //# sourceMappingURL=EntryDetailDrawer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"EntryDetailDrawer.js","sourceRoot":"","sources":["../../../src/components/forms/EntryDetailDrawer.tsx"],"names":[],"mappings":"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"}
@@ -1,9 +0,0 @@
1
- import { type RedirectRule } from '../../lib/seo-service.js';
2
- export declare function RedirectEditorDrawer({ open, onOpenChange, rule, onSaved, }: {
3
- open: boolean;
4
- onOpenChange: (open: boolean) => void;
5
- /** null = create mode */
6
- rule: RedirectRule | null;
7
- onSaved?: () => void;
8
- }): import("react/jsx-runtime").JSX.Element;
9
- //# sourceMappingURL=RedirectEditorDrawer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RedirectEditorDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/seo/RedirectEditorDrawer.tsx"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,0BAA0B,CAAA;AAuBjC,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,yBAAyB;IACzB,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,2CA0LA"}