domma-cms 0.9.10 → 0.12.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 (125) hide show
  1. package/CLAUDE.md +248 -159
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/api.js +1 -1
  4. package/admin/js/app.js +7 -3
  5. package/admin/js/config/sidebar-config.js +1 -1
  6. package/admin/js/http-interceptor.js +1 -0
  7. package/admin/js/lib/card-builder.js +2 -2
  8. package/admin/js/lib/markdown-toolbar.js +5 -5
  9. package/admin/js/lib/safe-html.js +1 -0
  10. package/admin/js/lib/shortcode-modal.js +1 -0
  11. package/admin/js/templates/layouts.html +5 -4
  12. package/admin/js/templates/notifications.html +14 -0
  13. package/admin/js/templates/plugin-marketplace.html +16 -0
  14. package/admin/js/templates/plugins.html +17 -5
  15. package/admin/js/views/index.js +1 -1
  16. package/admin/js/views/layouts.js +1 -16
  17. package/admin/js/views/notifications.js +1 -0
  18. package/admin/js/views/page-editor.js +37 -33
  19. package/admin/js/views/plugin-marketplace.js +1 -0
  20. package/admin/js/views/plugins.js +16 -16
  21. package/config/navigation.json +5 -72
  22. package/config/plugins.json +10 -14
  23. package/config/presets.json +50 -13
  24. package/config/site.json +11 -63
  25. package/package.json +2 -1
  26. package/plugins/_template/admin/templates/index.html +17 -0
  27. package/plugins/_template/admin/views/index.js +19 -0
  28. package/plugins/_template/config.js +8 -0
  29. package/plugins/_template/plugin.js +23 -0
  30. package/plugins/_template/plugin.json +34 -0
  31. package/plugins/analytics/plugin.json +41 -31
  32. package/plugins/blog/admin/templates/blog.html +22 -0
  33. package/plugins/blog/admin/templates/categories.html +7 -0
  34. package/plugins/blog/admin/templates/comments.html +11 -0
  35. package/plugins/blog/admin/templates/post-editor.html +97 -0
  36. package/plugins/blog/admin/templates/settings.html +11 -0
  37. package/plugins/blog/admin/views/blog.js +183 -0
  38. package/plugins/blog/admin/views/categories.js +235 -0
  39. package/plugins/blog/admin/views/comments.js +187 -0
  40. package/plugins/blog/admin/views/post-editor.js +291 -0
  41. package/plugins/blog/admin/views/settings.js +100 -0
  42. package/plugins/blog/collections/categories/schema.json +12 -0
  43. package/plugins/blog/collections/comments/schema.json +16 -0
  44. package/plugins/blog/collections/posts/schema.json +19 -0
  45. package/plugins/blog/config.js +8 -0
  46. package/plugins/blog/plugin.js +352 -0
  47. package/plugins/blog/plugin.json +96 -0
  48. package/plugins/blog/roles/blog-author.json +10 -0
  49. package/plugins/blog/roles/blog-editor.json +12 -0
  50. package/plugins/blog/templates/author.html +9 -0
  51. package/plugins/blog/templates/category.html +9 -0
  52. package/plugins/blog/templates/index.html +9 -0
  53. package/plugins/blog/templates/post.html +17 -0
  54. package/plugins/blog/templates/tag.html +9 -0
  55. package/plugins/contacts/collections/user-contact-groups/schema.json +1 -1
  56. package/plugins/contacts/collections/user-contacts/schema.json +1 -1
  57. package/plugins/contacts/plugin.js +4 -10
  58. package/plugins/contacts/plugin.json +13 -3
  59. package/plugins/notes/collections/user-notes/schema.json +1 -1
  60. package/plugins/notes/plugin.js +3 -9
  61. package/plugins/notes/plugin.json +13 -3
  62. package/plugins/site-search/plugin.json +5 -2
  63. package/plugins/theme-switcher/plugin.json +1 -1
  64. package/plugins/todo/collections/todos/schema.json +1 -1
  65. package/plugins/todo/plugin.js +3 -9
  66. package/plugins/todo/plugin.json +13 -3
  67. package/public/css/site.css +1 -1
  68. package/public/js/site.js +1 -1
  69. package/scripts/build.js +48 -0
  70. package/scripts/create-plugin.js +113 -0
  71. package/scripts/fresh.js +6 -7
  72. package/scripts/gen-instance-secret.js +46 -0
  73. package/scripts/reset.js +3 -3
  74. package/scripts/setup.js +31 -13
  75. package/server/middleware/auth.js +48 -0
  76. package/server/middleware/managerAuth.js +36 -0
  77. package/server/routes/api/actions.js +1 -1
  78. package/server/routes/api/auth.js +4 -3
  79. package/server/routes/api/layouts.js +173 -49
  80. package/server/routes/api/notifications.js +155 -0
  81. package/server/routes/api/plugin-marketplace.js +75 -0
  82. package/server/routes/api/users.js +1 -1
  83. package/server/routes/api/views.js +1 -1
  84. package/server/routes/public.js +4 -9
  85. package/server/server.js +32 -3
  86. package/server/services/actions.js +1 -1
  87. package/server/services/managerClient.js +182 -0
  88. package/server/services/markdown.js +76 -9
  89. package/server/services/permissionRegistry.js +245 -173
  90. package/server/services/pluginInstaller.js +301 -0
  91. package/server/services/plugins.js +117 -10
  92. package/server/services/presetCollections.js +66 -251
  93. package/server/services/renderer.js +99 -0
  94. package/server/services/roles.js +191 -39
  95. package/server/services/users.js +1 -1
  96. package/server/services/views.js +1 -1
  97. package/server/templates/page.html +2 -2
  98. package/plugins/docs/admin/templates/docs.html +0 -69
  99. package/plugins/docs/admin/views/docs.js +0 -276
  100. package/plugins/docs/config.js +0 -8
  101. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +0 -11
  102. package/plugins/docs/data/folders.json +0 -9
  103. package/plugins/docs/data/templates.json +0 -1
  104. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +0 -5
  105. package/plugins/docs/plugin.js +0 -375
  106. package/plugins/docs/plugin.json +0 -23
  107. package/plugins/form-builder/data/forms/contacts.json +0 -66
  108. package/plugins/form-builder/data/forms/enquiries.json +0 -103
  109. package/plugins/form-builder/data/forms/feedback.json +0 -131
  110. package/plugins/form-builder/data/forms/notes.json +0 -79
  111. package/plugins/form-builder/data/forms/to-do.json +0 -100
  112. package/plugins/form-builder/data/submissions/contacts.json +0 -1
  113. package/plugins/form-builder/data/submissions/enquiries.json +0 -1
  114. package/plugins/form-builder/data/submissions/feedback.json +0 -1
  115. package/plugins/form-builder/data/submissions/notes.json +0 -1
  116. package/plugins/form-builder/data/submissions/to-do.json +0 -1
  117. package/plugins/garage/admin/templates/garage.html +0 -111
  118. package/plugins/garage/admin/views/garage.js +0 -622
  119. package/plugins/garage/collections/garage-vehicles/schema.json +0 -101
  120. package/plugins/garage/config.js +0 -18
  121. package/plugins/garage/data/vehicles.json +0 -70
  122. package/plugins/garage/plugin.js +0 -398
  123. package/plugins/garage/plugin.json +0 -33
  124. package/scripts/seed.js +0 -1996
  125. package/server/services/userTypes.js +0 -227
@@ -1,9 +0,0 @@
1
- [
2
- {
3
- "id": "d62d4e48-39c8-4602-9c46-f5235a6f169c",
4
- "name": "Personal",
5
- "parentId": null,
6
- "userId": "2421ad8e-060d-4548-8878-af7011d5e08b",
7
- "createdAt": "2026-04-03T15:07:14.590Z"
8
- }
9
- ]
@@ -1 +0,0 @@
1
- []
@@ -1,5 +0,0 @@
1
- {
2
- "num": 1,
3
- "content": "",
4
- "createdAt": "2026-04-03T15:06:45.982Z"
5
- }
@@ -1,375 +0,0 @@
1
- import { createStore } from '../_lib/dataStore.js';
2
- import defaultConfig from './config.js';
3
- import { randomUUID } from 'crypto';
4
- import fs from 'fs/promises';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
-
10
- const dataDir = path.join(__dirname, 'data');
11
- const documentsDir = path.join(dataDir, 'documents');
12
- const versionsDir = path.join(dataDir, 'versions');
13
-
14
- const foldersStore = createStore(path.join(dataDir, 'folders.json'));
15
- const templatesStore = createStore(path.join(dataDir, 'templates.json'));
16
-
17
- function countWords(html) {
18
- if (!html) return 0;
19
- const text = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
20
- return text ? text.split(' ').length : 0;
21
- }
22
-
23
- async function readDoc(id) {
24
- try {
25
- const raw = await fs.readFile(path.join(documentsDir, `${id}.json`), 'utf8');
26
- return JSON.parse(raw);
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- async function writeDoc(doc) {
33
- await fs.mkdir(documentsDir, { recursive: true });
34
- await fs.writeFile(path.join(documentsDir, `${doc.id}.json`), JSON.stringify(doc, null, 2));
35
- }
36
-
37
- async function listDocs() {
38
- await fs.mkdir(documentsDir, { recursive: true });
39
- const files = await fs.readdir(documentsDir);
40
- const docs = [];
41
- for (const file of files) {
42
- if (!file.endsWith('.json')) continue;
43
- try {
44
- const raw = await fs.readFile(path.join(documentsDir, file), 'utf8');
45
- docs.push(JSON.parse(raw));
46
- } catch {
47
- // skip corrupt files
48
- }
49
- }
50
- return docs;
51
- }
52
-
53
- export default async function docsPlugin(fastify, options) {
54
- const prefix = '';
55
- const {authenticate} = options.auth;
56
- const config = {...defaultConfig, ...(options.settings || {})};
57
-
58
- // -------------------------
59
- // Documents
60
- // -------------------------
61
-
62
- // GET /documents
63
- fastify.get(`${prefix}/documents`, {preHandler: [authenticate]}, async (req, reply) => {
64
- const {folder, q} = req.query;
65
- let docs = await listDocs();
66
-
67
- if (config.scope === 'user') {
68
- docs = docs.filter(d => d.userId === req.user.id);
69
- }
70
-
71
- if (folder !== undefined) {
72
- docs = docs.filter(d => (folder === '' || folder === 'null' ? !d.folderId : d.folderId === folder));
73
- }
74
-
75
- if (q) {
76
- const term = q.toLowerCase();
77
- docs = docs.filter(d =>
78
- (d.title || '').toLowerCase().includes(term) ||
79
- (d.content || '').toLowerCase().includes(term)
80
- );
81
- }
82
-
83
- docs.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
84
- return reply.send(docs);
85
- });
86
-
87
- // POST /documents
88
- fastify.post(`${prefix}/documents`, { preHandler: [authenticate] }, async (req, reply) => {
89
- const { title = 'Untitled', content = '', folderId = null, tags = [] } = req.body || {};
90
- const now = new Date().toISOString();
91
- const doc = {
92
- id: randomUUID(),
93
- title,
94
- content,
95
- folderId: folderId || null,
96
- tags,
97
- userId: req.user.id,
98
- wordCount: countWords(content),
99
- createdAt: now,
100
- updatedAt: now
101
- };
102
- await writeDoc(doc);
103
- return reply.code(201).send(doc);
104
- });
105
-
106
- // GET /documents/:id
107
- fastify.get(`${prefix}/documents/:id`, {preHandler: [authenticate]}, async (req, reply) => {
108
- const doc = await readDoc(req.params.id);
109
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
110
- if (config.scope === 'user' && doc.userId !== req.user.id) {
111
- return reply.code(404).send({ error: 'Document not found' });
112
- }
113
- return reply.send(doc);
114
- });
115
-
116
- // PUT /documents/:id
117
- fastify.put(`${prefix}/documents/:id`, {preHandler: [authenticate]}, async (req, reply) => {
118
- const doc = await readDoc(req.params.id);
119
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
120
- if (config.scope === 'user' && doc.userId !== req.user.id) {
121
- return reply.code(404).send({ error: 'Document not found' });
122
- }
123
-
124
- const body = req.body || {};
125
-
126
- // Auto-create version if content changed
127
- if (body.content !== undefined && body.content !== doc.content) {
128
- const docVersionsDir = path.join(versionsDir, doc.id);
129
- await fs.mkdir(docVersionsDir, { recursive: true });
130
- const existing = await fs.readdir(docVersionsDir).catch(() => []);
131
- const nums = existing.map(f => parseInt(f)).filter(n => !isNaN(n));
132
- const nextNum = nums.length ? Math.max(...nums) + 1 : 1;
133
- if (nextNum <= (config.maxVersions || 50)) {
134
- await fs.writeFile(
135
- path.join(docVersionsDir, `${nextNum}.json`),
136
- JSON.stringify({ num: nextNum, content: doc.content, createdAt: new Date().toISOString() }, null, 2)
137
- );
138
- }
139
- }
140
-
141
- const updated = {
142
- ...doc,
143
- title: body.title !== undefined ? body.title : doc.title,
144
- content: body.content !== undefined ? body.content : doc.content,
145
- folderId: body.folderId !== undefined ? body.folderId : doc.folderId,
146
- tags: body.tags !== undefined ? body.tags : doc.tags,
147
- updatedAt: new Date().toISOString()
148
- };
149
- updated.wordCount = countWords(updated.content);
150
- await writeDoc(updated);
151
- return reply.send(updated);
152
- });
153
-
154
- // DELETE /documents/:id
155
- fastify.delete(`${prefix}/documents/:id`, {preHandler: [authenticate]}, async (req, reply) => {
156
- const doc = await readDoc(req.params.id);
157
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
158
- if (config.scope === 'user' && doc.userId !== req.user.id) {
159
- return reply.code(404).send({ error: 'Document not found' });
160
- }
161
- // Delete doc file
162
- await fs.rm(path.join(documentsDir, `${doc.id}.json`), { force: true });
163
- // Delete all versions
164
- await fs.rm(path.join(versionsDir, doc.id), { recursive: true, force: true });
165
- return reply.send({ ok: true });
166
- });
167
-
168
- // POST /documents/:id/duplicate
169
- fastify.post(`${prefix}/documents/:id/duplicate`, {preHandler: [authenticate]}, async (req, reply) => {
170
- const doc = await readDoc(req.params.id);
171
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
172
- if (config.scope === 'user' && doc.userId !== req.user.id) {
173
- return reply.code(404).send({ error: 'Document not found' });
174
- }
175
- const now = new Date().toISOString();
176
- const copy = {
177
- ...doc,
178
- id: randomUUID(),
179
- title: `${doc.title} (Copy)`,
180
- createdAt: now,
181
- updatedAt: now
182
- };
183
- await writeDoc(copy);
184
- return reply.code(201).send(copy);
185
- });
186
-
187
- // -------------------------
188
- // Versions
189
- // -------------------------
190
-
191
- // GET /documents/:id/versions
192
- fastify.get(`${prefix}/documents/:id/versions`, {preHandler: [authenticate]}, async (req, reply) => {
193
- const doc = await readDoc(req.params.id);
194
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
195
- if (config.scope === 'user' && doc.userId !== req.user.id) {
196
- return reply.code(404).send({ error: 'Document not found' });
197
- }
198
-
199
- const docVersionsDir = path.join(versionsDir, doc.id);
200
- const files = await fs.readdir(docVersionsDir).catch(() => []);
201
- const versions = [];
202
- for (const file of files) {
203
- if (!file.endsWith('.json')) continue;
204
- try {
205
- const raw = await fs.readFile(path.join(docVersionsDir, file), 'utf8');
206
- versions.push(JSON.parse(raw));
207
- } catch {
208
- // skip
209
- }
210
- }
211
- versions.sort((a, b) => b.num - a.num);
212
- return reply.send(versions);
213
- });
214
-
215
- // GET /documents/:id/versions/:num
216
- fastify.get(`${prefix}/documents/:id/versions/:num`, {preHandler: [authenticate]}, async (req, reply) => {
217
- const doc = await readDoc(req.params.id);
218
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
219
- if (config.scope === 'user' && doc.userId !== req.user.id) {
220
- return reply.code(404).send({ error: 'Document not found' });
221
- }
222
-
223
- const versionFile = path.join(versionsDir, doc.id, `${req.params.num}.json`);
224
- try {
225
- const raw = await fs.readFile(versionFile, 'utf8');
226
- return reply.send(JSON.parse(raw));
227
- } catch {
228
- return reply.code(404).send({ error: 'Version not found' });
229
- }
230
- });
231
-
232
- // POST /documents/:id/versions/:num/restore
233
- fastify.post(`${prefix}/documents/:id/versions/:num/restore`, {preHandler: [authenticate]}, async (req, reply) => {
234
- const doc = await readDoc(req.params.id);
235
- if (!doc) return reply.code(404).send({ error: 'Document not found' });
236
- if (config.scope === 'user' && doc.userId !== req.user.id) {
237
- return reply.code(404).send({ error: 'Document not found' });
238
- }
239
-
240
- const versionFile = path.join(versionsDir, doc.id, `${req.params.num}.json`);
241
- let version;
242
- try {
243
- const raw = await fs.readFile(versionFile, 'utf8');
244
- version = JSON.parse(raw);
245
- } catch {
246
- return reply.code(404).send({ error: 'Version not found' });
247
- }
248
-
249
- // Save current state as a new version first
250
- const docVersionsDir = path.join(versionsDir, doc.id);
251
- await fs.mkdir(docVersionsDir, { recursive: true });
252
- const existing = await fs.readdir(docVersionsDir).catch(() => []);
253
- const nums = existing.map(f => parseInt(f)).filter(n => !isNaN(n));
254
- const nextNum = nums.length ? Math.max(...nums) + 1 : 1;
255
- if (nextNum <= (config.maxVersions || 50)) {
256
- await fs.writeFile(
257
- path.join(docVersionsDir, `${nextNum}.json`),
258
- JSON.stringify({ num: nextNum, content: doc.content, createdAt: new Date().toISOString() }, null, 2)
259
- );
260
- }
261
-
262
- // Restore
263
- const restored = {
264
- ...doc,
265
- content: version.content,
266
- wordCount: countWords(version.content),
267
- updatedAt: new Date().toISOString()
268
- };
269
- await writeDoc(restored);
270
- return reply.send(restored);
271
- });
272
-
273
- // -------------------------
274
- // Folders
275
- // -------------------------
276
-
277
- // GET /folders
278
- fastify.get(`${prefix}/folders`, {preHandler: [authenticate]}, async (req, reply) => {
279
- let folders = await foldersStore.readArray();
280
- if (config.scope === 'user') {
281
- folders = folders.filter(f => f.userId === req.user.id);
282
- }
283
- return reply.send(folders);
284
- });
285
-
286
- // POST /folders
287
- fastify.post(`${prefix}/folders`, { preHandler: [authenticate] }, async (req, reply) => {
288
- const { name, parentId = null } = req.body || {};
289
- if (!name || !name.trim()) return reply.code(400).send({ error: 'Name is required' });
290
- const folder = {
291
- id: randomUUID(),
292
- name: name.trim(),
293
- parentId: parentId || null,
294
- userId: req.user.id,
295
- createdAt: new Date().toISOString()
296
- };
297
- await foldersStore.appendItem(folder);
298
- return reply.code(201).send(folder);
299
- });
300
-
301
- // PUT /folders/:id
302
- fastify.put(`${prefix}/folders/:id`, {preHandler: [authenticate]}, async (req, reply) => {
303
- const folders = await foldersStore.readArray();
304
- const folder = folders.find(f => f.id === req.params.id);
305
- if (!folder) return reply.code(404).send({ error: 'Folder not found' });
306
- if (config.scope === 'user' && folder.userId !== req.user.id) {
307
- return reply.code(404).send({ error: 'Folder not found' });
308
- }
309
- const { name } = req.body || {};
310
- if (!name || !name.trim()) return reply.code(400).send({ error: 'Name is required' });
311
- const updated = await foldersStore.updateItem(req.params.id, { name: name.trim() });
312
- return reply.send(updated);
313
- });
314
-
315
- // DELETE /folders/:id
316
- fastify.delete(`${prefix}/folders/:id`, {preHandler: [authenticate]}, async (req, reply) => {
317
- const folders = await foldersStore.readArray();
318
- const folder = folders.find(f => f.id === req.params.id);
319
- if (!folder) return reply.code(404).send({ error: 'Folder not found' });
320
- if (config.scope === 'user' && folder.userId !== req.user.id) {
321
- return reply.code(404).send({ error: 'Folder not found' });
322
- }
323
-
324
- // Move docs in this folder to root
325
- const docs = await listDocs();
326
- for (const doc of docs) {
327
- if (doc.folderId === req.params.id) {
328
- await writeDoc({ ...doc, folderId: null, updatedAt: new Date().toISOString() });
329
- }
330
- }
331
-
332
- await foldersStore.deleteItem(req.params.id);
333
- return reply.send({ ok: true });
334
- });
335
-
336
- // -------------------------
337
- // Templates
338
- // -------------------------
339
-
340
- // GET /templates
341
- fastify.get(`${prefix}/templates`, {preHandler: [authenticate]}, async (req, reply) => {
342
- let templates = await templatesStore.readArray();
343
- if (config.scope === 'user') {
344
- templates = templates.filter(t => t.userId === req.user.id);
345
- }
346
- return reply.send(templates);
347
- });
348
-
349
- // POST /templates
350
- fastify.post(`${prefix}/templates`, { preHandler: [authenticate] }, async (req, reply) => {
351
- const { name, content = '' } = req.body || {};
352
- if (!name || !name.trim()) return reply.code(400).send({ error: 'Name is required' });
353
- const template = {
354
- id: randomUUID(),
355
- name: name.trim(),
356
- content,
357
- userId: req.user.id,
358
- createdAt: new Date().toISOString()
359
- };
360
- await templatesStore.appendItem(template);
361
- return reply.code(201).send(template);
362
- });
363
-
364
- // DELETE /templates/:id
365
- fastify.delete(`${prefix}/templates/:id`, {preHandler: [authenticate]}, async (req, reply) => {
366
- const templates = await templatesStore.readArray();
367
- const template = templates.find(t => t.id === req.params.id);
368
- if (!template) return reply.code(404).send({ error: 'Template not found' });
369
- if (config.scope === 'user' && template.userId !== req.user.id) {
370
- return reply.code(404).send({ error: 'Template not found' });
371
- }
372
- await templatesStore.deleteItem(req.params.id);
373
- return reply.send({ ok: true });
374
- });
375
- }
@@ -1,23 +0,0 @@
1
- {
2
- "name": "docs",
3
- "displayName": "Docs",
4
- "version": "1.0.0",
5
- "description": "Document editor with folders, version history, templates, and find & replace.",
6
- "author": "Darryl Waterhouse",
7
- "date": "2026-03-24",
8
- "icon": "book-open",
9
- "admin": {
10
- "sidebar": [
11
- { "id": "docs", "text": "Docs", "icon": "book-open", "url": "#/plugins/docs", "section": "#/plugins/docs" }
12
- ],
13
- "routes": [
14
- { "path": "/plugins/docs", "view": "plugin-docs", "title": "Docs - Domma CMS" }
15
- ],
16
- "views": {
17
- "plugin-docs": {
18
- "entry": "docs/admin/views/docs.js",
19
- "exportName": "docsView"
20
- }
21
- }
22
- }
23
- }
@@ -1,66 +0,0 @@
1
- {
2
- "slug": "contacts",
3
- "title": "Contacts",
4
- "description": "Contact Information",
5
- "fields": [
6
- {
7
- "name": "full_name",
8
- "type": "string",
9
- "label": "Full Name",
10
- "required": false,
11
- "placeholder": "Full Name",
12
- "helper": "Full Name",
13
- "minLength": 8,
14
- "maxLength": 255
15
- },
16
- {
17
- "type": "spacer"
18
- },
19
- {
20
- "name": "phone_number",
21
- "type": "tel",
22
- "label": "Phone Number",
23
- "required": false,
24
- "placeholder": "Phone Number",
25
- "helper": "Primary Phone Number"
26
- },
27
- {
28
- "type": "spacer"
29
- },
30
- {
31
- "name": "email_address",
32
- "type": "string",
33
- "label": "Email Address",
34
- "required": false,
35
- "placeholder": "Email Address",
36
- "helper": "Email Address",
37
- "minLength": 8,
38
- "maxLength": 255
39
- }
40
- ],
41
- "settings": {
42
- "submitText": "Submit",
43
- "successMessage": "Thank you for your submission.",
44
- "layout": "stacked",
45
- "honeypot": true,
46
- "rateLimitPerMinute": 3
47
- },
48
- "actions": {
49
- "email": {
50
- "enabled": false,
51
- "recipients": "",
52
- "subjectPrefix": "[Contacts]"
53
- },
54
- "webhook": {
55
- "enabled": false,
56
- "url": "",
57
- "method": "POST"
58
- },
59
- "collection": {
60
- "enabled": true,
61
- "slug": "contacts"
62
- }
63
- },
64
- "createdAt": "2026-03-17T12:35:44.569Z",
65
- "updatedAt": "2026-03-17T12:35:44.569Z"
66
- }
@@ -1,103 +0,0 @@
1
- {
2
- "slug": "enquiries",
3
- "title": "Enquiries",
4
- "description": "Get in touch with us",
5
- "fields": [
6
- {
7
- "name": "full_name",
8
- "type": "string",
9
- "label": "Full Name",
10
- "required": true,
11
- "placeholder": "Your full name",
12
- "helper": "",
13
- "validation": {
14
- "min": 2,
15
- "max": 100
16
- }
17
- },
18
- {
19
- "name": "email",
20
- "type": "email",
21
- "label": "Email Address",
22
- "required": true,
23
- "placeholder": "your@email.com",
24
- "helper": ""
25
- },
26
- {
27
- "name": "phone",
28
- "type": "tel",
29
- "label": "Phone Number",
30
- "required": false,
31
- "placeholder": "+44 7700 000000",
32
- "helper": "Optional"
33
- },
34
- {
35
- "name": "subject",
36
- "type": "select",
37
- "label": "Subject",
38
- "required": true,
39
- "placeholder": "Please select a subject",
40
- "helper": "",
41
- "options": [
42
- {
43
- "value": "general",
44
- "label": "General Enquiry"
45
- },
46
- {
47
- "value": "support",
48
- "label": "Support"
49
- },
50
- {
51
- "value": "sales",
52
- "label": "Sales"
53
- },
54
- {
55
- "value": "partnership",
56
- "label": "Partnership"
57
- },
58
- {
59
- "value": "other",
60
- "label": "Other"
61
- }
62
- ]
63
- },
64
- {
65
- "name": "message",
66
- "type": "textarea",
67
- "label": "Message",
68
- "required": true,
69
- "placeholder": "How can we help you?",
70
- "helper": "",
71
- "rows": 4,
72
- "validation": {
73
- "min": 10,
74
- "max": 2000
75
- }
76
- }
77
- ],
78
- "settings": {
79
- "submitText": "Send Message",
80
- "successMessage": "Thanks for reaching out! We'll get back to you shortly.",
81
- "layout": "stacked",
82
- "honeypot": true,
83
- "rateLimitPerMinute": 3
84
- },
85
- "actions": {
86
- "email": {
87
- "enabled": false,
88
- "recipients": "",
89
- "subjectPrefix": "[enquiries]"
90
- },
91
- "webhook": {
92
- "enabled": false,
93
- "url": "",
94
- "method": "POST"
95
- },
96
- "collection": {
97
- "enabled": true,
98
- "slug": "enquiries"
99
- }
100
- },
101
- "createdAt": "2026-03-17T12:35:44.569Z",
102
- "updatedAt": "2026-03-17T12:35:44.569Z"
103
- }