@frigear-nu/forms 0.0.0-stage → 0.0.2

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 (96) hide show
  1. package/README.md +22 -2
  2. package/dist/module.d.mts +281 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +102 -0
  5. package/dist/runtime/app/components/FormField.d.vue.ts +16 -0
  6. package/dist/runtime/app/components/FormField.vue +80 -0
  7. package/dist/runtime/app/components/FormField.vue.d.ts +16 -0
  8. package/dist/runtime/app/components/FormRenderer.d.vue.ts +19 -0
  9. package/dist/runtime/app/components/FormRenderer.vue +145 -0
  10. package/dist/runtime/app/components/FormRenderer.vue.d.ts +19 -0
  11. package/dist/runtime/app/components/FormSubmissionView.d.vue.ts +7 -0
  12. package/dist/runtime/app/components/FormSubmissionView.vue +77 -0
  13. package/dist/runtime/app/components/FormSubmissionView.vue.d.ts +7 -0
  14. package/dist/runtime/app/composables/index.d.ts +2 -0
  15. package/dist/runtime/app/composables/index.js +2 -0
  16. package/dist/runtime/app/composables/useFormStepper.d.ts +21 -0
  17. package/dist/runtime/app/composables/useFormStepper.js +33 -0
  18. package/dist/runtime/app/composables/useForms.d.ts +21 -0
  19. package/dist/runtime/app/composables/useForms.js +52 -0
  20. package/dist/runtime/app/pages/admin-form.d.vue.ts +3 -0
  21. package/dist/runtime/app/pages/admin-form.vue +150 -0
  22. package/dist/runtime/app/pages/admin-form.vue.d.ts +3 -0
  23. package/dist/runtime/app/pages/admin-submission.d.vue.ts +3 -0
  24. package/dist/runtime/app/pages/admin-submission.vue +59 -0
  25. package/dist/runtime/app/pages/admin-submission.vue.d.ts +3 -0
  26. package/dist/runtime/app/pages/admin-submissions.d.vue.ts +3 -0
  27. package/dist/runtime/app/pages/admin-submissions.vue +71 -0
  28. package/dist/runtime/app/pages/admin-submissions.vue.d.ts +3 -0
  29. package/dist/runtime/app/pages/admin.d.vue.ts +3 -0
  30. package/dist/runtime/app/pages/admin.vue +194 -0
  31. package/dist/runtime/app/pages/admin.vue.d.ts +3 -0
  32. package/dist/runtime/app/pages/form.d.vue.ts +3 -0
  33. package/dist/runtime/app/pages/form.vue +18 -0
  34. package/dist/runtime/app/pages/form.vue.d.ts +3 -0
  35. package/dist/runtime/i18n/lang/da.json +84 -0
  36. package/dist/runtime/i18n/lang/en.json +84 -0
  37. package/dist/runtime/menus.d.ts +2 -0
  38. package/dist/runtime/menus.js +15 -0
  39. package/dist/runtime/server/db/schema.sqlite.d.ts +504 -0
  40. package/dist/runtime/server/db/schema.sqlite.js +46 -0
  41. package/dist/runtime/server/plugins/email-delivery.d.ts +2 -0
  42. package/dist/runtime/server/plugins/email-delivery.js +21 -0
  43. package/dist/runtime/server/plugins/seed.d.ts +2 -0
  44. package/dist/runtime/server/plugins/seed.js +11 -0
  45. package/dist/runtime/server/routes/admin/form.delete.d.ts +2 -0
  46. package/dist/runtime/server/routes/admin/form.delete.js +8 -0
  47. package/dist/runtime/server/routes/admin/form.get.d.ts +2 -0
  48. package/dist/runtime/server/routes/admin/form.get.js +4 -0
  49. package/dist/runtime/server/routes/admin/form.patch.d.ts +2 -0
  50. package/dist/runtime/server/routes/admin/form.patch.js +17 -0
  51. package/dist/runtime/server/routes/admin/forms.get.d.ts +2 -0
  52. package/dist/runtime/server/routes/admin/forms.get.js +19 -0
  53. package/dist/runtime/server/routes/admin/forms.post.d.ts +2 -0
  54. package/dist/runtime/server/routes/admin/forms.post.js +13 -0
  55. package/dist/runtime/server/routes/admin/import.post.d.ts +2 -0
  56. package/dist/runtime/server/routes/admin/import.post.js +6 -0
  57. package/dist/runtime/server/routes/admin/submission-file.get.d.ts +2 -0
  58. package/dist/runtime/server/routes/admin/submission-file.get.js +17 -0
  59. package/dist/runtime/server/routes/admin/submission-redeliver.post.d.ts +2 -0
  60. package/dist/runtime/server/routes/admin/submission-redeliver.post.js +29 -0
  61. package/dist/runtime/server/routes/admin/submission.get.d.ts +2 -0
  62. package/dist/runtime/server/routes/admin/submission.get.js +22 -0
  63. package/dist/runtime/server/routes/admin/submissions.get.d.ts +2 -0
  64. package/dist/runtime/server/routes/admin/submissions.get.js +16 -0
  65. package/dist/runtime/server/routes/public/form.get.d.ts +2 -0
  66. package/dist/runtime/server/routes/public/form.get.js +3 -0
  67. package/dist/runtime/server/routes/public/submit.post.d.ts +2 -0
  68. package/dist/runtime/server/routes/public/submit.post.js +7 -0
  69. package/dist/runtime/server/utils/admin-schemas.d.ts +291 -0
  70. package/dist/runtime/server/utils/admin-schemas.js +14 -0
  71. package/dist/runtime/server/utils/auth.d.ts +11 -0
  72. package/dist/runtime/server/utils/auth.js +7 -0
  73. package/dist/runtime/server/utils/forms.d.ts +9 -0
  74. package/dist/runtime/server/utils/forms.js +30 -0
  75. package/dist/runtime/server/utils/rate-limit.d.ts +6 -0
  76. package/dist/runtime/server/utils/rate-limit.js +14 -0
  77. package/dist/runtime/server/utils/render.d.ts +11 -0
  78. package/dist/runtime/server/utils/render.js +26 -0
  79. package/dist/runtime/server/utils/seed.d.ts +9 -0
  80. package/dist/runtime/server/utils/seed.js +22 -0
  81. package/dist/runtime/server/utils/submit.d.ts +9 -0
  82. package/dist/runtime/server/utils/submit.js +120 -0
  83. package/dist/runtime/utils/conditions.d.ts +19 -0
  84. package/dist/runtime/utils/conditions.js +36 -0
  85. package/dist/runtime/utils/definition.d.ts +91 -0
  86. package/dist/runtime/utils/definition.js +103 -0
  87. package/dist/runtime/utils/index.d.ts +13 -0
  88. package/dist/runtime/utils/index.js +8 -0
  89. package/dist/runtime/utils/markdown.d.ts +5 -0
  90. package/dist/runtime/utils/markdown.js +5 -0
  91. package/dist/runtime/utils/schema.d.ts +27 -0
  92. package/dist/runtime/utils/schema.js +95 -0
  93. package/dist/runtime/utils/text.d.ts +12 -0
  94. package/dist/runtime/utils/text.js +12 -0
  95. package/dist/types.d.mts +3 -0
  96. package/package.json +86 -3
package/README.md CHANGED
@@ -1,3 +1,23 @@
1
- # Temporary Holding Version
1
+ # @frigear-nu/forms
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Dynamic, database-stored forms for the frigear ecosystem. A form is a JSON definition (single page or stepped,
4
+ with conditional fields), submissions are stored with a snapshot of the definition they were answered against, and
5
+ delivery (email, webhooks, ...) hangs off the `forms:submitted` Nitro hook.
6
+
7
+ Full documentation: `apps/docs/content/4.modules/26.forms.md`.
8
+
9
+ ```ts
10
+ export default defineNuxtConfig({
11
+ modules: ['@frigear-nu/forms'],
12
+ forms: {
13
+ settings: { pages: { form: true, admin: true, adminForm: true, adminSubmissions: true, adminSubmission: true } },
14
+ },
15
+ })
16
+ ```
17
+
18
+ ```ts
19
+ // server/plugins/forms-policy.ts - who may manage forms (denied by default)
20
+ export default defineNitroPlugin((nitro) => {
21
+ nitro.hooks.hook('forms:authorize', (ctx) => { ctx.allowed = isStaff(ctx.userId) })
22
+ })
23
+ ```
@@ -0,0 +1,281 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { H3Event } from 'h3';
3
+
4
+ /** A text that is either one string or a map of locale -> string. */
5
+ type Translated = string | Record<string, string>;
6
+ type FormStatus = 'draft' | 'published' | 'archived';
7
+ type FormMode = 'single' | 'stepped';
8
+ type FormStorage = 'db' | 'none';
9
+ type DeliveryStatus = 'none' | 'pending' | 'delivered' | 'failed';
10
+ type FieldType = 'text' | 'email' | 'tel' | 'number' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'date' | 'file' | 'markdown';
11
+ type ConditionOp = 'eq' | 'neq' | 'in' | 'truthy';
12
+ /** Shows a field (or step) only while the condition on another field's answer holds. */
13
+ type Condition = {
14
+ /** Key of the field whose answer is inspected. */
15
+ field: string;
16
+ op: ConditionOp;
17
+ /** Compared value for `eq`/`neq`; a list of accepted values for `in`. Unused for `truthy`. */
18
+ value?: unknown;
19
+ };
20
+ type FieldOption = {
21
+ value: string;
22
+ label: Translated;
23
+ };
24
+ type FieldValidation = {
25
+ min?: number;
26
+ max?: number;
27
+ minLength?: number;
28
+ maxLength?: number;
29
+ /** Regular expression source the (non-empty) value must match. */
30
+ pattern?: string;
31
+ /** Files only. */
32
+ maxFiles?: number;
33
+ /** Files only, bytes per file. */
34
+ maxSize?: number;
35
+ /** Files only, accepted MIME types or `type/*` wildcards. */
36
+ accept?: string[];
37
+ /** Replaces the default error message of every rule on this field. */
38
+ message?: Translated;
39
+ };
40
+ type FormField = {
41
+ /** Unique across the whole form; the key of the answer. */
42
+ key: string;
43
+ type: FieldType;
44
+ /** Markdown text for `markdown` fields (they collect no answer). */
45
+ content?: Translated;
46
+ label?: Translated;
47
+ description?: Translated;
48
+ placeholder?: Translated;
49
+ required?: boolean;
50
+ options?: FieldOption[];
51
+ /** `file` fields only: allow more than one file. */
52
+ multiple?: boolean;
53
+ /** Browser `autocomplete` token, e.g. `email`. */
54
+ autocomplete?: string;
55
+ validation?: FieldValidation;
56
+ showIf?: Condition;
57
+ };
58
+ type FormStep = {
59
+ /** Unique across the form. */
60
+ id: string;
61
+ title?: Translated;
62
+ description?: Translated;
63
+ showIf?: Condition;
64
+ fields: FormField[];
65
+ };
66
+ type FormDelivery = {
67
+ email?: {
68
+ /** Recipient addresses. */
69
+ to: string[];
70
+ subject?: Translated;
71
+ };
72
+ };
73
+ type FormSubmitSettings = {
74
+ label?: Translated;
75
+ successMessage?: Translated;
76
+ /** Same-origin path to go to after a successful submit. */
77
+ redirect?: string;
78
+ /** Offers "send another" after submit; these field keys stay filled in. */
79
+ allowResubmit?: {
80
+ keep: string[];
81
+ };
82
+ };
83
+ type FormDefinition = {
84
+ mode: FormMode;
85
+ /** `none` never stores answers (or files): they are only emitted through `forms:submitted` and emailed. */
86
+ storage: FormStorage;
87
+ steps: FormStep[];
88
+ submit?: FormSubmitSettings;
89
+ delivery?: FormDelivery;
90
+ };
91
+ type ModuleOptions = {
92
+ settings?: {
93
+ pages?: {
94
+ /** Public form page; must contain a `:slug` param. `true` = `/forms/:slug`. */
95
+ form?: string | boolean;
96
+ /** Admin list of forms. `true` = `/admin/forms`. */
97
+ admin?: string | boolean;
98
+ /** Admin form editor; must contain an `:id` param. `true` = `/admin/forms/:id`. */
99
+ adminForm?: string | boolean;
100
+ /** Admin submissions of one form; must contain an `:id` param. `true` = `/admin/forms/:id/submissions`. */
101
+ adminSubmissions?: string | boolean;
102
+ /** One submission; must contain `:id` and `:submissionId`. `true` = `/admin/forms/:id/submissions/:submissionId`. */
103
+ adminSubmission?: string | boolean;
104
+ };
105
+ /** Submissions allowed per client and form within `rateLimitWindowSeconds`. Defaults to `10`. `0` disables. */
106
+ rateLimit?: number;
107
+ /** Defaults to `600`. */
108
+ rateLimitWindowSeconds?: number;
109
+ };
110
+ /**
111
+ * Opt-in seeding. Nothing is ever inserted unless `enabled` is `true`; then only slugs that
112
+ * do not exist yet are inserted (existing forms are never overwritten).
113
+ */
114
+ seed?: {
115
+ enabled?: boolean;
116
+ forms?: SeedForm[];
117
+ };
118
+ };
119
+ type SeedForm = {
120
+ slug: string;
121
+ name: string;
122
+ status?: FormStatus;
123
+ definition: FormDefinition;
124
+ };
125
+ type FormsRuntimeConfig = {
126
+ rateLimit: number;
127
+ rateLimitWindowSeconds: number;
128
+ seed: {
129
+ enabled: boolean;
130
+ forms: SeedForm[];
131
+ };
132
+ };
133
+ type PublicModuleOptions = {
134
+ pages: {
135
+ form?: string;
136
+ admin?: string;
137
+ adminForm?: string;
138
+ adminSubmissions?: string;
139
+ adminSubmission?: string;
140
+ };
141
+ };
142
+ type AuthorizeContext = {
143
+ event: H3Event;
144
+ userId: number;
145
+ allowed: boolean;
146
+ };
147
+ type SubmittedFile = {
148
+ fieldKey: string;
149
+ filename: string;
150
+ contentType: string;
151
+ size: number;
152
+ /** Blob pathname; `null` when the form does not store (`storage: 'none'` never has files). */
153
+ pathname: string | null;
154
+ };
155
+ type SubmittedContext = {
156
+ event: H3Event;
157
+ form: {
158
+ id: string;
159
+ slug: string;
160
+ name: string;
161
+ version: number;
162
+ definition: FormDefinition;
163
+ };
164
+ /** Validated answers keyed by field key. File fields hold the file names. */
165
+ answers: Record<string, unknown>;
166
+ files: SubmittedFile[];
167
+ locale: string;
168
+ userId: number | null;
169
+ /** `null` when the form has `storage: 'none'`. */
170
+ submissionId: string | null;
171
+ stored: boolean;
172
+ /** Set by a handler to report that it delivered the submission (email delivery does). */
173
+ delivered: boolean;
174
+ };
175
+ type DeliveredContext = {
176
+ submissionId: string | null;
177
+ formSlug: string;
178
+ status: 'delivered' | 'failed';
179
+ error?: string;
180
+ };
181
+ type PublicFormDto = {
182
+ id: string;
183
+ slug: string;
184
+ name: string;
185
+ version: number;
186
+ /** The definition without `delivery` (recipients are never public). */
187
+ definition: Omit<FormDefinition, 'delivery'>;
188
+ };
189
+ type FormSummaryDto = {
190
+ id: string;
191
+ slug: string;
192
+ name: string;
193
+ status: FormStatus;
194
+ version: number;
195
+ storage: FormStorage;
196
+ mode: FormMode;
197
+ submissionCount: number;
198
+ updatedAt: string;
199
+ };
200
+ type FormAdminDto = {
201
+ id: string;
202
+ slug: string;
203
+ name: string;
204
+ status: FormStatus;
205
+ version: number;
206
+ definition: FormDefinition;
207
+ createdAt: string;
208
+ updatedAt: string;
209
+ };
210
+ type SubmissionSummaryDto = {
211
+ id: string;
212
+ createdAt: string;
213
+ locale: string;
214
+ userId: number | null;
215
+ deliveryStatus: DeliveryStatus;
216
+ /** First few answers as `label: value`, for the list view. */
217
+ preview: {
218
+ label: string;
219
+ value: string;
220
+ }[];
221
+ };
222
+ type SubmissionFileDto = {
223
+ id: string;
224
+ fieldKey: string;
225
+ filename: string;
226
+ contentType: string;
227
+ size: number;
228
+ };
229
+ type SubmissionDto = {
230
+ id: string;
231
+ formId: string;
232
+ formSlug: string;
233
+ formVersion: number;
234
+ definition: FormDefinition;
235
+ answers: Record<string, unknown>;
236
+ files: SubmissionFileDto[];
237
+ locale: string;
238
+ userId: number | null;
239
+ deliveryStatus: DeliveryStatus;
240
+ deliveryError: string | null;
241
+ createdAt: string;
242
+ };
243
+ type SubmitResponse = {
244
+ ok: true;
245
+ id: string | null;
246
+ };
247
+ /** Descriptor of a picked/uploaded file, as validated on both client and server. */
248
+ type FileDescriptor = {
249
+ name: string;
250
+ size: number;
251
+ type: string;
252
+ };
253
+
254
+ declare module '@nuxt/schema' {
255
+ interface NuxtConfig {
256
+ forms?: ModuleOptions;
257
+ }
258
+ interface RuntimeConfig {
259
+ forms: FormsRuntimeConfig;
260
+ }
261
+ interface PublicRuntimeConfig {
262
+ forms: PublicModuleOptions;
263
+ }
264
+ }
265
+ declare module 'nitropack' {
266
+ interface NitroRuntimeHooks {
267
+ /** Staff gate (definitions, submissions, files): set `ctx.allowed = true` to let the user in. Denied by default. */
268
+ 'forms:authorize': (ctx: AuthorizeContext) => void | Promise<void>;
269
+ /**
270
+ * A valid submission was received (and stored, unless the form has `storage: 'none'`). Deliver it
271
+ * here (webhooks, CRM, ...); set `ctx.delivered = true` once done. Throwing marks delivery failed.
272
+ */
273
+ 'forms:submitted': (ctx: SubmittedContext) => void | Promise<void>;
274
+ /** Delivery of a submission finished (successfully or not). */
275
+ 'forms:delivered': (ctx: DeliveredContext) => void | Promise<void>;
276
+ }
277
+ }
278
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
279
+
280
+ export { _default as default };
281
+ export type { AuthorizeContext, Condition, ConditionOp, DeliveredContext, DeliveryStatus, FieldOption, FieldType, FieldValidation, FileDescriptor, FormAdminDto, FormDefinition, FormDelivery, FormField, FormMode, FormStatus, FormStep, FormStorage, FormSubmitSettings, FormSummaryDto, FormsRuntimeConfig, ModuleOptions, PublicFormDto, PublicModuleOptions, SeedForm, SubmissionDto, SubmissionFileDto, SubmissionSummaryDto, SubmitResponse, SubmittedContext, SubmittedFile, Translated };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@frigear-nu/forms",
3
+ "configKey": "forms",
4
+ "version": "0.0.2",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.3",
7
+ "unbuild": "unknown"
8
+ }
9
+ }
@@ -0,0 +1,102 @@
1
+ import { defineNuxtModule, createResolver, hasNuxtModule, addServerImportsDir, addServerPlugin, addImports, addComponentsDir, extendPages, addServerHandler } from '@nuxt/kit';
2
+ import { registerModule, registerLocales, registerDatabaseTable, defineModulePage } from '@frigear-nu/module-kit/utils';
3
+
4
+ const MODULE_NAME = "@frigear-nu/forms";
5
+ function resolvePagePath(setting, defaultPath) {
6
+ if (typeof setting === "string") return setting;
7
+ if (setting === true) return defaultPath;
8
+ return void 0;
9
+ }
10
+ const module$1 = defineNuxtModule({
11
+ meta: { name: MODULE_NAME, configKey: "forms" },
12
+ defaults: {
13
+ settings: { pages: {}, rateLimit: 10, rateLimitWindowSeconds: 600 },
14
+ seed: { enabled: false, forms: [] }
15
+ },
16
+ moduleDependencies: {
17
+ "@nuxthub/core": {
18
+ version: ">=0.10",
19
+ defaults: { kv: true, blob: true }
20
+ },
21
+ "@frigear-nu/module-kit": { version: ">=0.0.1" },
22
+ "@frigear-nu/users": { version: ">=0.0.1" },
23
+ "@frigear-nu/notify": { version: ">=0.0.1" },
24
+ "@frigear-nu/menu": { version: ">=0.0.1", optional: true }
25
+ },
26
+ async setup(options, nuxt) {
27
+ registerModule(MODULE_NAME, nuxt);
28
+ const resolver = createResolver(import.meta.url);
29
+ registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
30
+ const settings = options.settings ?? {};
31
+ const withMenus = hasNuxtModule("@frigear-nu/menu");
32
+ const formPath = resolvePagePath(settings.pages?.form, "/forms/:slug");
33
+ const adminPath = resolvePagePath(settings.pages?.admin, "/admin/forms");
34
+ const adminFormPath = resolvePagePath(settings.pages?.adminForm, "/admin/forms/:id");
35
+ const adminSubmissionsPath = resolvePagePath(settings.pages?.adminSubmissions, "/admin/forms/:id/submissions");
36
+ const adminSubmissionPath = resolvePagePath(settings.pages?.adminSubmission, "/admin/forms/:id/submissions/:submissionId");
37
+ nuxt.options.runtimeConfig.forms = {
38
+ rateLimit: settings.rateLimit ?? 10,
39
+ rateLimitWindowSeconds: settings.rateLimitWindowSeconds ?? 600,
40
+ seed: { enabled: options.seed?.enabled ?? false, forms: options.seed?.forms ?? [] }
41
+ };
42
+ nuxt.options.runtimeConfig.public.forms = {
43
+ pages: {
44
+ form: formPath,
45
+ admin: adminPath,
46
+ adminForm: adminFormPath,
47
+ adminSubmissions: adminSubmissionsPath,
48
+ adminSubmission: adminSubmissionPath
49
+ }
50
+ };
51
+ await registerDatabaseTable(nuxt, {
52
+ resolver,
53
+ schema: (dialect) => `./runtime/server/db/schema.${dialect}`
54
+ });
55
+ const route = (method, path, file) => addServerHandler({ route: `/api/forms${path}`, method, handler: resolver.resolve(`./runtime/server/routes/${file}`) });
56
+ route("get", "/admin/forms", "admin/forms.get");
57
+ route("post", "/admin/forms", "admin/forms.post");
58
+ route("get", "/admin/forms/:id", "admin/form.get");
59
+ route("patch", "/admin/forms/:id", "admin/form.patch");
60
+ route("delete", "/admin/forms/:id", "admin/form.delete");
61
+ route("get", "/admin/forms/:id/submissions", "admin/submissions.get");
62
+ route("post", "/admin/import", "admin/import.post");
63
+ route("get", "/admin/submissions/:id", "admin/submission.get");
64
+ route("get", "/admin/submissions/:id/files/:fileId", "admin/submission-file.get");
65
+ route("post", "/admin/submissions/:id/redeliver", "admin/submission-redeliver.post");
66
+ route("get", "/:slug", "public/form.get");
67
+ route("post", "/:slug/submit", "public/submit.post");
68
+ addServerImportsDir(resolver.resolve("./runtime/server/utils"));
69
+ addServerPlugin(resolver.resolve("./runtime/server/plugins/email-delivery"));
70
+ addServerPlugin(resolver.resolve("./runtime/server/plugins/seed"));
71
+ addImports([
72
+ { name: "useForm", from: resolver.resolve("./runtime/app/composables/useForms") },
73
+ { name: "useFormsLocale", from: resolver.resolve("./runtime/app/composables/useForms") },
74
+ { name: "useFormsPaths", from: resolver.resolve("./runtime/app/composables/useForms") },
75
+ { name: "useFormStepper", from: resolver.resolve("./runtime/app/composables/useFormStepper") }
76
+ ]);
77
+ addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
78
+ const pages = [
79
+ { name: "forms-form", path: formPath, file: "form.vue" },
80
+ { name: "forms-admin", path: adminPath, file: "admin.vue" },
81
+ { name: "forms-admin-form", path: adminFormPath, file: "admin-form.vue" },
82
+ { name: "forms-admin-submissions", path: adminSubmissionsPath, file: "admin-submissions.vue" },
83
+ { name: "forms-admin-submission", path: adminSubmissionPath, file: "admin-submission.vue" }
84
+ ];
85
+ if (pages.some((page) => page.path)) {
86
+ extendPages((existing) => {
87
+ const taken = new Set(existing.map((page) => page.path));
88
+ for (const page of pages) {
89
+ if (page.path && !taken.has(page.path)) {
90
+ existing.push(defineModulePage(MODULE_NAME, { name: page.name, path: page.path, file: resolver.resolve(`./runtime/app/pages/${page.file}`) }));
91
+ }
92
+ }
93
+ });
94
+ }
95
+ if (withMenus) {
96
+ const { registerMenuSource } = await import('@frigear-nu/menu/utils');
97
+ registerMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "dashboard.account" });
98
+ }
99
+ }
100
+ });
101
+
102
+ export { module$1 as default };
@@ -0,0 +1,16 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ locale: string;
5
+ };
6
+ type __VLS_ModelProps = {
7
+ modelValue?: unknown;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:modelValue": (value: unknown) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
+ "onUpdate:modelValue"?: ((value: unknown) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,80 @@
1
+ <script setup>
2
+ import { renderMarkdown, translate } from "../../utils";
3
+ const props = defineProps({
4
+ field: { type: Object, required: true },
5
+ locale: { type: String, required: true }
6
+ });
7
+ const model = defineModel({ type: null });
8
+ const text = (value) => translate(value, props.locale) || void 0;
9
+ const items = computed(() => (props.field.options ?? []).map((option) => ({
10
+ value: option.value,
11
+ label: translate(option.label, props.locale) || option.value
12
+ })));
13
+ const html = computed(() => renderMarkdown(translate(props.field.content, props.locale)));
14
+ const isCheckbox = computed(() => props.field.type === "checkbox");
15
+ </script>
16
+
17
+ <template>
18
+ <!-- eslint-disable vue/no-v-html -- renderMarkdown escapes all input -->
19
+ <div
20
+ v-if="field.type === 'markdown'"
21
+ class="prose prose-sm max-w-none text-muted"
22
+ v-html="html"
23
+ />
24
+ <!-- eslint-enable vue/no-v-html -->
25
+
26
+ <UFormField
27
+ v-else
28
+ :name="field.key"
29
+ :label="isCheckbox ? void 0 : text(field.label)"
30
+ :description="isCheckbox ? void 0 : text(field.description)"
31
+ :required="field.required"
32
+ >
33
+ <UTextarea
34
+ v-if="field.type === 'textarea'"
35
+ v-model="model"
36
+ :placeholder="text(field.placeholder)"
37
+ :rows="5"
38
+ class="w-full"
39
+ />
40
+ <USelect
41
+ v-else-if="field.type === 'select'"
42
+ v-model="model"
43
+ :items="items"
44
+ :placeholder="text(field.placeholder)"
45
+ class="w-full"
46
+ />
47
+ <URadioGroup
48
+ v-else-if="field.type === 'radio'"
49
+ v-model="model"
50
+ :items="items"
51
+ />
52
+ <UCheckbox
53
+ v-else-if="field.type === 'checkbox'"
54
+ v-model="model"
55
+ :label="text(field.label)"
56
+ :description="text(field.description)"
57
+ />
58
+ <UInputNumber
59
+ v-else-if="field.type === 'number'"
60
+ v-model="model"
61
+ :placeholder="text(field.placeholder)"
62
+ class="w-full"
63
+ />
64
+ <UFileUpload
65
+ v-else-if="field.type === 'file'"
66
+ v-model="model"
67
+ :multiple="field.multiple"
68
+ :accept="field.validation?.accept?.join(',')"
69
+ class="w-full"
70
+ />
71
+ <UInput
72
+ v-else
73
+ v-model="model"
74
+ :type="field.type === 'date' ? 'date' : field.type === 'tel' ? 'tel' : field.type === 'email' ? 'email' : 'text'"
75
+ :autocomplete="field.autocomplete"
76
+ :placeholder="text(field.placeholder)"
77
+ class="w-full"
78
+ />
79
+ </UFormField>
80
+ </template>
@@ -0,0 +1,16 @@
1
+ import type { FormField } from '../../../types.js';
2
+ type __VLS_Props = {
3
+ field: FormField;
4
+ locale: string;
5
+ };
6
+ type __VLS_ModelProps = {
7
+ modelValue?: unknown;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:modelValue": (value: unknown) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
+ "onUpdate:modelValue"?: ((value: unknown) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import type { FormDefinition, PublicFormDto } from '../../../types.js';
2
+ type FormShape = Pick<PublicFormDto, 'slug' | 'definition'> & {
3
+ definition: Omit<FormDefinition, 'delivery'>;
4
+ };
5
+ type __VLS_Props = {
6
+ /** Slug of a published form (loaded from the API). */
7
+ slug?: string;
8
+ /** A definition to render directly (admin preview, tests). */
9
+ form?: FormShape;
10
+ /** Validates and steps like normal, but never posts. */
11
+ preview?: boolean;
12
+ };
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ submitted: (id: string | null) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onSubmitted?: ((id: string | null) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;