@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
@@ -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}
@@ -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"}
@@ -1,95 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useEffect, useState } from 'react';
4
- import { toast } from 'sonner';
5
- import { createRedirectRule, updateRedirectRule, } from '../../lib/seo-service.js';
6
- import { Drawer } from './Drawer.js';
7
- import { btnPrimary, btnSecondary } from './primitives.js';
8
- import { Toggle } from '../ui/Toggle.js';
9
- const STATUS_CODES = [
10
- { value: 301, label: '301 — Permanent' },
11
- { value: 302, label: '302 — Temporary' },
12
- { value: 307, label: '307 — Temporary (preserve method)' },
13
- { value: 308, label: '308 — Permanent (preserve method)' },
14
- { value: 410, label: '410 — Gone (no destination)' },
15
- ];
16
- const inputCls = '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';
17
- function normalizePath(p) {
18
- const t = p.trim();
19
- if (!t)
20
- return '';
21
- if (/^https?:\/\//i.test(t))
22
- return t;
23
- return t.startsWith('/') ? t : `/${t}`;
24
- }
25
- export function RedirectEditorDrawer({ open, onOpenChange, rule, onSaved, }) {
26
- const [form, setForm] = useState({
27
- type: 301,
28
- fromPath: '',
29
- toPath: '',
30
- enabled: true,
31
- preserveQuery: false,
32
- notes: '',
33
- });
34
- const [saving, setSaving] = useState(false);
35
- const [clientError, setClientError] = useState(null);
36
- useEffect(() => {
37
- if (!open)
38
- return;
39
- if (rule) {
40
- setForm({
41
- type: rule.type,
42
- fromPath: rule.fromPath,
43
- toPath: rule.toPath,
44
- enabled: rule.enabled,
45
- preserveQuery: rule.preserveQuery,
46
- notes: rule.notes ?? '',
47
- });
48
- }
49
- else {
50
- setForm({
51
- type: 301,
52
- fromPath: '',
53
- toPath: '',
54
- enabled: true,
55
- preserveQuery: false,
56
- notes: '',
57
- });
58
- }
59
- setClientError(null);
60
- }, [open, rule]);
61
- const isGone = form.type === 410;
62
- const update = (patch) => setForm((f) => ({ ...f, ...patch }));
63
- async function handleSave() {
64
- const fromPath = normalizePath(form.fromPath);
65
- const toPath = isGone ? '' : normalizePath(form.toPath);
66
- if (!fromPath) {
67
- setClientError('Source path is required.');
68
- return;
69
- }
70
- if (!isGone && !toPath) {
71
- setClientError('Destination is required for redirect status codes.');
72
- return;
73
- }
74
- if (!isGone && fromPath === toPath) {
75
- setClientError('Source and destination cannot be identical (would create a loop).');
76
- return;
77
- }
78
- setClientError(null);
79
- setSaving(true);
80
- const payload = { ...form, fromPath, toPath };
81
- const res = rule
82
- ? await updateRedirectRule(rule.id, payload)
83
- : await createRedirectRule(payload);
84
- setSaving(false);
85
- if (res.error) {
86
- toast.error(res.error);
87
- return;
88
- }
89
- toast.success(rule ? 'Redirect updated.' : 'Redirect created.');
90
- onOpenChange(false);
91
- onSaved?.();
92
- }
93
- return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, title: rule ? 'Edit redirect' : 'New redirect', width: "max-w-lg", footer: _jsxs(_Fragment, { children: [_jsx("button", { className: btnSecondary, onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }), _jsx("button", { className: btnPrimary, onClick: handleSave, disabled: saving, children: saving ? 'Saving…' : rule ? 'Save changes' : 'Create redirect' })] }), children: _jsxs("div", { className: "space-y-4", children: [clientError && (_jsx("p", { className: "text-destructive bg-destructive/10 rounded-md px-3 py-2 text-sm", role: "alert", children: clientError })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-type", children: "Status code" }), _jsx("select", { id: "redir-type", className: inputCls, value: form.type, onChange: (e) => update({ type: Number(e.target.value) }), children: STATUS_CODES.map((s) => (_jsx("option", { value: s.value, children: s.label }, s.value))) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-from", children: "Source path" }), _jsx("input", { id: "redir-from", className: inputCls, value: form.fromPath, onChange: (e) => update({ fromPath: e.target.value }), placeholder: "/old-path" })] }), !isGone && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-to", children: "Destination" }), _jsx("input", { id: "redir-to", className: inputCls, value: form.toPath, onChange: (e) => update({ toPath: e.target.value }), placeholder: "/new-path or https://example.com/page" })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsxs("label", { className: "text-foreground text-sm font-medium", htmlFor: "redir-notes", children: ["Notes ", _jsx("span", { className: "text-muted-foreground font-normal", children: "(optional)" })] }), _jsx("input", { id: "redir-notes", className: inputCls, value: form.notes ?? '', onChange: (e) => update({ notes: e.target.value }), placeholder: "Why this redirect exists" })] }), _jsxs("div", { className: "border-border space-y-3 rounded-lg border p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: "Enabled" }), _jsx(Toggle, { checked: !!form.enabled, onCheckedChange: (v) => update({ enabled: v }), variant: "enabled", "aria-label": "Enabled" })] }), !isGone && (_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: "Preserve query string" }), _jsx("span", { className: "text-muted-foreground block text-sm", children: "Append the original query params to the destination." })] }), _jsx(Toggle, { checked: !!form.preserveQuery, onCheckedChange: (v) => update({ preserveQuery: v }), "aria-label": "Preserve query string" })] }))] })] }) }));
94
- }
95
- //# sourceMappingURL=RedirectEditorDrawer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RedirectEditorDrawer.js","sourceRoot":"","sources":["../../../src/components/seo/RedirectEditorDrawer.tsx"],"names":[],"mappings":"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"}