@actuate-media/cms-admin 0.21.1 → 0.22.0

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 (64) hide show
  1. package/dist/__tests__/layout/sidebar-forms-badge.render.test.d.ts +2 -0
  2. package/dist/__tests__/layout/sidebar-forms-badge.render.test.d.ts.map +1 -0
  3. package/dist/__tests__/layout/sidebar-forms-badge.render.test.js +30 -0
  4. package/dist/__tests__/layout/sidebar-forms-badge.render.test.js.map +1 -0
  5. package/dist/__tests__/layout/sidebar-submenu.render.test.js +8 -2
  6. package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
  7. package/dist/__tests__/views/forms-list.render.test.d.ts +2 -0
  8. package/dist/__tests__/views/forms-list.render.test.d.ts.map +1 -0
  9. package/dist/__tests__/views/forms-list.render.test.js +122 -0
  10. package/dist/__tests__/views/forms-list.render.test.js.map +1 -0
  11. package/dist/actuate-admin.css +1 -1
  12. package/dist/components/forms/Drawer.d.ts +13 -0
  13. package/dist/components/forms/Drawer.d.ts.map +1 -0
  14. package/dist/components/forms/Drawer.js +13 -0
  15. package/dist/components/forms/Drawer.js.map +1 -0
  16. package/dist/components/forms/EmbedPanel.d.ts +7 -0
  17. package/dist/components/forms/EmbedPanel.d.ts.map +1 -0
  18. package/dist/components/forms/EmbedPanel.js +91 -0
  19. package/dist/components/forms/EmbedPanel.js.map +1 -0
  20. package/dist/components/forms/FieldsPanel.d.ts +8 -0
  21. package/dist/components/forms/FieldsPanel.d.ts.map +1 -0
  22. package/dist/components/forms/FieldsPanel.js +123 -0
  23. package/dist/components/forms/FieldsPanel.js.map +1 -0
  24. package/dist/components/forms/FormSchemaDrawer.d.ts +9 -0
  25. package/dist/components/forms/FormSchemaDrawer.d.ts.map +1 -0
  26. package/dist/components/forms/FormSchemaDrawer.js +96 -0
  27. package/dist/components/forms/FormSchemaDrawer.js.map +1 -0
  28. package/dist/components/forms/NotificationsPanel.d.ts +6 -0
  29. package/dist/components/forms/NotificationsPanel.d.ts.map +1 -0
  30. package/dist/components/forms/NotificationsPanel.js +80 -0
  31. package/dist/components/forms/NotificationsPanel.js.map +1 -0
  32. package/dist/components/forms/primitives.d.ts +42 -0
  33. package/dist/components/forms/primitives.d.ts.map +1 -0
  34. package/dist/components/forms/primitives.js +96 -0
  35. package/dist/components/forms/primitives.js.map +1 -0
  36. package/dist/layout/Sidebar.d.ts +5 -0
  37. package/dist/layout/Sidebar.d.ts.map +1 -1
  38. package/dist/layout/Sidebar.js +43 -3
  39. package/dist/layout/Sidebar.js.map +1 -1
  40. package/dist/lib/forms-events.d.ts +17 -0
  41. package/dist/lib/forms-events.d.ts.map +1 -0
  42. package/dist/lib/forms-events.js +20 -0
  43. package/dist/lib/forms-events.js.map +1 -0
  44. package/dist/lib/forms-service.d.ts +80 -0
  45. package/dist/lib/forms-service.d.ts.map +1 -0
  46. package/dist/lib/forms-service.js +144 -0
  47. package/dist/lib/forms-service.js.map +1 -0
  48. package/dist/views/Forms.d.ts.map +1 -1
  49. package/dist/views/Forms.js +119 -17
  50. package/dist/views/Forms.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/__tests__/layout/sidebar-forms-badge.render.test.tsx +50 -0
  53. package/src/__tests__/layout/sidebar-submenu.render.test.tsx +9 -2
  54. package/src/__tests__/views/forms-list.render.test.tsx +141 -0
  55. package/src/components/forms/Drawer.tsx +70 -0
  56. package/src/components/forms/EmbedPanel.tsx +173 -0
  57. package/src/components/forms/FieldsPanel.tsx +385 -0
  58. package/src/components/forms/FormSchemaDrawer.tsx +185 -0
  59. package/src/components/forms/NotificationsPanel.tsx +240 -0
  60. package/src/components/forms/primitives.tsx +200 -0
  61. package/src/layout/Sidebar.tsx +72 -6
  62. package/src/lib/forms-events.ts +32 -0
  63. package/src/lib/forms-service.ts +244 -0
  64. package/src/views/Forms.tsx +343 -106
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Forms service — typed client over the cms-core Forms REST endpoints.
3
+ *
4
+ * Every Forms admin component reads/writes through this module (never raw
5
+ * `cmsApi` / `fetch`), so the network contract lives in one place. Read
6
+ * fetchers re-surface `cmsApi`'s `{ error }` as a thrown error (so the
7
+ * resource hook can show error/retry UI); mutations return `{ error? }`.
8
+ *
9
+ * The endpoint paths here are the contract implemented by cms-core
10
+ * `packages/cms-core/src/api/handlers.ts` (PR 2). Domain types are re-used
11
+ * from `@actuate-media/cms-core` so the admin can never drift from the server.
12
+ */
13
+ import { cmsApi } from './api.js'
14
+ import type {
15
+ FormDefinition,
16
+ FormSchemaVersion,
17
+ FormField,
18
+ FormNotificationSettings,
19
+ FormEmbedSettings,
20
+ FormStatus,
21
+ FormStats,
22
+ FormsSidebarCount,
23
+ FetchFormsParams,
24
+ } from '@actuate-media/cms-core'
25
+
26
+ export type {
27
+ FormDefinition,
28
+ FormSchemaVersion,
29
+ FormField,
30
+ FormFieldType,
31
+ FormFieldOption,
32
+ FieldValidationRules,
33
+ FormNotificationSettings,
34
+ FormEmbedSettings,
35
+ FormStatus,
36
+ FormStats,
37
+ FormsSidebarCount,
38
+ } from '@actuate-media/cms-core'
39
+
40
+ /**
41
+ * `cmsApi` never throws — it returns `{ error, status }` on a network/HTTP
42
+ * failure. Read (GET) fetchers must re-surface that as a thrown error so the
43
+ * resource hook can distinguish "request failed" from "empty result".
44
+ */
45
+ function throwIfError(res: { error?: string }): void {
46
+ if (res.error) throw new Error(res.error)
47
+ }
48
+
49
+ /**
50
+ * A form as returned by `GET /forms` — the typed definition plus the legacy
51
+ * numeric aliases the list endpoint adds for backward compatibility.
52
+ */
53
+ export interface FormListItem extends FormDefinition {
54
+ /** `fields.length` precomputed by the server. */
55
+ fieldCount?: number
56
+ /** Alias of `totalEntries`. */
57
+ submissions?: number
58
+ }
59
+
60
+ export interface FormSchemaResult {
61
+ fields: FormField[]
62
+ activeVersionId: string | null
63
+ versions: FormSchemaVersion[]
64
+ }
65
+
66
+ // ─── Forms list / stats / sidebar ────────────────────────────────────────
67
+
68
+ function buildFormsQuery(params: FetchFormsParams): string {
69
+ const qs = new URLSearchParams()
70
+ if (params.search) qs.set('search', params.search)
71
+ if (params.status) qs.set('status', params.status)
72
+ if (typeof params.notifyEnabled === 'boolean') {
73
+ qs.set('notifyEnabled', String(params.notifyEnabled))
74
+ }
75
+ if (params.sortBy) qs.set('sortBy', params.sortBy)
76
+ if (params.sortDirection) qs.set('sortDirection', params.sortDirection)
77
+ if (params.page) qs.set('page', String(params.page))
78
+ if (params.pageSize) qs.set('pageSize', String(params.pageSize))
79
+ const str = qs.toString()
80
+ return str ? `?${str}` : ''
81
+ }
82
+
83
+ export async function fetchForms(params: FetchFormsParams = {}): Promise<FormListItem[]> {
84
+ const res = await cmsApi<FormListItem[]>(`/forms${buildFormsQuery(params)}`)
85
+ throwIfError(res)
86
+ return res.data ?? []
87
+ }
88
+
89
+ export async function fetchFormById(id: string): Promise<FormDefinition | null> {
90
+ const res = await cmsApi<FormDefinition>(`/forms/${encodeURIComponent(id)}`)
91
+ throwIfError(res)
92
+ return res.data ?? null
93
+ }
94
+
95
+ const EMPTY_STATS: FormStats = {
96
+ totalForms: 0,
97
+ activeForms: 0,
98
+ totalEntries: 0,
99
+ unreadEntries: 0,
100
+ }
101
+
102
+ export async function fetchFormStats(): Promise<FormStats> {
103
+ const res = await cmsApi<FormStats>('/forms/stats')
104
+ throwIfError(res)
105
+ return res.data ?? EMPTY_STATS
106
+ }
107
+
108
+ export async function fetchFormsSidebarCounts(): Promise<FormsSidebarCount[]> {
109
+ const res = await cmsApi<FormsSidebarCount[]>('/forms/sidebar-counts')
110
+ throwIfError(res)
111
+ return res.data ?? []
112
+ }
113
+
114
+ // ─── Form mutations ──────────────────────────────────────────────────────
115
+
116
+ export interface CreateFormPayload {
117
+ name: string
118
+ slug?: string
119
+ description?: string
120
+ status?: FormStatus
121
+ template?: string
122
+ fields?: FormField[]
123
+ }
124
+
125
+ export async function createForm(
126
+ payload: CreateFormPayload,
127
+ ): Promise<{ data?: FormDefinition; error?: string }> {
128
+ const res = await cmsApi<FormDefinition>('/forms', {
129
+ method: 'POST',
130
+ body: JSON.stringify(payload),
131
+ })
132
+ return { data: res.data, error: res.error }
133
+ }
134
+
135
+ export async function updateForm(
136
+ id: string,
137
+ payload: Partial<Pick<FormDefinition, 'name' | 'slug' | 'description' | 'status'>>,
138
+ ): Promise<{ data?: FormDefinition; error?: string }> {
139
+ const res = await cmsApi<FormDefinition>(`/forms/${encodeURIComponent(id)}`, {
140
+ method: 'PATCH',
141
+ body: JSON.stringify(payload),
142
+ })
143
+ return { data: res.data, error: res.error }
144
+ }
145
+
146
+ export async function duplicateForm(
147
+ id: string,
148
+ ): Promise<{ data?: FormDefinition; error?: string }> {
149
+ const res = await cmsApi<FormDefinition>(`/forms/${encodeURIComponent(id)}/duplicate`, {
150
+ method: 'POST',
151
+ })
152
+ return { data: res.data, error: res.error }
153
+ }
154
+
155
+ /** Move a form to the archived state (hidden from embed pickers, no new submissions). */
156
+ export async function archiveForm(id: string): Promise<{ error?: string }> {
157
+ return updateForm(id, { status: 'archived' })
158
+ }
159
+
160
+ /** Restore an archived form back to draft. */
161
+ export async function restoreForm(id: string): Promise<{ error?: string }> {
162
+ return updateForm(id, { status: 'draft' })
163
+ }
164
+
165
+ export async function deleteForm(
166
+ id: string,
167
+ opts: { force?: boolean } = {},
168
+ ): Promise<{ error?: string }> {
169
+ const qs = opts.force ? '?force=true' : ''
170
+ const res = await cmsApi(`/forms/${encodeURIComponent(id)}${qs}`, { method: 'DELETE' })
171
+ return { error: res.error }
172
+ }
173
+
174
+ // ─── Schema ──────────────────────────────────────────────────────────────
175
+
176
+ export async function fetchFormSchema(id: string): Promise<FormSchemaResult> {
177
+ const res = await cmsApi<FormSchemaResult>(`/forms/${encodeURIComponent(id)}/schema`)
178
+ throwIfError(res)
179
+ return res.data ?? { fields: [], activeVersionId: null, versions: [] }
180
+ }
181
+
182
+ export async function saveFormSchema(
183
+ id: string,
184
+ fields: FormField[],
185
+ notes?: string,
186
+ ): Promise<{ data?: { form: FormDefinition; version: FormSchemaVersion }; error?: string }> {
187
+ const res = await cmsApi<{ form: FormDefinition; version: FormSchemaVersion }>(
188
+ `/forms/${encodeURIComponent(id)}/schema`,
189
+ { method: 'PUT', body: JSON.stringify({ fields, notes }) },
190
+ )
191
+ return { data: res.data, error: res.error }
192
+ }
193
+
194
+ // ─── Notifications ─────────────────────────────────────────────────────────
195
+
196
+ export async function fetchNotificationSettings(id: string): Promise<FormNotificationSettings> {
197
+ const res = await cmsApi<FormNotificationSettings>(
198
+ `/forms/${encodeURIComponent(id)}/notifications`,
199
+ )
200
+ throwIfError(res)
201
+ return res.data ?? { enabled: false, recipients: [] }
202
+ }
203
+
204
+ export async function updateNotificationSettings(
205
+ id: string,
206
+ payload: Partial<FormNotificationSettings>,
207
+ ): Promise<{ data?: FormNotificationSettings; error?: string }> {
208
+ const res = await cmsApi<FormNotificationSettings>(
209
+ `/forms/${encodeURIComponent(id)}/notifications`,
210
+ { method: 'PUT', body: JSON.stringify(payload) },
211
+ )
212
+ return { data: res.data, error: res.error }
213
+ }
214
+
215
+ /** Lightweight notify on/off toggle used by the forms-list switch. */
216
+ export async function setNotifyEnabled(
217
+ id: string,
218
+ enabled: boolean,
219
+ ): Promise<{ data?: { notifyEnabled: boolean }; error?: string }> {
220
+ const res = await cmsApi<{ notifyEnabled: boolean }>(`/forms/${encodeURIComponent(id)}/notify`, {
221
+ method: 'PUT',
222
+ body: JSON.stringify({ enabled }),
223
+ })
224
+ return { data: res.data, error: res.error }
225
+ }
226
+
227
+ // ─── Embed ───────────────────────────────────────────────────────────────
228
+
229
+ export async function fetchEmbedSettings(id: string): Promise<FormEmbedSettings> {
230
+ const res = await cmsApi<FormEmbedSettings>(`/forms/${encodeURIComponent(id)}/embed`)
231
+ throwIfError(res)
232
+ return res.data ?? {}
233
+ }
234
+
235
+ export async function updateEmbedSettings(
236
+ id: string,
237
+ payload: Partial<FormEmbedSettings>,
238
+ ): Promise<{ data?: FormEmbedSettings; error?: string }> {
239
+ const res = await cmsApi<FormEmbedSettings>(`/forms/${encodeURIComponent(id)}/embed`, {
240
+ method: 'PUT',
241
+ body: JSON.stringify(payload),
242
+ })
243
+ return { data: res.data, error: res.error }
244
+ }