@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
@@ -0,0 +1,71 @@
1
+ <script setup>
2
+ const t = useModuleT("forms");
3
+ const route = useRoute();
4
+ const paths = useFormsPaths();
5
+ const id = computed(() => String(route.params.id));
6
+ const { data: submissions, error } = await useFetch(() => `/api/forms/admin/forms/${id.value}/submissions`);
7
+ const color = { none: "neutral", pending: "warning", delivered: "success", failed: "error" };
8
+ const format = (iso) => new Date(iso).toLocaleString();
9
+ </script>
10
+
11
+ <template>
12
+ <UContainer class="space-y-6 py-10">
13
+ <div>
14
+ <NuxtLink
15
+ :to="paths('adminForm', { id })"
16
+ class="text-sm text-muted hover:underline"
17
+ >
18
+ ← {{ t("admin.edit", "Edit") }}
19
+ </NuxtLink>
20
+ <h1 class="text-3xl font-bold">
21
+ {{ t("admin.submissionsTitle", "Submissions") }}
22
+ </h1>
23
+ </div>
24
+
25
+ <UAlert
26
+ v-if="error"
27
+ color="error"
28
+ variant="subtle"
29
+ :title="t('admin.forbidden', 'You are not allowed to manage forms.')"
30
+ />
31
+ <UEmpty
32
+ v-else-if="!submissions?.length"
33
+ icon="i-lucide-inbox"
34
+ :title="t('admin.noSubmissions', 'No submissions yet.')"
35
+ />
36
+
37
+ <div
38
+ v-else
39
+ class="divide-y divide-default rounded-lg border border-default"
40
+ >
41
+ <NuxtLink
42
+ v-for="submission in submissions"
43
+ :key="submission.id"
44
+ :to="paths('adminSubmission', { id, submissionId: submission.id })"
45
+ class="flex flex-wrap items-center justify-between gap-3 p-4 hover:bg-elevated"
46
+ >
47
+ <div class="min-w-0 space-y-1">
48
+ <p class="text-sm text-muted">
49
+ {{ format(submission.createdAt) }}
50
+ </p>
51
+ <p class="truncate">
52
+ <span
53
+ v-for="(answer, index) in submission.preview"
54
+ :key="answer.label"
55
+ >
56
+ <span
57
+ v-if="index"
58
+ class="text-muted"
59
+ > · </span>{{ answer.value }}
60
+ </span>
61
+ </p>
62
+ </div>
63
+ <UBadge
64
+ :color="color[submission.deliveryStatus]"
65
+ variant="subtle"
66
+ :label="t(`delivery.${submission.deliveryStatus}`, submission.deliveryStatus)"
67
+ />
68
+ </NuxtLink>
69
+ </div>
70
+ </UContainer>
71
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,194 @@
1
+ <script setup>
2
+ import { defineForm } from "../../utils";
3
+ const t = useModuleT("forms");
4
+ const paths = useFormsPaths();
5
+ const toast = useToast();
6
+ const { data: forms, error, refresh } = await useFetch("/api/forms/admin/forms");
7
+ const starter = defineForm({
8
+ mode: "single",
9
+ storage: "db",
10
+ steps: [{
11
+ id: "main",
12
+ fields: [
13
+ { key: "name", type: "text", label: "Name", required: true },
14
+ { key: "message", type: "textarea", label: "Message", required: true }
15
+ ]
16
+ }]
17
+ });
18
+ const creating = ref(false);
19
+ const draft = reactive({ name: "", slug: "" });
20
+ const importing = ref(false);
21
+ const importJson = ref("");
22
+ const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
23
+ watch(() => draft.name, (name, previous) => {
24
+ if (draft.slug === slugify(previous)) draft.slug = slugify(name);
25
+ });
26
+ async function create() {
27
+ try {
28
+ const created = await $fetch("/api/forms/admin/forms", { method: "POST", body: { ...draft, definition: starter } });
29
+ await navigateTo(paths("adminForm", { id: created.id }));
30
+ } catch (cause) {
31
+ toast.add({ title: cause.statusMessage ?? t("error.generic", "Something went wrong. Please try again."), color: "error" });
32
+ }
33
+ }
34
+ async function runImport() {
35
+ try {
36
+ const parsed = JSON.parse(importJson.value);
37
+ const result = await $fetch("/api/forms/admin/import", {
38
+ method: "POST",
39
+ body: { forms: Array.isArray(parsed) ? parsed : [parsed] }
40
+ });
41
+ toast.add({ title: t("admin.imported", "Imported {inserted}, skipped {skipped} existing.", { inserted: result.inserted.length, skipped: result.skipped.length }), color: "success" });
42
+ importing.value = false;
43
+ importJson.value = "";
44
+ await refresh();
45
+ } catch (cause) {
46
+ toast.add({ title: cause.statusMessage ?? t("admin.invalidJson", "The JSON is not valid."), color: "error" });
47
+ }
48
+ }
49
+ const statusColor = { draft: "neutral", published: "success", archived: "warning" };
50
+ </script>
51
+
52
+ <template>
53
+ <UContainer class="space-y-6 py-10">
54
+ <div class="flex items-center justify-between gap-2">
55
+ <h1 class="text-3xl font-bold">
56
+ {{ t("admin.title", "Forms") }}
57
+ </h1>
58
+ <div class="flex gap-2">
59
+ <UButton
60
+ color="neutral"
61
+ variant="outline"
62
+ icon="i-lucide-upload"
63
+ :label="t('admin.import', 'Import')"
64
+ @click="importing = true"
65
+ />
66
+ <UButton
67
+ icon="i-lucide-plus"
68
+ :label="t('admin.new', 'New form')"
69
+ @click="creating = true"
70
+ />
71
+ </div>
72
+ </div>
73
+
74
+ <UAlert
75
+ v-if="error"
76
+ color="error"
77
+ variant="subtle"
78
+ :title="t('admin.forbidden', 'You are not allowed to manage forms.')"
79
+ />
80
+
81
+ <UEmpty
82
+ v-else-if="!forms?.length"
83
+ icon="i-lucide-clipboard-list"
84
+ :title="t('admin.empty', 'No forms yet.')"
85
+ />
86
+
87
+ <div
88
+ v-else
89
+ class="divide-y divide-default rounded-lg border border-default"
90
+ >
91
+ <div
92
+ v-for="form in forms"
93
+ :key="form.id"
94
+ class="flex flex-wrap items-center justify-between gap-3 p-4"
95
+ >
96
+ <div class="space-y-1">
97
+ <NuxtLink
98
+ :to="paths('adminForm', { id: form.id })"
99
+ class="font-medium hover:underline"
100
+ >
101
+ {{ form.name }}
102
+ </NuxtLink>
103
+ <p class="text-sm text-muted">
104
+ /{{ form.slug }} · {{ form.mode === "stepped" ? t("admin.stepped", "Stepped") : t("admin.single", "Single page") }}
105
+ · v{{ form.version }}
106
+ <template v-if="form.storage === 'none'">
107
+ · {{ t("admin.notStored", "Not stored") }}
108
+ </template>
109
+ </p>
110
+ </div>
111
+ <div class="flex items-center gap-3">
112
+ <UBadge
113
+ :color="statusColor[form.status]"
114
+ variant="subtle"
115
+ :label="t(`status.${form.status}`, form.status)"
116
+ />
117
+ <UButton
118
+ v-if="form.storage === 'db'"
119
+ color="neutral"
120
+ variant="outline"
121
+ size="sm"
122
+ :to="paths('adminSubmissions', { id: form.id })"
123
+ :label="t('admin.submissions', 'Submissions ({count})', { count: form.submissionCount })"
124
+ />
125
+ <UButton
126
+ color="neutral"
127
+ variant="ghost"
128
+ size="sm"
129
+ icon="i-lucide-pencil"
130
+ :to="paths('adminForm', { id: form.id })"
131
+ :aria-label="t('admin.edit', 'Edit')"
132
+ />
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <UModal
138
+ v-model:open="creating"
139
+ :title="t('admin.new', 'New form')"
140
+ >
141
+ <template #body>
142
+ <form
143
+ class="space-y-4"
144
+ @submit.prevent="create"
145
+ >
146
+ <UFormField :label="t('admin.name', 'Name')">
147
+ <UInput
148
+ v-model="draft.name"
149
+ class="w-full"
150
+ autofocus
151
+ />
152
+ </UFormField>
153
+ <UFormField
154
+ :label="t('admin.slug', 'Slug')"
155
+ :description="t('admin.slugHint', 'Used in the public address.')"
156
+ >
157
+ <UInput
158
+ v-model="draft.slug"
159
+ class="w-full"
160
+ />
161
+ </UFormField>
162
+ <UButton
163
+ type="submit"
164
+ :label="t('admin.create', 'Create')"
165
+ :disabled="!draft.name || !draft.slug"
166
+ />
167
+ </form>
168
+ </template>
169
+ </UModal>
170
+
171
+ <UModal
172
+ v-model:open="importing"
173
+ :title="t('admin.import', 'Import')"
174
+ >
175
+ <template #body>
176
+ <div class="space-y-4">
177
+ <p class="text-sm text-muted">
178
+ {{ t("admin.importHint", "Paste one form or a list of forms (each with slug, name and definition). Forms whose slug already exists are skipped.") }}
179
+ </p>
180
+ <UTextarea
181
+ v-model="importJson"
182
+ :rows="12"
183
+ class="w-full font-mono text-xs"
184
+ />
185
+ <UButton
186
+ :label="t('admin.import', 'Import')"
187
+ :disabled="!importJson.trim()"
188
+ @click="runImport"
189
+ />
190
+ </div>
191
+ </template>
192
+ </UModal>
193
+ </UContainer>
194
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,18 @@
1
+ <script setup>
2
+ const route = useRoute();
3
+ const slug = computed(() => String(route.params.slug));
4
+ const { data: form } = await useForm(slug);
5
+ useHead({ title: computed(() => form.value?.name ?? "") });
6
+ </script>
7
+
8
+ <template>
9
+ <UContainer class="max-w-2xl py-10">
10
+ <h1
11
+ v-if="form"
12
+ class="mb-6 text-3xl font-bold"
13
+ >
14
+ {{ form.name }}
15
+ </h1>
16
+ <FormRenderer :slug="slug" />
17
+ </UContainer>
18
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,84 @@
1
+ {
2
+ "forms": {
3
+ "actions": {
4
+ "next": "Næste",
5
+ "back": "Tilbage",
6
+ "submit": "Send",
7
+ "sendAnother": "Send en ny"
8
+ },
9
+ "step": {
10
+ "progress": "Trin {current} af {total}"
11
+ },
12
+ "success": {
13
+ "title": "Tak!",
14
+ "description": "Dine svar er sendt."
15
+ },
16
+ "error": {
17
+ "generic": "Noget gik galt. Prøv igen.",
18
+ "invalid": "Tjek de markerede felter.",
19
+ "notFound": "Formularen blev ikke fundet."
20
+ },
21
+ "preview": {
22
+ "notSent": "Forhåndsvisning: intet blev sendt."
23
+ },
24
+ "value": {
25
+ "yes": "Ja",
26
+ "no": "Nej"
27
+ },
28
+ "status": {
29
+ "draft": "Kladde",
30
+ "published": "Udgivet",
31
+ "archived": "Arkiveret"
32
+ },
33
+ "delivery": {
34
+ "none": "Ikke leveret",
35
+ "pending": "Afventer",
36
+ "delivered": "Leveret",
37
+ "failed": "Levering fejlede"
38
+ },
39
+ "validation": {
40
+ "required": "Feltet er påkrævet.",
41
+ "email": "Indtast en gyldig e-mailadresse.",
42
+ "tel": "Indtast et gyldigt telefonnummer.",
43
+ "date": "Indtast en gyldig dato.",
44
+ "option": "Vælg en af mulighederne.",
45
+ "min": "Skal være mindst {min}.",
46
+ "max": "Må højst være {max}.",
47
+ "minLength": "Indtast mindst {min} tegn.",
48
+ "maxLength": "Indtast højst {max} tegn.",
49
+ "pattern": "Værdien har et ugyldigt format.",
50
+ "maxFiles": "Upload højst {max} fil(er).",
51
+ "maxSize": "{name} er større end {max} MB.",
52
+ "accept": "{name} er ikke en tilladt filtype."
53
+ },
54
+ "admin": {
55
+ "title": "Formularer",
56
+ "new": "Ny formular",
57
+ "import": "Importér",
58
+ "edit": "Rediger",
59
+ "create": "Opret",
60
+ "save": "Gem",
61
+ "saved": "Gemt.",
62
+ "name": "Navn",
63
+ "slug": "Slug",
64
+ "slugHint": "Bruges i den offentlige adresse.",
65
+ "status": "Status",
66
+ "empty": "Ingen formularer endnu.",
67
+ "stepped": "Flere trin",
68
+ "single": "Én side",
69
+ "notStored": "Gemmes ikke",
70
+ "submissions": "Besvarelser ({count})",
71
+ "submissionsTitle": "Besvarelser",
72
+ "noSubmissions": "Ingen besvarelser endnu.",
73
+ "redeliver": "Lever igen",
74
+ "forbidden": "Du har ikke adgang til at administrere formularer.",
75
+ "imported": "Importerede {inserted}, sprang {skipped} eksisterende over.",
76
+ "invalidJson": "JSON er ugyldig.",
77
+ "importHint": "Indsæt én formular eller en liste af formularer (hver med slug, name og definition). Formularer med et eksisterende slug springes over.",
78
+ "definition": "Definition (JSON)",
79
+ "invalidDefinition": "Definitionen har fejl",
80
+ "preview": "Forhåndsvisning",
81
+ "previewUnavailable": "Ret definitionen for at se en forhåndsvisning."
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "forms": {
3
+ "actions": {
4
+ "next": "Next",
5
+ "back": "Back",
6
+ "submit": "Submit",
7
+ "sendAnother": "Send another"
8
+ },
9
+ "step": {
10
+ "progress": "Step {current} of {total}"
11
+ },
12
+ "success": {
13
+ "title": "Thank you!",
14
+ "description": "Your answers have been sent."
15
+ },
16
+ "error": {
17
+ "generic": "Something went wrong. Please try again.",
18
+ "invalid": "Please check the highlighted fields.",
19
+ "notFound": "This form could not be found."
20
+ },
21
+ "preview": {
22
+ "notSent": "Preview: nothing was sent."
23
+ },
24
+ "value": {
25
+ "yes": "Yes",
26
+ "no": "No"
27
+ },
28
+ "status": {
29
+ "draft": "Draft",
30
+ "published": "Published",
31
+ "archived": "Archived"
32
+ },
33
+ "delivery": {
34
+ "none": "Not delivered",
35
+ "pending": "Pending",
36
+ "delivered": "Delivered",
37
+ "failed": "Delivery failed"
38
+ },
39
+ "validation": {
40
+ "required": "This field is required.",
41
+ "email": "Enter a valid email address.",
42
+ "tel": "Enter a valid phone number.",
43
+ "date": "Enter a valid date.",
44
+ "option": "Choose one of the available options.",
45
+ "min": "Must be at least {min}.",
46
+ "max": "Must be at most {max}.",
47
+ "minLength": "Enter at least {min} characters.",
48
+ "maxLength": "Enter at most {max} characters.",
49
+ "pattern": "The value has an invalid format.",
50
+ "maxFiles": "Upload at most {max} file(s).",
51
+ "maxSize": "{name} is larger than {max} MB.",
52
+ "accept": "{name} is not an accepted file type."
53
+ },
54
+ "admin": {
55
+ "title": "Forms",
56
+ "new": "New form",
57
+ "import": "Import",
58
+ "edit": "Edit",
59
+ "create": "Create",
60
+ "save": "Save",
61
+ "saved": "Saved.",
62
+ "name": "Name",
63
+ "slug": "Slug",
64
+ "slugHint": "Used in the public address.",
65
+ "status": "Status",
66
+ "empty": "No forms yet.",
67
+ "stepped": "Stepped",
68
+ "single": "Single page",
69
+ "notStored": "Not stored",
70
+ "submissions": "Submissions ({count})",
71
+ "submissionsTitle": "Submissions",
72
+ "noSubmissions": "No submissions yet.",
73
+ "redeliver": "Deliver again",
74
+ "forbidden": "You are not allowed to manage forms.",
75
+ "imported": "Imported {inserted}, skipped {skipped} existing.",
76
+ "invalidJson": "The JSON is not valid.",
77
+ "importHint": "Paste one form or a list of forms (each with slug, name and definition). Forms whose slug already exists are skipped.",
78
+ "definition": "Definition (JSON)",
79
+ "invalidDefinition": "The definition has problems",
80
+ "preview": "Preview",
81
+ "previewUnavailable": "Fix the definition to see a preview."
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@frigear-nu/menu/types").MenuSource;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { defineMenuItem, defineMenuSource, extendMenuItem } from "@frigear-nu/menu/utils";
2
+ export default defineMenuSource(() => {
3
+ const config = useRuntimeConfig().public.forms;
4
+ if (config?.pages?.admin) {
5
+ extendMenuItem("dashboard.account", defineMenuItem({
6
+ id: "forms-admin",
7
+ label: "Forms",
8
+ description: "Manage forms and their submissions.",
9
+ icon: "i-lucide-clipboard-list",
10
+ to: config.pages.admin,
11
+ tags: ["admin"],
12
+ order: 90
13
+ }));
14
+ }
15
+ });