@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
@@ -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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"}
@@ -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
- }