@frigear-nu/events 0.1.1 → 0.1.3

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 (97) hide show
  1. package/dist/module.d.mts +105 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +40 -31
  4. package/dist/runtime/app/components/EventAddonGroupSlideover.d.vue.ts +18 -0
  5. package/dist/runtime/app/components/EventAddonGroupSlideover.vue +66 -0
  6. package/dist/runtime/app/components/EventAddonGroupSlideover.vue.d.ts +18 -0
  7. package/dist/runtime/app/components/EventAddonSlideover.d.vue.ts +20 -0
  8. package/dist/runtime/app/components/EventAddonSlideover.vue +103 -0
  9. package/dist/runtime/app/components/EventAddonSlideover.vue.d.ts +20 -0
  10. package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +1 -9
  11. package/dist/runtime/app/components/EventAdminAddonGroups.vue +6 -48
  12. package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +1 -9
  13. package/dist/runtime/app/components/EventFormSlideover.vue +45 -23
  14. package/dist/runtime/app/components/EventTicketCard.vue +59 -62
  15. package/dist/runtime/app/components/EventTicketTypeSlideover.d.vue.ts +20 -0
  16. package/dist/runtime/app/components/EventTicketTypeSlideover.vue +176 -0
  17. package/dist/runtime/app/components/EventTicketTypeSlideover.vue.d.ts +20 -0
  18. package/dist/runtime/app/composables/index.d.ts +2 -0
  19. package/dist/runtime/app/composables/index.js +1 -0
  20. package/dist/runtime/app/composables/useEventActions.d.ts +3 -3
  21. package/dist/runtime/app/composables/useEventActions.js +9 -4
  22. package/dist/runtime/app/composables/useEventCatalogActions.d.ts +33 -0
  23. package/dist/runtime/app/composables/useEventCatalogActions.js +107 -0
  24. package/dist/runtime/app/composables/useEvents.d.ts +7 -6
  25. package/dist/runtime/app/composables/useEvents.js +17 -10
  26. package/dist/runtime/app/pages/admin-event.vue +71 -136
  27. package/dist/runtime/app/pages/admin.vue +91 -29
  28. package/dist/runtime/app/pages/event.vue +8 -4
  29. package/dist/runtime/app/pages/tickets.vue +24 -2
  30. package/dist/runtime/app/tabs/Attendees.vue +78 -39
  31. package/dist/runtime/app/tabs/Details.d.vue.ts +1 -1
  32. package/dist/runtime/app/tabs/Details.vue +112 -30
  33. package/dist/runtime/app/tabs/Details.vue.d.ts +1 -1
  34. package/dist/runtime/app/tabs/Tickets.vue +105 -132
  35. package/dist/runtime/app/utils/admin-event.d.ts +53 -38
  36. package/dist/runtime/app/utils/admin-event.js +14 -0
  37. package/dist/runtime/app/widgets/UpcomingEvents.vue +2 -1
  38. package/dist/runtime/app/widgets/UpcomingEventsStat.vue +2 -1
  39. package/dist/runtime/i18n/lang/da.json +24 -3
  40. package/dist/runtime/i18n/lang/en.json +24 -3
  41. package/dist/runtime/server/bootstrap/my-tickets.d.ts +7 -0
  42. package/dist/runtime/server/{routes/public/tickets-me.get.js → bootstrap/my-tickets.js} +2 -2
  43. package/dist/runtime/server/routes/admin/addon-group.delete.js +6 -3
  44. package/dist/runtime/server/routes/admin/addon-group.patch.js +6 -4
  45. package/dist/runtime/server/routes/admin/addon-groups.post.js +8 -6
  46. package/dist/runtime/server/routes/admin/addon.delete.js +6 -3
  47. package/dist/runtime/server/routes/admin/addon.patch.js +8 -5
  48. package/dist/runtime/server/routes/admin/addons.post.js +8 -6
  49. package/dist/runtime/server/routes/admin/attendees.get.js +38 -8
  50. package/dist/runtime/server/routes/{public → admin}/check-in.post.js +4 -3
  51. package/dist/runtime/server/routes/admin/event-clone.post.js +37 -19
  52. package/dist/runtime/server/routes/admin/event.delete.js +7 -4
  53. package/dist/runtime/server/routes/admin/event.get.js +4 -2
  54. package/dist/runtime/server/routes/admin/event.patch.js +4 -2
  55. package/dist/runtime/server/routes/admin/events.get.js +36 -3
  56. package/dist/runtime/server/routes/admin/events.post.js +3 -4
  57. package/dist/runtime/server/routes/admin/membership-options.get.js +3 -2
  58. package/dist/runtime/server/routes/admin/ticket-cancel.post.js +7 -8
  59. package/dist/runtime/server/routes/admin/ticket-refund.post.js +7 -5
  60. package/dist/runtime/server/routes/admin/ticket-type.delete.js +7 -4
  61. package/dist/runtime/server/routes/admin/ticket-type.patch.js +4 -2
  62. package/dist/runtime/server/routes/admin/ticket-types.post.js +10 -7
  63. package/dist/runtime/server/routes/admin/upcoming.get.js +7 -3
  64. package/dist/runtime/server/routes/me/ticket-pay.post.js +13 -0
  65. package/dist/runtime/server/routes/me/ticket.get.js +6 -0
  66. package/dist/runtime/server/routes/me/tickets.get.js +2 -0
  67. package/dist/runtime/server/routes/public/event.get.js +5 -2
  68. package/dist/runtime/server/routes/public/purchase.post.js +3 -1
  69. package/dist/runtime/server/utils/admin-dto.d.ts +30 -0
  70. package/dist/runtime/server/utils/admin-dto.js +101 -0
  71. package/dist/runtime/server/utils/admin-schemas.js +2 -1
  72. package/dist/runtime/server/utils/admin.d.ts +3 -1
  73. package/dist/runtime/server/utils/admin.js +20 -6
  74. package/dist/runtime/server/utils/auth.d.ts +5 -4
  75. package/dist/runtime/server/utils/auth.js +6 -7
  76. package/dist/runtime/server/utils/tickets.js +6 -11
  77. package/dist/runtime/utils/api.d.ts +48 -0
  78. package/dist/runtime/utils/api.js +51 -0
  79. package/dist/runtime/utils/index.d.ts +2 -1
  80. package/dist/runtime/utils/index.js +1 -0
  81. package/dist/types.d.mts +1 -1
  82. package/package.json +21 -16
  83. package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +0 -11
  84. package/dist/runtime/app/components/EventAdminAddonDialog.vue +0 -112
  85. package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +0 -11
  86. package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +0 -11
  87. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +0 -78
  88. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +0 -11
  89. package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +0 -20
  90. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +0 -174
  91. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +0 -20
  92. package/dist/runtime/server/routes/public/ticket-pay.post.js +0 -10
  93. package/dist/runtime/server/routes/public/ticket.get.js +0 -4
  94. /package/dist/runtime/server/routes/{public → admin}/check-in.post.d.ts +0 -0
  95. /package/dist/runtime/server/routes/{public → me}/ticket-pay.post.d.ts +0 -0
  96. /package/dist/runtime/server/routes/{public → me}/ticket.get.d.ts +0 -0
  97. /package/dist/runtime/server/routes/{public/tickets-me.get.d.ts → me/tickets.get.d.ts} +0 -0
package/dist/module.d.mts CHANGED
@@ -2,6 +2,7 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { H3Event } from 'h3';
3
3
  import { Translated } from '@frigear-nu/ui/translated';
4
4
  export { Translated } from '@frigear-nu/ui/translated';
5
+ import { Schedule } from '@frigear-nu/module-kit/schedule';
5
6
 
6
7
  /** `null` = open to everyone, `'any'` = any active subscription, or a list of plan keys. */
7
8
  type MembershipRequirement = 'any' | string[] | null;
@@ -133,6 +134,109 @@ type CheckInResponse = {
133
134
  checkedInAt: string;
134
135
  ticket: TicketDto;
135
136
  };
137
+ /** An event as staff see it (`/api/admin/events`): every field, whatever its status. Dates are ISO strings. */
138
+ type AdminEventDto = {
139
+ id: string;
140
+ slug: string;
141
+ name: Translated;
142
+ description: Translated | null;
143
+ body: Translated | null;
144
+ image: string | null;
145
+ location: Translated | null;
146
+ locationUrl: string | null;
147
+ startsAt: string;
148
+ endsAt: string | null;
149
+ status: EventStatus;
150
+ requiresMembership: MembershipRequirement;
151
+ createdAt: string;
152
+ updatedAt: string;
153
+ };
154
+ /** A ticket type as staff see it, with its capacity, sale window and membership rules. */
155
+ type AdminTicketTypeDto = {
156
+ id: string;
157
+ eventId: string;
158
+ key: string;
159
+ name: Translated;
160
+ description: Translated | null;
161
+ price: number;
162
+ currency: string;
163
+ hidePrice: boolean;
164
+ capacity: number | null;
165
+ saleStartsAt: string | null;
166
+ saleEndsAt: string | null;
167
+ saleSchedule: Schedule | null;
168
+ maxPerUser: number;
169
+ requiresMembership: MembershipRequirement;
170
+ excludesMembership: MembershipRequirement;
171
+ isDefault: boolean;
172
+ sortOrder: number;
173
+ createdAt: string;
174
+ updatedAt: string;
175
+ };
176
+ /** An add-on group of a ticket type, as staff see it. */
177
+ type AdminAddonGroupDto = {
178
+ id: string;
179
+ ticketTypeId: string;
180
+ title: Translated;
181
+ required: boolean;
182
+ multiple: boolean;
183
+ sortOrder: number;
184
+ };
185
+ /** An add-on (one choice of a group), as staff see it. */
186
+ type AdminAddonDto = {
187
+ id: string;
188
+ groupId: string;
189
+ label: Translated;
190
+ description: Translated | null;
191
+ price: number;
192
+ capacity: number | null;
193
+ sortOrder: number;
194
+ };
195
+ /** One event with its whole catalogue (`GET /api/admin/events/:id`). */
196
+ type AdminEventDetailDto = AdminEventDto & {
197
+ ticketTypes: Array<AdminTicketTypeDto & {
198
+ addonGroups: Array<AdminAddonGroupDto & {
199
+ addons: AdminAddonDto[];
200
+ }>;
201
+ }>;
202
+ };
203
+ /** One ticket of an event with its holder (`GET /api/admin/events/:id/tickets`). `code` is there for staff check-in. */
204
+ type AttendeeDto = {
205
+ id: string;
206
+ eventId: string;
207
+ ticketTypeId: string;
208
+ userId: number;
209
+ status: TicketStatus;
210
+ addons: Array<{
211
+ id: string;
212
+ label: Translated;
213
+ price: number;
214
+ }>;
215
+ amount: number;
216
+ currency: string;
217
+ paymentId: string | null;
218
+ code: string;
219
+ locale: string | null;
220
+ checkedInAt: string | null;
221
+ createdAt: string;
222
+ userName: string | null;
223
+ userEmail: string | null;
224
+ };
225
+ /** The admin dashboard's upcoming events (`GET /api/admin/events/upcoming`). */
226
+ type UpcomingEventsDto = {
227
+ total: number;
228
+ items: Array<{
229
+ id: string;
230
+ name: Translated;
231
+ location: Translated | null;
232
+ startsAt: string;
233
+ }>;
234
+ };
235
+ /** What a membership provider offers (`GET /api/admin/events/membership-options`). */
236
+ type MembershipOptionsDto = {
237
+ available: boolean;
238
+ options: MembershipOption[];
239
+ };
136
240
  type PurchaseBeforeContext = {
137
241
  event: H3Event;
138
242
  userId: number;
@@ -203,4 +307,4 @@ declare module 'nitropack' {
203
307
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
204
308
 
205
309
  export { _default as default };
206
- export type { AddonDto, AddonGroupDto, AuthorizeContext, CheckInResponse, EventDetailDto, EventStatus, EventSummaryDto, EventsRuntimeConfig, MembershipCheckContext, MembershipOption, MembershipOptionsContext, MembershipRequirement, ModuleOptions, PublicModuleOptions, PurchaseBeforeContext, PurchaseBody, PurchaseResponse, TicketDto, TicketHookContext, TicketStatus, TicketTypeDto };
310
+ export type { AddonDto, AddonGroupDto, AdminAddonDto, AdminAddonGroupDto, AdminEventDetailDto, AdminEventDto, AdminTicketTypeDto, AttendeeDto, AuthorizeContext, CheckInResponse, EventDetailDto, EventStatus, EventSummaryDto, EventsRuntimeConfig, MembershipCheckContext, MembershipOption, MembershipOptionsContext, MembershipOptionsDto, MembershipRequirement, ModuleOptions, PublicModuleOptions, PurchaseBeforeContext, PurchaseBody, PurchaseResponse, TicketDto, TicketHookContext, TicketStatus, TicketTypeDto, UpcomingEventsDto };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frigear-nu/events",
3
3
  "configKey": "events",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { defineNuxtModule, createResolver, hasNuxtModule, addServerImportsDir, addServerPlugin, addImports, addComponentsDir, addServerHandler } from '@nuxt/kit';
2
- import { registerModule, registerTailwindSource, registerLocales, resolvePagePath, registerDatabaseTable, inlineModuleRuntime, addModulePages, addOptionalMenuSource } from '@frigear-nu/module-kit/utils';
1
+ import { defineNuxtModule, createResolver, hasNuxtModule, addServerImportsDir, addServerPlugin, addImports, addComponentsDir } from '@nuxt/kit';
2
+ import { registerModule, registerTailwindSource, registerLocales, resolvePagePath, registerDatabaseTable, addModuleRoutes, registerBootstrapProvider, inlineModuleRuntime, addModulePages, addOptionalMenuSource } from '@frigear-nu/module-kit/utils';
3
3
  import { adminRoute } from '@frigear-nu/admin/utils';
4
4
  import { addAuthScope } from '@frigear-nu/auth/utils';
5
5
  import { addMcpDefinitions } from '@frigear-nu/mcp/utils';
@@ -57,34 +57,42 @@ const module$1 = defineNuxtModule({
57
57
  resolver,
58
58
  schema: (dialect) => `./runtime/server/db/schema.${dialect}`
59
59
  });
60
- const route = (method, path, file) => addServerHandler({ route: `/api/events${path}`, method, handler: resolver.resolve(`./runtime/server/routes/${file}`) });
61
- route("get", "", "public/events.get");
62
- route("get", "/tickets/me", "public/tickets-me.get");
63
- route("get", "/tickets/:id", "public/ticket.get");
64
- route("post", "/tickets/:id/pay", "public/ticket-pay.post");
65
- route("post", "/check-in", "public/check-in.post");
66
- route("get", "/:slug", "public/event.get");
67
- route("post", "/:slug/purchase", "public/purchase.post");
68
- route("get", "/admin/membership-options", "admin/membership-options.get");
69
- route("get", "/admin/upcoming", "admin/upcoming.get");
70
- route("get", "/admin/events", "admin/events.get");
71
- route("get", "/admin/events/:id", "admin/event.get");
72
- route("post", "/admin/events", "admin/events.post");
73
- route("patch", "/admin/events/:id", "admin/event.patch");
74
- route("delete", "/admin/events/:id", "admin/event.delete");
75
- route("post", "/admin/events/:id/clone", "admin/event-clone.post");
76
- route("get", "/admin/events/:id/tickets", "admin/attendees.get");
77
- route("post", "/admin/events/:id/ticket-types", "admin/ticket-types.post");
78
- route("patch", "/admin/ticket-types/:id", "admin/ticket-type.patch");
79
- route("delete", "/admin/ticket-types/:id", "admin/ticket-type.delete");
80
- route("post", "/admin/ticket-types/:id/addon-groups", "admin/addon-groups.post");
81
- route("patch", "/admin/addon-groups/:id", "admin/addon-group.patch");
82
- route("delete", "/admin/addon-groups/:id", "admin/addon-group.delete");
83
- route("post", "/admin/addon-groups/:id/addons", "admin/addons.post");
84
- route("patch", "/admin/addons/:id", "admin/addon.patch");
85
- route("delete", "/admin/addons/:id", "admin/addon.delete");
86
- route("post", "/admin/tickets/:id/cancel", "admin/ticket-cancel.post");
87
- route("post", "/admin/tickets/:id/refund", "admin/ticket-refund.post");
60
+ addModuleRoutes(resolver, [
61
+ { method: "get", path: "", file: "public/events.get" },
62
+ { method: "get", path: "me/tickets", file: "me/tickets.get" },
63
+ { method: "get", path: "me/tickets/:id", file: "me/ticket.get" },
64
+ { method: "post", path: "me/tickets/:id/pay", file: "me/ticket-pay.post" },
65
+ { method: "get", path: ":slug", file: "public/event.get" },
66
+ { method: "post", path: ":slug/purchase", file: "public/purchase.post" }
67
+ ], { base: "/api/events" });
68
+ registerBootstrapProvider(nuxt, {
69
+ scope: "account",
70
+ key: "events:tickets:mine",
71
+ handler: resolver.resolve("./runtime/server/bootstrap/my-tickets")
72
+ });
73
+ addModuleRoutes(resolver, [
74
+ { method: "get", path: "", file: "admin/events.get" },
75
+ { method: "post", path: "", file: "admin/events.post" },
76
+ { method: "get", path: "membership-options", file: "admin/membership-options.get" },
77
+ { method: "get", path: "upcoming", file: "admin/upcoming.get" },
78
+ { method: "post", path: "check-in", file: "admin/check-in.post" },
79
+ { method: "get", path: ":id", file: "admin/event.get" },
80
+ { method: "patch", path: ":id", file: "admin/event.patch" },
81
+ { method: "delete", path: ":id", file: "admin/event.delete" },
82
+ { method: "post", path: ":id/clone", file: "admin/event-clone.post" },
83
+ { method: "get", path: ":id/tickets", file: "admin/attendees.get" },
84
+ { method: "post", path: ":id/ticket-types", file: "admin/ticket-types.post" },
85
+ { method: "patch", path: "ticket-types/:id", file: "admin/ticket-type.patch" },
86
+ { method: "delete", path: "ticket-types/:id", file: "admin/ticket-type.delete" },
87
+ { method: "post", path: "ticket-types/:id/addon-groups", file: "admin/addon-groups.post" },
88
+ { method: "patch", path: "addon-groups/:id", file: "admin/addon-group.patch" },
89
+ { method: "delete", path: "addon-groups/:id", file: "admin/addon-group.delete" },
90
+ { method: "post", path: "addon-groups/:id/addons", file: "admin/addons.post" },
91
+ { method: "patch", path: "addons/:id", file: "admin/addon.patch" },
92
+ { method: "delete", path: "addons/:id", file: "admin/addon.delete" },
93
+ { method: "post", path: "tickets/:id/cancel", file: "admin/ticket-cancel.post" },
94
+ { method: "post", path: "tickets/:id/refund", file: "admin/ticket-refund.post" }
95
+ ], { base: "/api/admin/events" });
88
96
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
89
97
  inlineModuleRuntime(nuxt, resolver);
90
98
  if (settings.mcp === true) addMcpDefinitions(nuxt, resolver.resolve("./runtime/server/mcp"));
@@ -103,7 +111,8 @@ const module$1 = defineNuxtModule({
103
111
  { name: "useEventsFallbackLocale", from: resolver.resolve("./runtime/app/composables/useEvents") },
104
112
  { name: "useEventsTranslate", from: resolver.resolve("./runtime/app/composables/useEvents") },
105
113
  { name: "useEventLinks", from: resolver.resolve("./runtime/app/composables/useEventLinks") },
106
- { name: "useEventActions", from: resolver.resolve("./runtime/app/composables/useEventActions") }
114
+ { name: "useEventActions", from: resolver.resolve("./runtime/app/composables/useEventActions") },
115
+ { name: "useEventCatalogActions", from: resolver.resolve("./runtime/app/composables/useEventCatalogActions") }
107
116
  ]);
108
117
  addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
109
118
  addModulePages(MODULE_NAME, resolver, [
@@ -0,0 +1,18 @@
1
+ import type { StaffAddonGroup } from '../utils/admin-event.js';
2
+ type __VLS_Props = {
3
+ /** The ticket type a new group is added to. */
4
+ ticketTypeId: string;
5
+ /** The group to edit; leave out to add one. */
6
+ group?: StaffAddonGroup;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ close: (saved: false | {
10
+ id: string;
11
+ }) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onClose?: ((saved: false | {
14
+ id: string;
15
+ }) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,66 @@
1
+ <script setup>
2
+ import { z } from "zod";
3
+ import { eventsApi } from "../../utils/api";
4
+ defineOptions({ name: "EventAddonGroupSlideover" });
5
+ const props = defineProps({
6
+ ticketTypeId: { type: String, required: true },
7
+ group: { type: Object, required: false }
8
+ });
9
+ const emit = defineEmits(["close"]);
10
+ const t = useModuleT("events");
11
+ const tr = useEventsTranslate();
12
+ const { busy, run } = useAction();
13
+ const schema = z.object({
14
+ title: z.custom().refine((value) => !!tr(value).trim(), t("admin.groupTitleRequired", "Enter a title.")),
15
+ required: z.boolean(),
16
+ multiple: z.boolean()
17
+ });
18
+ const state = reactive({
19
+ title: props.group?.title,
20
+ required: props.group?.required ?? false,
21
+ multiple: props.group?.multiple ?? false
22
+ });
23
+ async function save(event) {
24
+ const body = event.data;
25
+ const saved = await run(
26
+ () => props.group ? $fetch(eventsApi.admin.addonGroup(props.group.id), { method: "PATCH", body }) : $fetch(eventsApi.admin.addonGroups(props.ticketTypeId), { method: "POST", body }),
27
+ { success: t("admin.groupSaved", "Add-on group saved.") }
28
+ );
29
+ if (saved) emit("close", saved);
30
+ }
31
+ </script>
32
+
33
+ <template>
34
+ <FormSlideover
35
+ form-id="event-addon-group-form"
36
+ :title="group ? t('admin.editGroup', 'Edit add-on group') : t('admin.addGroup', 'Add add-on group')"
37
+ :submit-label="group ? t('admin.saveGroup', 'Save add-on group') : t('admin.addGroup', 'Add add-on group')"
38
+ :loading="busy"
39
+ @close="emit('close', false)"
40
+ >
41
+ <UForm
42
+ id="event-addon-group-form"
43
+ :schema="schema"
44
+ :state="state"
45
+ class="flex flex-col gap-4"
46
+ @submit="save"
47
+ >
48
+ <UFormField
49
+ name="title"
50
+ :label="t('admin.groupTitle', 'Title')"
51
+ :description="t('admin.groupTitleHelp', 'Shown above the choices, e.g. Food.')"
52
+ required
53
+ >
54
+ <TranslatedInput v-model="state.title" />
55
+ </UFormField>
56
+ <UCheckbox
57
+ v-model="state.required"
58
+ :label="t('admin.groupRequiredLabel', 'Buyers must choose at least one')"
59
+ />
60
+ <UCheckbox
61
+ v-model="state.multiple"
62
+ :label="t('admin.groupMultipleLabel', 'Buyers can choose several')"
63
+ />
64
+ </UForm>
65
+ </FormSlideover>
66
+ </template>
@@ -0,0 +1,18 @@
1
+ import type { StaffAddonGroup } from '../utils/admin-event.js';
2
+ type __VLS_Props = {
3
+ /** The ticket type a new group is added to. */
4
+ ticketTypeId: string;
5
+ /** The group to edit; leave out to add one. */
6
+ group?: StaffAddonGroup;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ close: (saved: false | {
10
+ id: string;
11
+ }) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onClose?: ((saved: false | {
14
+ id: string;
15
+ }) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import type { StaffAddon } from '../utils/admin-event.js';
2
+ type __VLS_Props = {
3
+ /** The add-on group a new choice is added to. */
4
+ groupId: string;
5
+ /** The currency of the ticket type the group belongs to. */
6
+ currency: string;
7
+ /** The choice to edit; leave out to add one. */
8
+ addon?: StaffAddon;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ close: (saved: false | {
12
+ id: string;
13
+ }) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onClose?: ((saved: false | {
16
+ id: string;
17
+ }) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,103 @@
1
+ <script setup>
2
+ import { z } from "zod";
3
+ import { fromCents, toCents } from "@frigear-nu/ui/money";
4
+ import { eventsApi } from "../../utils/api";
5
+ defineOptions({ name: "EventAddonSlideover" });
6
+ const props = defineProps({
7
+ groupId: { type: String, required: true },
8
+ currency: { type: String, required: true },
9
+ addon: { type: Object, required: false }
10
+ });
11
+ const emit = defineEmits(["close"]);
12
+ const t = useModuleT("events");
13
+ const tr = useEventsTranslate();
14
+ const { busy, run } = useAction();
15
+ const schema = z.object({
16
+ label: z.custom().refine((value) => !!tr(value).trim(), t("admin.nameRequired", "Enter a name.")),
17
+ description: z.custom().optional(),
18
+ price: z.coerce.number().min(0, t("admin.priceInvalid", "Enter a price of 0 or more.")),
19
+ capacity: z.union([z.literal(""), z.null(), z.undefined(), z.coerce.number().int(t("admin.countInvalid", "Enter a whole number of 0 or more.")).min(0, t("admin.countInvalid", "Enter a whole number of 0 or more."))]).transform((value) => typeof value === "number" ? value : null)
20
+ });
21
+ const state = reactive({
22
+ label: props.addon?.label,
23
+ description: props.addon?.description ?? void 0,
24
+ price: props.addon ? fromCents(props.addon.price, props.currency) : 0,
25
+ capacity: props.addon?.capacity ?? ""
26
+ });
27
+ async function save(event) {
28
+ const data = event.data;
29
+ const body = {
30
+ label: data.label,
31
+ description: data.description ?? null,
32
+ // Prices are stored in minor units (øre/cents).
33
+ price: toCents(data.price, props.currency) ?? 0,
34
+ capacity: data.capacity
35
+ };
36
+ const saved = await run(
37
+ () => props.addon ? $fetch(eventsApi.admin.addon(props.addon.id), { method: "PATCH", body }) : $fetch(eventsApi.admin.addons(props.groupId), { method: "POST", body }),
38
+ { success: t("admin.addonSaved", "Add-on saved.") }
39
+ );
40
+ if (saved) emit("close", saved);
41
+ }
42
+ </script>
43
+
44
+ <template>
45
+ <FormSlideover
46
+ form-id="event-addon-form"
47
+ :title="addon ? t('admin.editAddon', 'Edit choice') : t('admin.addAddon', 'Add choice')"
48
+ :submit-label="addon ? t('admin.saveAddon', 'Save choice') : t('admin.addAddon', 'Add choice')"
49
+ :loading="busy"
50
+ @close="emit('close', false)"
51
+ >
52
+ <UForm
53
+ id="event-addon-form"
54
+ :schema="schema"
55
+ :state="state"
56
+ class="flex flex-col gap-4"
57
+ @submit="save"
58
+ >
59
+ <UFormField
60
+ name="label"
61
+ :label="t('admin.name', 'Name')"
62
+ required
63
+ >
64
+ <TranslatedInput v-model="state.label" />
65
+ </UFormField>
66
+ <UFormField
67
+ name="description"
68
+ :label="t('admin.description', 'Description')"
69
+ >
70
+ <TranslatedInput
71
+ v-model="state.description"
72
+ multiline
73
+ :rows="2"
74
+ />
75
+ </UFormField>
76
+ <UFormField
77
+ name="price"
78
+ :label="t('admin.price', 'Price')"
79
+ :description="t('admin.addonPriceHelp', 'Added to the ticket price. 0 means included.')"
80
+ >
81
+ <UInput
82
+ v-model="state.price"
83
+ type="number"
84
+ min="0"
85
+ step="0.01"
86
+ class="w-full"
87
+ />
88
+ </UFormField>
89
+ <UFormField
90
+ name="capacity"
91
+ :label="t('admin.capacity', 'Capacity')"
92
+ :description="t('admin.capacityHelp', 'Leave empty for unlimited.')"
93
+ >
94
+ <UInput
95
+ v-model="state.capacity"
96
+ type="number"
97
+ min="0"
98
+ class="w-full"
99
+ />
100
+ </UFormField>
101
+ </UForm>
102
+ </FormSlideover>
103
+ </template>
@@ -0,0 +1,20 @@
1
+ import type { StaffAddon } from '../utils/admin-event.js';
2
+ type __VLS_Props = {
3
+ /** The add-on group a new choice is added to. */
4
+ groupId: string;
5
+ /** The currency of the ticket type the group belongs to. */
6
+ currency: string;
7
+ /** The choice to edit; leave out to add one. */
8
+ addon?: StaffAddon;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ close: (saved: false | {
12
+ id: string;
13
+ }) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onClose?: ((saved: false | {
16
+ id: string;
17
+ }) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -1,19 +1,11 @@
1
- import type { StaffAddon, StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
1
+ import type { StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
3
  ticketType: StaffTicketType;
4
4
  };
5
5
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
6
  refresh: () => any;
7
- addGroup: () => any;
8
- editGroup: (group: StaffAddonGroup) => any;
9
- addAddon: (group: StaffAddonGroup) => any;
10
- editAddon: (group: StaffAddonGroup, addon: StaffAddon) => any;
11
7
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
8
  onRefresh?: (() => any) | undefined;
13
- onAddGroup?: (() => any) | undefined;
14
- onEditGroup?: ((group: StaffAddonGroup) => any) | undefined;
15
- onAddAddon?: ((group: StaffAddonGroup) => any) | undefined;
16
- onEditAddon?: ((group: StaffAddonGroup, addon: StaffAddon) => any) | undefined;
17
9
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
10
  declare const _default: typeof __VLS_export;
19
11
  export default _default;
@@ -3,53 +3,11 @@ defineOptions({ name: "EventAdminAddonGroups" });
3
3
  defineProps({
4
4
  ticketType: { type: Object, required: true }
5
5
  });
6
- const emit = defineEmits(["refresh", "addGroup", "editGroup", "addAddon", "editAddon"]);
6
+ const emit = defineEmits(["refresh"]);
7
7
  const t = useModuleT("events");
8
8
  const format = useFormat();
9
9
  const tr = useEventsTranslate();
10
- const confirmAction = useConfirmDialog();
11
- const { run } = useAction();
12
- const removeGroup = async (addonGroup) => {
13
- const title = tr(addonGroup.title);
14
- if (!await confirmAction({
15
- title: t("admin.confirmRemoveGroup", "Delete {name}?", { name: title }),
16
- description: t("admin.confirmRemoveGroupDescription", "Its choices are deleted too. Tickets already bought keep the choices they made."),
17
- confirmText: t("admin.removeGroup", "Delete add-on group"),
18
- destructive: true
19
- })) return;
20
- return run(async () => {
21
- await $fetch(`/api/events/admin/addon-groups/${addonGroup.id}`, { method: "DELETE" });
22
- emit("refresh");
23
- }, { success: t("admin.groupDeleted", "Deleted {name}", { name: title }) });
24
- };
25
- const removeAddon = async (existing) => {
26
- const label = tr(existing.label);
27
- if (!await confirmAction({
28
- title: t("admin.confirmRemoveAddon", "Delete {name}?", { name: label }),
29
- description: t("admin.confirmRemoveAddonDescription", "Tickets already bought keep the choice they made."),
30
- confirmText: t("admin.removeAddon", "Delete choice"),
31
- destructive: true
32
- })) return;
33
- return run(async () => {
34
- await $fetch(`/api/events/admin/addons/${existing.id}`, { method: "DELETE" });
35
- emit("refresh");
36
- }, { success: t("admin.addonDeleted", "Deleted {name}", { name: label }) });
37
- };
38
- const groupMenu = (addonGroup) => [
39
- [
40
- { label: t("admin.addAddon", "Add choice"), icon: "i-lucide-plus", onSelect: () => emit("addAddon", addonGroup) },
41
- { label: t("admin.editGroup", "Edit add-on group"), icon: "i-lucide-pencil", onSelect: () => emit("editGroup", addonGroup) }
42
- ],
43
- [{ label: t("admin.removeGroup", "Delete add-on group"), icon: "i-lucide-trash-2", color: "error", onSelect: () => {
44
- void removeGroup(addonGroup);
45
- } }]
46
- ];
47
- const addonMenu = (addonGroup, item) => [
48
- [{ label: t("admin.editAddon", "Edit choice"), icon: "i-lucide-pencil", onSelect: () => emit("editAddon", addonGroup, item) }],
49
- [{ label: t("admin.removeAddon", "Delete choice"), icon: "i-lucide-trash-2", color: "error", onSelect: () => {
50
- void removeAddon(item);
51
- } }]
52
- ];
10
+ const catalog = useEventCatalogActions({ onChanged: () => emit("refresh") });
53
11
  </script>
54
12
 
55
13
  <template>
@@ -68,7 +26,7 @@ const addonMenu = (addonGroup, item) => [
68
26
  </span>
69
27
  </div>
70
28
  <RowMenu
71
- :items="groupMenu(addonGroup)"
29
+ :items="catalog.groupMenu(ticketType, addonGroup)"
72
30
  :subject="tr(addonGroup.title)"
73
31
  size="xs"
74
32
  />
@@ -84,10 +42,10 @@ const addonMenu = (addonGroup, item) => [
84
42
  >
85
43
  <div>
86
44
  {{ tr(item.label) }}
87
- <span class="text-muted">· {{ item.price === 0 ? t("admin.included", "Included") : `+ ${format.money(item.price, ticketType.currency)}` }}<template v-if="item.capacity !== null"> · {{ t("admin.capacity", "Capacity").toLowerCase() }} {{ item.capacity }}</template></span>
45
+ <span class="text-muted">· {{ item.price === 0 ? t("admin.included", "Included") : `+ ${format.money(item.price, ticketType.currency)}` }}<template v-if="item.capacity !== null"> · {{ t("admin.capacity", "Capacity").toLowerCase() }} <span class="tabular-nums">{{ item.capacity }}</span></template></span>
88
46
  </div>
89
47
  <RowMenu
90
- :items="addonMenu(addonGroup, item)"
48
+ :items="catalog.addonMenu(ticketType, addonGroup, item)"
91
49
  :subject="tr(item.label)"
92
50
  size="xs"
93
51
  />
@@ -107,7 +65,7 @@ const addonMenu = (addonGroup, item) => [
107
65
  variant="outline"
108
66
  size="xs"
109
67
  :label="t('admin.addGroup', 'Add add-on group')"
110
- @click="emit('addGroup')"
68
+ @click="catalog.openGroup(ticketType)"
111
69
  />
112
70
  </div>
113
71
  </div>
@@ -1,19 +1,11 @@
1
- import type { StaffAddon, StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
1
+ import type { StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
3
  ticketType: StaffTicketType;
4
4
  };
5
5
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
6
  refresh: () => any;
7
- addGroup: () => any;
8
- editGroup: (group: StaffAddonGroup) => any;
9
- addAddon: (group: StaffAddonGroup) => any;
10
- editAddon: (group: StaffAddonGroup, addon: StaffAddon) => any;
11
7
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
8
  onRefresh?: (() => any) | undefined;
13
- onAddGroup?: (() => any) | undefined;
14
- onEditGroup?: ((group: StaffAddonGroup) => any) | undefined;
15
- onAddAddon?: ((group: StaffAddonGroup) => any) | undefined;
16
- onEditAddon?: ((group: StaffAddonGroup, addon: StaffAddon) => any) | undefined;
17
9
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
10
  declare const _default: typeof __VLS_export;
19
11
  export default _default;