@frigear-nu/events 0.1.2 → 0.2.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 (102) hide show
  1. package/dist/module.d.mts +105 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +35 -31
  4. package/dist/runtime/app/components/EventAddonGroupSlideover.d.vue.ts +18 -0
  5. package/dist/runtime/app/components/EventAddonGroupSlideover.vue +67 -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 +104 -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 +7 -48
  12. package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +1 -9
  13. package/dist/runtime/app/components/EventCard.vue +1 -0
  14. package/dist/runtime/app/components/EventFormSlideover.vue +46 -23
  15. package/dist/runtime/app/components/EventTicketCard.vue +60 -62
  16. package/dist/runtime/app/components/EventTicketPicker.vue +1 -0
  17. package/dist/runtime/app/components/EventTicketTypeSlideover.d.vue.ts +20 -0
  18. package/dist/runtime/app/components/EventTicketTypeSlideover.vue +177 -0
  19. package/dist/runtime/app/components/EventTicketTypeSlideover.vue.d.ts +20 -0
  20. package/dist/runtime/app/composables/index.d.ts +2 -0
  21. package/dist/runtime/app/composables/index.js +1 -0
  22. package/dist/runtime/app/composables/useEventActions.d.ts +3 -3
  23. package/dist/runtime/app/composables/useEventActions.js +10 -4
  24. package/dist/runtime/app/composables/useEventCatalogActions.d.ts +33 -0
  25. package/dist/runtime/app/composables/useEventCatalogActions.js +108 -0
  26. package/dist/runtime/app/composables/useEventLinks.js +1 -0
  27. package/dist/runtime/app/composables/useEvents.d.ts +29 -16
  28. package/dist/runtime/app/composables/useEvents.js +7 -6
  29. package/dist/runtime/app/pages/admin-event.vue +72 -136
  30. package/dist/runtime/app/pages/admin.vue +92 -29
  31. package/dist/runtime/app/pages/event.vue +9 -4
  32. package/dist/runtime/app/pages/events.vue +1 -0
  33. package/dist/runtime/app/pages/tickets.vue +5 -0
  34. package/dist/runtime/app/tabs/Attendees.vue +79 -39
  35. package/dist/runtime/app/tabs/Details.d.vue.ts +1 -1
  36. package/dist/runtime/app/tabs/Details.vue +113 -30
  37. package/dist/runtime/app/tabs/Details.vue.d.ts +1 -1
  38. package/dist/runtime/app/tabs/Tickets.vue +103 -139
  39. package/dist/runtime/app/utils/admin-event.d.ts +53 -38
  40. package/dist/runtime/app/utils/admin-event.js +14 -0
  41. package/dist/runtime/app/widgets/UpcomingEvents.vue +3 -1
  42. package/dist/runtime/app/widgets/UpcomingEventsStat.vue +3 -1
  43. package/dist/runtime/i18n/lang/da.json +22 -3
  44. package/dist/runtime/i18n/lang/en.json +22 -3
  45. package/dist/runtime/menus.js +1 -0
  46. package/dist/runtime/server/bootstrap/my-tickets.d.ts +1 -1
  47. package/dist/runtime/server/mcp/schemas.d.ts +4 -4
  48. package/dist/runtime/server/routes/admin/addon-group.delete.js +6 -3
  49. package/dist/runtime/server/routes/admin/addon-group.patch.js +6 -4
  50. package/dist/runtime/server/routes/admin/addon-groups.post.js +8 -6
  51. package/dist/runtime/server/routes/admin/addon.delete.js +6 -3
  52. package/dist/runtime/server/routes/admin/addon.patch.js +8 -5
  53. package/dist/runtime/server/routes/admin/addons.post.js +8 -6
  54. package/dist/runtime/server/routes/admin/attendees.get.js +38 -8
  55. package/dist/runtime/server/routes/{public → admin}/check-in.post.js +4 -3
  56. package/dist/runtime/server/routes/admin/event-clone.post.js +37 -19
  57. package/dist/runtime/server/routes/admin/event.delete.js +7 -4
  58. package/dist/runtime/server/routes/admin/event.get.js +4 -2
  59. package/dist/runtime/server/routes/admin/event.patch.js +4 -2
  60. package/dist/runtime/server/routes/admin/events.get.js +36 -3
  61. package/dist/runtime/server/routes/admin/events.post.js +3 -4
  62. package/dist/runtime/server/routes/admin/membership-options.get.js +3 -2
  63. package/dist/runtime/server/routes/admin/ticket-cancel.post.js +7 -8
  64. package/dist/runtime/server/routes/admin/ticket-refund.post.js +7 -5
  65. package/dist/runtime/server/routes/admin/ticket-type.delete.js +7 -4
  66. package/dist/runtime/server/routes/admin/ticket-type.patch.js +4 -2
  67. package/dist/runtime/server/routes/admin/ticket-types.post.js +10 -7
  68. package/dist/runtime/server/routes/admin/upcoming.get.js +7 -3
  69. package/dist/runtime/server/routes/me/ticket-pay.post.js +13 -0
  70. package/dist/runtime/server/routes/me/ticket.get.js +6 -0
  71. package/dist/runtime/server/routes/public/event.get.js +5 -2
  72. package/dist/runtime/server/routes/public/purchase.post.js +3 -1
  73. package/dist/runtime/server/utils/admin-dto.d.ts +30 -0
  74. package/dist/runtime/server/utils/admin-dto.js +101 -0
  75. package/dist/runtime/server/utils/admin-schemas.js +2 -1
  76. package/dist/runtime/server/utils/admin.d.ts +10 -8
  77. package/dist/runtime/server/utils/admin.js +20 -6
  78. package/dist/runtime/server/utils/auth.d.ts +5 -4
  79. package/dist/runtime/server/utils/auth.js +6 -7
  80. package/dist/runtime/server/utils/tickets.js +6 -11
  81. package/dist/runtime/utils/api.d.ts +48 -0
  82. package/dist/runtime/utils/api.js +51 -0
  83. package/dist/runtime/utils/index.d.ts +2 -1
  84. package/dist/runtime/utils/index.js +1 -0
  85. package/dist/types.d.mts +1 -1
  86. package/package.json +19 -14
  87. package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +0 -11
  88. package/dist/runtime/app/components/EventAdminAddonDialog.vue +0 -112
  89. package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +0 -11
  90. package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +0 -11
  91. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +0 -78
  92. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +0 -11
  93. package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +0 -20
  94. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +0 -174
  95. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +0 -20
  96. package/dist/runtime/server/routes/public/ticket-pay.post.js +0 -10
  97. package/dist/runtime/server/routes/public/ticket.get.js +0 -4
  98. /package/dist/runtime/server/routes/{public → admin}/check-in.post.d.ts +0 -0
  99. /package/dist/runtime/server/routes/{public → me}/ticket-pay.post.d.ts +0 -0
  100. /package/dist/runtime/server/routes/{public → me}/ticket.get.d.ts +0 -0
  101. /package/dist/runtime/server/routes/{public/tickets-me.get.d.ts → me/tickets.get.d.ts} +0 -0
  102. /package/dist/runtime/server/routes/{public/tickets-me.get.js → me/tickets.get.js} +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.2",
4
+ "version": "0.2.0",
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, registerBootstrapProvider, 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,39 +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");
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" });
63
68
  registerBootstrapProvider(nuxt, {
64
69
  scope: "account",
65
70
  key: "events:tickets:mine",
66
71
  handler: resolver.resolve("./runtime/server/bootstrap/my-tickets")
67
72
  });
68
- route("get", "/tickets/:id", "public/ticket.get");
69
- route("post", "/tickets/:id/pay", "public/ticket-pay.post");
70
- route("post", "/check-in", "public/check-in.post");
71
- route("get", "/:slug", "public/event.get");
72
- route("post", "/:slug/purchase", "public/purchase.post");
73
- route("get", "/admin/membership-options", "admin/membership-options.get");
74
- route("get", "/admin/upcoming", "admin/upcoming.get");
75
- route("get", "/admin/events", "admin/events.get");
76
- route("get", "/admin/events/:id", "admin/event.get");
77
- route("post", "/admin/events", "admin/events.post");
78
- route("patch", "/admin/events/:id", "admin/event.patch");
79
- route("delete", "/admin/events/:id", "admin/event.delete");
80
- route("post", "/admin/events/:id/clone", "admin/event-clone.post");
81
- route("get", "/admin/events/:id/tickets", "admin/attendees.get");
82
- route("post", "/admin/events/:id/ticket-types", "admin/ticket-types.post");
83
- route("patch", "/admin/ticket-types/:id", "admin/ticket-type.patch");
84
- route("delete", "/admin/ticket-types/:id", "admin/ticket-type.delete");
85
- route("post", "/admin/ticket-types/:id/addon-groups", "admin/addon-groups.post");
86
- route("patch", "/admin/addon-groups/:id", "admin/addon-group.patch");
87
- route("delete", "/admin/addon-groups/:id", "admin/addon-group.delete");
88
- route("post", "/admin/addon-groups/:id/addons", "admin/addons.post");
89
- route("patch", "/admin/addons/:id", "admin/addon.patch");
90
- route("delete", "/admin/addons/:id", "admin/addon.delete");
91
- route("post", "/admin/tickets/:id/cancel", "admin/ticket-cancel.post");
92
- route("post", "/admin/tickets/:id/refund", "admin/ticket-refund.post");
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" });
93
96
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
94
97
  inlineModuleRuntime(nuxt, resolver);
95
98
  if (settings.mcp === true) addMcpDefinitions(nuxt, resolver.resolve("./runtime/server/mcp"));
@@ -108,7 +111,8 @@ const module$1 = defineNuxtModule({
108
111
  { name: "useEventsFallbackLocale", from: resolver.resolve("./runtime/app/composables/useEvents") },
109
112
  { name: "useEventsTranslate", from: resolver.resolve("./runtime/app/composables/useEvents") },
110
113
  { name: "useEventLinks", from: resolver.resolve("./runtime/app/composables/useEventLinks") },
111
- { 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") }
112
116
  ]);
113
117
  addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
114
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,67 @@
1
+ <script setup>
2
+ import { z } from "zod";
3
+ import { eventsApi } from "../../utils/api";
4
+ import { $fetch, reactive, useAction, useEventsTranslate, useModuleT } from "#imports";
5
+ defineOptions({ name: "EventAddonGroupSlideover" });
6
+ const props = defineProps({
7
+ ticketTypeId: { type: String, required: true },
8
+ group: { type: Object, required: false }
9
+ });
10
+ const emit = defineEmits(["close"]);
11
+ const t = useModuleT("events");
12
+ const tr = useEventsTranslate();
13
+ const { busy, run } = useAction();
14
+ const schema = z.object({
15
+ title: z.custom().refine((value) => !!tr(value).trim(), t("admin.groupTitleRequired", "Enter a title.")),
16
+ required: z.boolean(),
17
+ multiple: z.boolean()
18
+ });
19
+ const state = reactive({
20
+ title: props.group?.title,
21
+ required: props.group?.required ?? false,
22
+ multiple: props.group?.multiple ?? false
23
+ });
24
+ async function save(event) {
25
+ const body = event.data;
26
+ const saved = await run(
27
+ () => props.group ? $fetch(eventsApi.admin.addonGroup(props.group.id), { method: "PATCH", body }) : $fetch(eventsApi.admin.addonGroups(props.ticketTypeId), { method: "POST", body }),
28
+ { success: t("admin.groupSaved", "Add-on group saved.") }
29
+ );
30
+ if (saved) emit("close", saved);
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <FormSlideover
36
+ form-id="event-addon-group-form"
37
+ :title="group ? t('admin.editGroup', 'Edit add-on group') : t('admin.addGroup', 'Add add-on group')"
38
+ :submit-label="group ? t('admin.saveGroup', 'Save add-on group') : t('admin.addGroup', 'Add add-on group')"
39
+ :loading="busy"
40
+ @close="emit('close', false)"
41
+ >
42
+ <UForm
43
+ id="event-addon-group-form"
44
+ :schema="schema"
45
+ :state="state"
46
+ class="flex flex-col gap-4"
47
+ @submit="save"
48
+ >
49
+ <UFormField
50
+ name="title"
51
+ :label="t('admin.groupTitle', 'Title')"
52
+ :description="t('admin.groupTitleHelp', 'Shown above the choices, e.g. Food.')"
53
+ required
54
+ >
55
+ <TranslatedInput v-model="state.title" />
56
+ </UFormField>
57
+ <UCheckbox
58
+ v-model="state.required"
59
+ :label="t('admin.groupRequiredLabel', 'Buyers must choose at least one')"
60
+ />
61
+ <UCheckbox
62
+ v-model="state.multiple"
63
+ :label="t('admin.groupMultipleLabel', 'Buyers can choose several')"
64
+ />
65
+ </UForm>
66
+ </FormSlideover>
67
+ </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,104 @@
1
+ <script setup>
2
+ import { z } from "zod";
3
+ import { fromCents, toCents } from "@frigear-nu/ui/money";
4
+ import { eventsApi } from "../../utils/api";
5
+ import { $fetch, reactive, useAction, useEventsTranslate, useModuleT } from "#imports";
6
+ defineOptions({ name: "EventAddonSlideover" });
7
+ const props = defineProps({
8
+ groupId: { type: String, required: true },
9
+ currency: { type: String, required: true },
10
+ addon: { type: Object, required: false }
11
+ });
12
+ const emit = defineEmits(["close"]);
13
+ const t = useModuleT("events");
14
+ const tr = useEventsTranslate();
15
+ const { busy, run } = useAction();
16
+ const schema = z.object({
17
+ label: z.custom().refine((value) => !!tr(value).trim(), t("admin.nameRequired", "Enter a name.")),
18
+ description: z.custom().optional(),
19
+ price: z.coerce.number().min(0, t("admin.priceInvalid", "Enter a price of 0 or more.")),
20
+ 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)
21
+ });
22
+ const state = reactive({
23
+ label: props.addon?.label,
24
+ description: props.addon?.description ?? void 0,
25
+ price: props.addon ? fromCents(props.addon.price, props.currency) : 0,
26
+ capacity: props.addon?.capacity ?? ""
27
+ });
28
+ async function save(event) {
29
+ const data = event.data;
30
+ const body = {
31
+ label: data.label,
32
+ description: data.description ?? null,
33
+ // Prices are stored in minor units (øre/cents).
34
+ price: toCents(data.price, props.currency) ?? 0,
35
+ capacity: data.capacity
36
+ };
37
+ const saved = await run(
38
+ () => props.addon ? $fetch(eventsApi.admin.addon(props.addon.id), { method: "PATCH", body }) : $fetch(eventsApi.admin.addons(props.groupId), { method: "POST", body }),
39
+ { success: t("admin.addonSaved", "Add-on saved.") }
40
+ );
41
+ if (saved) emit("close", saved);
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <FormSlideover
47
+ form-id="event-addon-form"
48
+ :title="addon ? t('admin.editAddon', 'Edit choice') : t('admin.addAddon', 'Add choice')"
49
+ :submit-label="addon ? t('admin.saveAddon', 'Save choice') : t('admin.addAddon', 'Add choice')"
50
+ :loading="busy"
51
+ @close="emit('close', false)"
52
+ >
53
+ <UForm
54
+ id="event-addon-form"
55
+ :schema="schema"
56
+ :state="state"
57
+ class="flex flex-col gap-4"
58
+ @submit="save"
59
+ >
60
+ <UFormField
61
+ name="label"
62
+ :label="t('admin.name', 'Name')"
63
+ required
64
+ >
65
+ <TranslatedInput v-model="state.label" />
66
+ </UFormField>
67
+ <UFormField
68
+ name="description"
69
+ :label="t('admin.description', 'Description')"
70
+ >
71
+ <TranslatedInput
72
+ v-model="state.description"
73
+ multiline
74
+ :rows="2"
75
+ />
76
+ </UFormField>
77
+ <UFormField
78
+ name="price"
79
+ :label="t('admin.price', 'Price')"
80
+ :description="t('admin.addonPriceHelp', 'Added to the ticket price. 0 means included.')"
81
+ >
82
+ <UInput
83
+ v-model="state.price"
84
+ type="number"
85
+ min="0"
86
+ step="0.01"
87
+ class="w-full"
88
+ />
89
+ </UFormField>
90
+ <UFormField
91
+ name="capacity"
92
+ :label="t('admin.capacity', 'Capacity')"
93
+ :description="t('admin.capacityHelp', 'Leave empty for unlimited.')"
94
+ >
95
+ <UInput
96
+ v-model="state.capacity"
97
+ type="number"
98
+ min="0"
99
+ class="w-full"
100
+ />
101
+ </UFormField>
102
+ </UForm>
103
+ </FormSlideover>
104
+ </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;
@@ -1,55 +1,14 @@
1
1
  <script setup>
2
+ import { useEventCatalogActions, useEventsTranslate, useFormat, useModuleT } from "#imports";
2
3
  defineOptions({ name: "EventAdminAddonGroups" });
3
4
  defineProps({
4
5
  ticketType: { type: Object, required: true }
5
6
  });
6
- const emit = defineEmits(["refresh", "addGroup", "editGroup", "addAddon", "editAddon"]);
7
+ const emit = defineEmits(["refresh"]);
7
8
  const t = useModuleT("events");
8
9
  const format = useFormat();
9
10
  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
- ];
11
+ const catalog = useEventCatalogActions({ onChanged: () => emit("refresh") });
53
12
  </script>
54
13
 
55
14
  <template>
@@ -68,7 +27,7 @@ const addonMenu = (addonGroup, item) => [
68
27
  </span>
69
28
  </div>
70
29
  <RowMenu
71
- :items="groupMenu(addonGroup)"
30
+ :items="catalog.groupMenu(ticketType, addonGroup)"
72
31
  :subject="tr(addonGroup.title)"
73
32
  size="xs"
74
33
  />
@@ -84,10 +43,10 @@ const addonMenu = (addonGroup, item) => [
84
43
  >
85
44
  <div>
86
45
  {{ 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>
46
+ <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
47
  </div>
89
48
  <RowMenu
90
- :items="addonMenu(addonGroup, item)"
49
+ :items="catalog.addonMenu(ticketType, addonGroup, item)"
91
50
  :subject="tr(item.label)"
92
51
  size="xs"
93
52
  />
@@ -107,7 +66,7 @@ const addonMenu = (addonGroup, item) => [
107
66
  variant="outline"
108
67
  size="xs"
109
68
  :label="t('admin.addGroup', 'Add add-on group')"
110
- @click="emit('addGroup')"
69
+ @click="catalog.openGroup(ticketType)"
111
70
  />
112
71
  </div>
113
72
  </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;
@@ -1,4 +1,5 @@
1
1
  <script setup>
2
+ import { useEventsTranslate, useFormat, useModuleT } from "#imports";
2
3
  defineProps({
3
4
  event: { type: Object, required: true },
4
5
  to: { type: String, required: true }