@frigear-nu/events 0.0.9 → 0.1.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 (58) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +6 -4
  3. package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +2 -2
  4. package/dist/runtime/app/components/EventAdminAddonDialog.vue +3 -4
  5. package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +2 -2
  6. package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +2 -2
  7. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +3 -4
  8. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +2 -2
  9. package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +8 -8
  10. package/dist/runtime/app/components/EventAdminAddonGroups.vue +46 -55
  11. package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +8 -8
  12. package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +2 -2
  13. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +3 -4
  14. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +2 -2
  15. package/dist/runtime/app/components/EventCard.vue +7 -12
  16. package/dist/runtime/app/components/EventFormSlideover.d.vue.ts +13 -0
  17. package/dist/runtime/app/components/EventFormSlideover.vue +70 -0
  18. package/dist/runtime/app/components/EventFormSlideover.vue.d.ts +13 -0
  19. package/dist/runtime/app/components/EventTicketCard.vue +5 -7
  20. package/dist/runtime/app/components/EventTicketPicker.vue +5 -6
  21. package/dist/runtime/app/composables/index.d.ts +2 -0
  22. package/dist/runtime/app/composables/index.js +2 -0
  23. package/dist/runtime/app/composables/useEventActions.d.ts +26 -0
  24. package/dist/runtime/app/composables/useEventActions.js +61 -0
  25. package/dist/runtime/app/composables/useEventLinks.d.ts +12 -0
  26. package/dist/runtime/app/composables/useEventLinks.js +5 -0
  27. package/dist/runtime/app/composables/useEvents.d.ts +5 -2
  28. package/dist/runtime/app/pages/admin-event.vue +33 -21
  29. package/dist/runtime/app/pages/admin.vue +61 -135
  30. package/dist/runtime/app/pages/event.d.vue.ts +2 -2
  31. package/dist/runtime/app/pages/event.vue +113 -152
  32. package/dist/runtime/app/pages/event.vue.d.ts +2 -2
  33. package/dist/runtime/app/pages/events.vue +33 -31
  34. package/dist/runtime/app/pages/tickets.vue +21 -28
  35. package/dist/runtime/app/tabs/Attendees.d.vue.ts +2 -2
  36. package/dist/runtime/app/tabs/Attendees.vue +64 -60
  37. package/dist/runtime/app/tabs/Attendees.vue.d.ts +2 -2
  38. package/dist/runtime/app/tabs/Details.d.vue.ts +2 -2
  39. package/dist/runtime/app/tabs/Details.vue +4 -5
  40. package/dist/runtime/app/tabs/Details.vue.d.ts +2 -2
  41. package/dist/runtime/app/tabs/Tickets.d.vue.ts +2 -2
  42. package/dist/runtime/app/tabs/Tickets.vue +92 -99
  43. package/dist/runtime/app/tabs/Tickets.vue.d.ts +2 -2
  44. package/dist/runtime/app/utils/admin-event.d.ts +9 -12
  45. package/dist/runtime/app/utils/admin-event.js +0 -17
  46. package/dist/runtime/app/widgets/UpcomingEvents.vue +7 -9
  47. package/dist/runtime/app/widgets/UpcomingEventsStat.vue +5 -6
  48. package/dist/runtime/i18n/lang/da.json +111 -3
  49. package/dist/runtime/i18n/lang/en.json +111 -3
  50. package/dist/runtime/menus.js +2 -4
  51. package/dist/runtime/server/db/schema.postgresql.d.ts +1110 -0
  52. package/dist/runtime/server/db/schema.postgresql.js +125 -0
  53. package/dist/runtime/server/utils/admin.d.ts +5 -5
  54. package/dist/runtime/utils/index.d.ts +1 -0
  55. package/dist/runtime/utils/index.js +1 -0
  56. package/dist/runtime/utils/links.d.ts +21 -0
  57. package/dist/runtime/utils/links.js +12 -0
  58. package/package.json +21 -21
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frigear-nu/events",
3
3
  "configKey": "events",
4
- "version": "0.0.9",
4
+ "version": "0.1.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -25,7 +25,7 @@ const module$1 = defineNuxtModule({
25
25
  "@frigear-nu/subscriptions": { version: ">=0.0.1", optional: true },
26
26
  "@frigear-nu/menu": { version: ">=0.0.1", optional: true },
27
27
  "@frigear-nu/widget": { version: ">=0.0.0", optional: true },
28
- // Provides the shared page components of the admin pages (`AdminPage`, `AdminTable`, ...).
28
+ // Provides the admin route and shell the admin pages live in; the page components come from `@frigear-nu/ui`.
29
29
  "@frigear-nu/admin": { version: ">=0.0.0", optional: true }
30
30
  },
31
31
  async setup(options, nuxt) {
@@ -53,7 +53,7 @@ const module$1 = defineNuxtModule({
53
53
  nuxt.options.runtimeConfig.public.events = {
54
54
  pages: { events: eventsPath, event: eventPath, tickets: ticketsPath, admin: adminPath, adminEvent: adminEventPath }
55
55
  };
56
- await registerDatabaseTable(nuxt, {
56
+ registerDatabaseTable(nuxt, {
57
57
  resolver,
58
58
  schema: (dialect) => `./runtime/server/db/schema.${dialect}`
59
59
  });
@@ -101,7 +101,9 @@ const module$1 = defineNuxtModule({
101
101
  { name: "useMyTickets", from: resolver.resolve("./runtime/app/composables/useEvents") },
102
102
  { name: "useEventsLocale", from: resolver.resolve("./runtime/app/composables/useEvents") },
103
103
  { name: "useEventsFallbackLocale", from: resolver.resolve("./runtime/app/composables/useEvents") },
104
- { name: "useEventsTranslate", from: resolver.resolve("./runtime/app/composables/useEvents") }
104
+ { name: "useEventsTranslate", from: resolver.resolve("./runtime/app/composables/useEvents") },
105
+ { name: "useEventLinks", from: resolver.resolve("./runtime/app/composables/useEventLinks") },
106
+ { name: "useEventActions", from: resolver.resolve("./runtime/app/composables/useEventActions") }
105
107
  ]);
106
108
  addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
107
109
  addModulePages(MODULE_NAME, resolver, [
@@ -124,7 +126,7 @@ const module$1 = defineNuxtModule({
124
126
  extendWidgets(nuxt, "admin.dashboard.top", { id: "events-upcoming-stat", component: resolver.resolve("./runtime/app/widgets/UpcomingEventsStat.vue"), order: 20 });
125
127
  extendWidgets(nuxt, "admin.dashboard.side", { id: "events-upcoming", component: resolver.resolve("./runtime/app/widgets/UpcomingEvents.vue"), order: 10 });
126
128
  }
127
- addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "dashboard.account" });
129
+ addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "account" });
128
130
  }
129
131
  });
130
132
 
@@ -1,5 +1,5 @@
1
- import { type AdminAddon, type AdminAddonGroup } from '../utils/admin-event.js';
2
- declare function open(addonGroup: AdminAddonGroup, currency: string, existing?: AdminAddon): void;
1
+ import type { StaffAddon, StaffAddonGroup } from '../utils/admin-event.js';
2
+ declare function open(addonGroup: StaffAddonGroup, currency: string, existing?: StaffAddon): void;
3
3
  declare const __VLS_export: import("vue").DefineComponent<{}, {
4
4
  open: typeof open;
5
5
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,12 +1,11 @@
1
1
  <script setup>
2
2
  import { fromCents, toCents } from "@frigear-nu/ui/money";
3
3
  import { DEFAULT_CURRENCY } from "../../utils";
4
- import { useAdminAction } from "../utils/admin-event";
5
4
  defineOptions({ name: "EventAdminAddonDialog" });
6
5
  const emit = defineEmits(["refresh"]);
7
6
  const t = useModuleT("events");
8
7
  const tr = useEventsTranslate();
9
- const { busy, run } = useAdminAction();
8
+ const { busy, run } = useAction();
10
9
  const dialog = ref(false);
11
10
  const groupId = ref("");
12
11
  const editingAddon = ref(null);
@@ -40,7 +39,7 @@ const save = () => run(async () => {
40
39
  }
41
40
  dialog.value = false;
42
41
  emit("refresh");
43
- }, t("admin.addonSaved", "Add-on saved."));
42
+ }, { success: t("admin.addonSaved", "Add-on saved.") });
44
43
  </script>
45
44
 
46
45
  <template>
@@ -95,7 +94,7 @@ const save = () => run(async () => {
95
94
  <template #footer>
96
95
  <div class="flex w-full justify-end gap-2">
97
96
  <UButton
98
- variant="subtle"
97
+ variant="outline"
99
98
  color="neutral"
100
99
  :label="t('admin.cancel', 'Cancel')"
101
100
  @click="dialog = false"
@@ -1,5 +1,5 @@
1
- import { type AdminAddon, type AdminAddonGroup } from '../utils/admin-event.js';
2
- declare function open(addonGroup: AdminAddonGroup, currency: string, existing?: AdminAddon): void;
1
+ import type { StaffAddon, StaffAddonGroup } from '../utils/admin-event.js';
2
+ declare function open(addonGroup: StaffAddonGroup, currency: string, existing?: StaffAddon): void;
3
3
  declare const __VLS_export: import("vue").DefineComponent<{}, {
4
4
  open: typeof open;
5
5
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,5 +1,5 @@
1
- import { type AdminAddonGroup, type AdminTicketType } from '../utils/admin-event.js';
2
- declare function open(ticketType: AdminTicketType, existing?: AdminAddonGroup): void;
1
+ import type { StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
2
+ declare function open(ticketType: StaffTicketType, existing?: StaffAddonGroup): void;
3
3
  declare const __VLS_export: import("vue").DefineComponent<{}, {
4
4
  open: typeof open;
5
5
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,10 +1,9 @@
1
1
  <script setup>
2
- import { useAdminAction } from "../utils/admin-event";
3
2
  defineOptions({ name: "EventAdminAddonGroupDialog" });
4
3
  const emit = defineEmits(["refresh"]);
5
4
  const t = useModuleT("events");
6
5
  const tr = useEventsTranslate();
7
- const { busy, run } = useAdminAction();
6
+ const { busy, run } = useAction();
8
7
  const dialog = ref(false);
9
8
  const ticketTypeId = ref("");
10
9
  const editingGroup = ref(null);
@@ -25,7 +24,7 @@ const save = () => run(async () => {
25
24
  }
26
25
  dialog.value = false;
27
26
  emit("refresh");
28
- }, t("admin.groupSaved", "Add-on group saved."));
27
+ }, { success: t("admin.groupSaved", "Add-on group saved.") });
29
28
  </script>
30
29
 
31
30
  <template>
@@ -61,7 +60,7 @@ const save = () => run(async () => {
61
60
  <template #footer>
62
61
  <div class="flex w-full justify-end gap-2">
63
62
  <UButton
64
- variant="subtle"
63
+ variant="outline"
65
64
  color="neutral"
66
65
  :label="t('admin.cancel', 'Cancel')"
67
66
  @click="dialog = false"
@@ -1,5 +1,5 @@
1
- import { type AdminAddonGroup, type AdminTicketType } from '../utils/admin-event.js';
2
- declare function open(ticketType: AdminTicketType, existing?: AdminAddonGroup): void;
1
+ import type { StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
2
+ declare function open(ticketType: StaffTicketType, existing?: StaffAddonGroup): void;
3
3
  declare const __VLS_export: import("vue").DefineComponent<{}, {
4
4
  open: typeof open;
5
5
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,19 +1,19 @@
1
- import { type AdminAddon, type AdminAddonGroup, type AdminTicketType } from '../utils/admin-event.js';
1
+ import type { StaffAddon, StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- ticketType: AdminTicketType;
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
7
  addGroup: () => any;
8
- editGroup: (group: AdminAddonGroup) => any;
9
- addAddon: (group: AdminAddonGroup) => any;
10
- editAddon: (group: AdminAddonGroup, addon: AdminAddon) => any;
8
+ editGroup: (group: StaffAddonGroup) => any;
9
+ addAddon: (group: StaffAddonGroup) => any;
10
+ editAddon: (group: StaffAddonGroup, addon: StaffAddon) => any;
11
11
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
12
  onRefresh?: (() => any) | undefined;
13
13
  onAddGroup?: (() => any) | undefined;
14
- onEditGroup?: ((group: AdminAddonGroup) => any) | undefined;
15
- onAddAddon?: ((group: AdminAddonGroup) => any) | undefined;
16
- onEditAddon?: ((group: AdminAddonGroup, addon: AdminAddon) => any) | undefined;
14
+ onEditGroup?: ((group: StaffAddonGroup) => any) | undefined;
15
+ onAddAddon?: ((group: StaffAddonGroup) => any) | undefined;
16
+ onEditAddon?: ((group: StaffAddonGroup, addon: StaffAddon) => any) | undefined;
17
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
18
  declare const _default: typeof __VLS_export;
19
19
  export default _default;
@@ -1,30 +1,55 @@
1
1
  <script setup>
2
- import { formatMoney as formatPrice } from "@frigear-nu/ui/money";
3
- import { useAdminAction } from "../utils/admin-event";
4
2
  defineOptions({ name: "EventAdminAddonGroups" });
5
3
  defineProps({
6
4
  ticketType: { type: Object, required: true }
7
5
  });
8
6
  const emit = defineEmits(["refresh", "addGroup", "editGroup", "addAddon", "editAddon"]);
9
7
  const t = useModuleT("events");
10
- const locale = useEventsLocale();
8
+ const format = useFormat();
11
9
  const tr = useEventsTranslate();
12
10
  const confirmAction = useConfirmDialog();
13
- const { run } = useAdminAction();
11
+ const { run } = useAction();
14
12
  const removeGroup = async (addonGroup) => {
15
- if (!await confirmAction({ title: t("admin.confirmRemoveGroup", "Delete this add-on group and its choices?"), confirmText: t("admin.remove", "Delete"), cancelText: t("admin.cancel", "Cancel"), destructive: true })) return;
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;
16
20
  return run(async () => {
17
21
  await $fetch(`/api/events/admin/addon-groups/${addonGroup.id}`, { method: "DELETE" });
18
22
  emit("refresh");
19
- });
23
+ }, { success: t("admin.groupDeleted", "Deleted {name}", { name: title }) });
20
24
  };
21
25
  const removeAddon = async (existing) => {
22
- if (!await confirmAction({ title: t("admin.confirmRemoveAddon", "Delete this choice?"), confirmText: t("admin.remove", "Delete"), cancelText: t("admin.cancel", "Cancel"), destructive: true })) return;
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;
23
33
  return run(async () => {
24
34
  await $fetch(`/api/events/admin/addons/${existing.id}`, { method: "DELETE" });
25
35
  emit("refresh");
26
- });
36
+ }, { success: t("admin.addonDeleted", "Deleted {name}", { name: label }) });
27
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
+ ];
28
53
  </script>
29
54
 
30
55
  <template>
@@ -32,42 +57,21 @@ const removeAddon = async (existing) => {
32
57
  <div
33
58
  v-for="addonGroup in ticketType.addonGroups"
34
59
  :key="addonGroup.id"
35
- class="border-default overflow-hidden rounded-lg border bg-default"
60
+ class="border-default bg-default overflow-hidden rounded-lg border"
36
61
  >
37
62
  <div class="border-default flex items-center justify-between gap-2 border-b px-3 py-2">
38
63
  <div class="min-w-0 text-sm">
39
- <span class="font-medium">{{ tr(addonGroup.title) }}</span>
64
+ <span class="text-highlighted font-medium">{{ tr(addonGroup.title) }}</span>
40
65
  <span class="text-muted ms-2 text-xs">
41
66
  {{ addonGroup.required ? t("admin.groupRequired", "required") : t("admin.groupOptional", "optional") }}
42
67
  · {{ addonGroup.multiple ? t("admin.groupMultiple", "several choices") : t("admin.groupSingle", "one choice") }}
43
68
  </span>
44
69
  </div>
45
- <div class="flex shrink-0 gap-1">
46
- <UButton
47
- icon="i-lucide-plus"
48
- color="neutral"
49
- variant="ghost"
50
- size="xs"
51
- :aria-label="t('admin.addAddon', 'Add choice')"
52
- @click="emit('addAddon', addonGroup)"
53
- />
54
- <UButton
55
- icon="i-lucide-pencil"
56
- color="neutral"
57
- variant="ghost"
58
- size="xs"
59
- :aria-label="t('admin.edit', 'Edit')"
60
- @click="emit('editGroup', addonGroup)"
61
- />
62
- <UButton
63
- icon="i-lucide-trash"
64
- color="error"
65
- variant="ghost"
66
- size="xs"
67
- :aria-label="t('admin.remove', 'Delete')"
68
- @click="removeGroup(addonGroup)"
69
- />
70
- </div>
70
+ <RowMenu
71
+ :items="groupMenu(addonGroup)"
72
+ :subject="tr(addonGroup.title)"
73
+ size="xs"
74
+ />
71
75
  </div>
72
76
  <ul
73
77
  v-if="addonGroup.addons.length"
@@ -80,26 +84,13 @@ const removeAddon = async (existing) => {
80
84
  >
81
85
  <div>
82
86
  {{ tr(item.label) }}
83
- <span class="text-muted">· {{ item.price === 0 ? t("admin.included", "Included") : `+ ${formatPrice(item.price, ticketType.currency, locale)}` }}<template v-if="item.capacity !== null"> · {{ t("admin.capacity", "capacity") }} {{ item.capacity }}</template></span>
84
- </div>
85
- <div class="flex shrink-0 gap-1">
86
- <UButton
87
- icon="i-lucide-pencil"
88
- color="neutral"
89
- variant="ghost"
90
- size="xs"
91
- :aria-label="t('admin.edit', 'Edit')"
92
- @click="emit('editAddon', addonGroup, item)"
93
- />
94
- <UButton
95
- icon="i-lucide-trash"
96
- color="error"
97
- variant="ghost"
98
- size="xs"
99
- :aria-label="t('admin.remove', 'Delete')"
100
- @click="removeAddon(item)"
101
- />
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>
102
88
  </div>
89
+ <RowMenu
90
+ :items="addonMenu(addonGroup, item)"
91
+ :subject="tr(item.label)"
92
+ size="xs"
93
+ />
103
94
  </li>
104
95
  </ul>
105
96
  <p
@@ -1,19 +1,19 @@
1
- import { type AdminAddon, type AdminAddonGroup, type AdminTicketType } from '../utils/admin-event.js';
1
+ import type { StaffAddon, StaffAddonGroup, StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
- ticketType: AdminTicketType;
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
7
  addGroup: () => any;
8
- editGroup: (group: AdminAddonGroup) => any;
9
- addAddon: (group: AdminAddonGroup) => any;
10
- editAddon: (group: AdminAddonGroup, addon: AdminAddon) => any;
8
+ editGroup: (group: StaffAddonGroup) => any;
9
+ addAddon: (group: StaffAddonGroup) => any;
10
+ editAddon: (group: StaffAddonGroup, addon: StaffAddon) => any;
11
11
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
12
  onRefresh?: (() => any) | undefined;
13
13
  onAddGroup?: (() => any) | undefined;
14
- onEditGroup?: ((group: AdminAddonGroup) => any) | undefined;
15
- onAddAddon?: ((group: AdminAddonGroup) => any) | undefined;
16
- onEditAddon?: ((group: AdminAddonGroup, addon: AdminAddon) => any) | undefined;
14
+ onEditGroup?: ((group: StaffAddonGroup) => any) | undefined;
15
+ onAddAddon?: ((group: StaffAddonGroup) => any) | undefined;
16
+ onEditAddon?: ((group: StaffAddonGroup, addon: StaffAddon) => any) | undefined;
17
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
18
  declare const _default: typeof __VLS_export;
19
19
  export default _default;
@@ -1,4 +1,4 @@
1
- import { type AdminTicketType } from '../utils/admin-event.js';
1
+ import type { StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
3
  eventId: string;
4
4
  /** Membership rules are only offered when @frigear-nu/subscriptions is installed. */
@@ -8,7 +8,7 @@ type __VLS_Props = {
8
8
  value: string;
9
9
  }>;
10
10
  };
11
- declare function open(ticketType?: AdminTicketType): void;
11
+ declare function open(ticketType?: StaffTicketType): void;
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
13
13
  open: typeof open;
14
14
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,7 +1,6 @@
1
1
  <script setup>
2
2
  import { toCents, fromCents } from "@frigear-nu/ui/money";
3
3
  import { DEFAULT_CURRENCY, ticketMdcData } from "../../utils";
4
- import { useAdminAction } from "../utils/admin-event";
5
4
  defineOptions({ name: "EventAdminTicketTypeDialog" });
6
5
  const props = defineProps({
7
6
  eventId: { type: String, required: true },
@@ -12,7 +11,7 @@ const emit = defineEmits(["refresh"]);
12
11
  const t = useModuleT("events");
13
12
  const locale = useEventsLocale();
14
13
  const tr = useEventsTranslate();
15
- const { busy, run } = useAdminAction();
14
+ const { busy, run } = useAction();
16
15
  const modes = computed(() => [
17
16
  { label: t("admin.ruleNone", "Nobody"), value: "none" },
18
17
  { label: t("admin.ruleAny", "Any membership"), value: "any" },
@@ -53,7 +52,7 @@ const save = () => run(async () => {
53
52
  }
54
53
  dialog.value = false;
55
54
  emit("refresh");
56
- }, t("admin.typeSaved", "Ticket type saved."));
55
+ }, { success: t("admin.typeSaved", "Ticket type saved.") });
57
56
  </script>
58
57
 
59
58
  <template>
@@ -157,7 +156,7 @@ const save = () => run(async () => {
157
156
  <template #footer>
158
157
  <div class="flex w-full justify-end gap-2">
159
158
  <UButton
160
- variant="subtle"
159
+ variant="outline"
161
160
  color="neutral"
162
161
  :label="t('admin.cancel', 'Cancel')"
163
162
  @click="dialog = false"
@@ -1,4 +1,4 @@
1
- import { type AdminTicketType } from '../utils/admin-event.js';
1
+ import type { StaffTicketType } from '../utils/admin-event.js';
2
2
  type __VLS_Props = {
3
3
  eventId: string;
4
4
  /** Membership rules are only offered when @frigear-nu/subscriptions is installed. */
@@ -8,7 +8,7 @@ type __VLS_Props = {
8
8
  value: string;
9
9
  }>;
10
10
  };
11
- declare function open(ticketType?: AdminTicketType): void;
11
+ declare function open(ticketType?: StaffTicketType): void;
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
13
13
  open: typeof open;
14
14
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -4,14 +4,14 @@ defineProps({
4
4
  to: { type: String, required: true }
5
5
  });
6
6
  const t = useModuleT("events");
7
- const locale = useEventsLocale();
7
+ const format = useFormat();
8
8
  const tr = useEventsTranslate();
9
9
  </script>
10
10
 
11
11
  <template>
12
12
  <NuxtLink
13
13
  :to="to"
14
- class="group flex flex-col overflow-hidden rounded-xl bg-default ring ring-default transition hover:shadow-lg"
14
+ class="group bg-default ring-default flex flex-col overflow-hidden rounded-lg ring transition hover:shadow-lg"
15
15
  >
16
16
  <img
17
17
  v-if="event.image"
@@ -33,26 +33,21 @@ const tr = useEventsTranslate();
33
33
  >
34
34
  {{ t("members", "Members") }}
35
35
  </UBadge>
36
- <h3 class="text-lg font-semibold text-highlighted">
36
+ <h3 class="text-highlighted font-semibold">
37
37
  {{ tr(event.name) }}
38
38
  </h3>
39
- <p class="text-sm text-muted">
40
- <NuxtTime
41
- :datetime="event.startsAt"
42
- :locale="locale"
43
- date-style="full"
44
- time-style="short"
45
- /><template v-if="event.location">
39
+ <p class="text-muted text-sm">
40
+ {{ format.dateTime(event.startsAt, { dateStyle: "full" }) }}<template v-if="event.location">
46
41
  · {{ tr(event.location) }}
47
42
  </template>
48
43
  </p>
49
44
  <p
50
45
  v-if="event.description"
51
- class="line-clamp-3 text-sm text-muted"
46
+ class="text-muted line-clamp-3 text-sm"
52
47
  >
53
48
  {{ tr(event.description) }}
54
49
  </p>
55
- <span class="mt-auto inline-flex items-center gap-1 pt-2 text-sm font-medium text-primary">
50
+ <span class="text-primary mt-auto inline-flex items-center gap-1 pt-2 text-sm font-medium">
56
51
  {{ t("viewEvent", "View event") }}
57
52
  <UIcon
58
53
  name="i-lucide-arrow-right"
@@ -0,0 +1,13 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
2
+ close: (created: false | {
3
+ id: string;
4
+ slug: string;
5
+ }) => any;
6
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
7
+ onClose?: ((created: false | {
8
+ id: string;
9
+ slug: string;
10
+ }) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -0,0 +1,70 @@
1
+ <script setup>
2
+ defineOptions({ name: "EventFormSlideover" });
3
+ const emit = defineEmits(["close"]);
4
+ const t = useModuleT("events");
5
+ const tr = useEventsTranslate();
6
+ const { busy, run } = useAction();
7
+ const draft = reactive({ name: void 0, slug: "", startsAt: "", location: void 0 });
8
+ const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9
+ const draftName = computed(() => tr(draft.name));
10
+ watch(draftName, (name, previous) => {
11
+ if (draft.slug === slugify(previous)) draft.slug = slugify(name);
12
+ });
13
+ async function save() {
14
+ const created = await run(
15
+ () => $fetch("/api/events/admin/events", {
16
+ method: "POST",
17
+ body: { name: draft.name, slug: draft.slug, startsAt: draft.startsAt, location: draft.location || null, status: "draft" }
18
+ }),
19
+ { success: t("admin.created", "Event created") }
20
+ );
21
+ if (created) emit("close", created);
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <FormSlideover
27
+ form-id="event-form"
28
+ :title="t('admin.create', 'New event')"
29
+ :description="t('admin.createDescription', 'It is saved as a draft until you publish it.')"
30
+ :submit-label="t('admin.createAction', 'Create draft')"
31
+ :loading="busy"
32
+ :disabled="!draftName"
33
+ @close="emit('close', false)"
34
+ >
35
+ <form
36
+ id="event-form"
37
+ class="flex flex-col gap-4"
38
+ @submit.prevent="save"
39
+ >
40
+ <UFormField :label="t('admin.name', 'Name')">
41
+ <TranslatedInput
42
+ v-model="draft.name"
43
+ required
44
+ />
45
+ </UFormField>
46
+ <UFormField
47
+ :label="t('admin.slug', 'URL name')"
48
+ :description="t('admin.slugHelp', 'Lowercase letters, numbers and dashes.')"
49
+ >
50
+ <UInput
51
+ v-model="draft.slug"
52
+ class="w-full"
53
+ required
54
+ pattern="[a-z0-9]+(-[a-z0-9]+)*"
55
+ />
56
+ </UFormField>
57
+ <UFormField :label="t('admin.starts', 'Starts')">
58
+ <UInput
59
+ v-model="draft.startsAt"
60
+ type="datetime-local"
61
+ class="w-full"
62
+ required
63
+ />
64
+ </UFormField>
65
+ <UFormField :label="t('admin.location', 'Location')">
66
+ <TranslatedInput v-model="draft.location" />
67
+ </UFormField>
68
+ </form>
69
+ </FormSlideover>
70
+ </template>
@@ -0,0 +1,13 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
2
+ close: (created: false | {
3
+ id: string;
4
+ slug: string;
5
+ }) => any;
6
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
7
+ onClose?: ((created: false | {
8
+ id: string;
9
+ slug: string;
10
+ }) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -1,15 +1,13 @@
1
1
  <script setup>
2
2
  import { renderSVG } from "uqr";
3
- import { formatMoney as formatPrice } from "@frigear-nu/ui/money";
4
3
  const props = defineProps({
5
4
  ticket: { type: Object, required: true },
6
5
  to: { type: String, required: false }
7
6
  });
8
7
  const emit = defineEmits(["pay"]);
9
8
  const t = useModuleT("events");
10
- const locale = useEventsLocale();
9
+ const format = useFormat();
11
10
  const tr = useEventsTranslate();
12
- const colors = { paid: "success", pending: "warning", cancelled: "neutral", expired: "neutral", refunded: "error" };
13
11
  const qr = computed(() => props.ticket.code ? `data:image/svg+xml;utf8,${encodeURIComponent(renderSVG(props.ticket.code, { border: 1 }))}` : null);
14
12
  const showQr = ref(false);
15
13
  </script>
@@ -18,7 +16,7 @@ const showQr = ref(false);
18
16
  <UCard :ui="{ body: 'flex flex-col gap-3' }">
19
17
  <div class="flex items-start gap-2">
20
18
  <div>
21
- <h3 class="font-semibold">
19
+ <h3 class="text-highlighted font-semibold">
22
20
  {{ tr(ticket.eventName) }}
23
21
  </h3>
24
22
  <p class="text-sm text-muted">
@@ -32,14 +30,14 @@ const showQr = ref(false);
32
30
  </div>
33
31
  <UBadge
34
32
  class="ml-auto capitalize"
35
- :color="colors[ticket.status] ?? 'neutral'"
33
+ :color="statusColor(ticket.status, { refunded: 'error' })"
36
34
  variant="subtle"
37
35
  >
38
36
  {{ t(`tickets.status.${ticket.status}`, ticket.status) }}
39
37
  </UBadge>
40
38
  </div>
41
39
  <p class="text-sm">
42
- {{ ticket.amount === 0 ? t("detail.tickets.free", "Free") : formatPrice(ticket.amount, ticket.currency, locale) }}
40
+ {{ ticket.amount === 0 ? t("detail.tickets.free", "Free") : format.money(ticket.amount, ticket.currency) }}
43
41
  <UBadge
44
42
  v-if="ticket.checkedInAt"
45
43
  color="info"
@@ -76,7 +74,7 @@ const showQr = ref(false);
76
74
  <img
77
75
  v-if="showQr && qr"
78
76
  :src="qr"
79
- alt="Ticket QR code"
77
+ :alt="t('tickets.qrAlt', 'Ticket QR code')"
80
78
  class="mx-auto w-56 rounded-md bg-white p-2"
81
79
  >
82
80
  </UCard>