@frigear-nu/events 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.2",
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
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';
2
+ import { registerModule, registerTailwindSource, registerLocales, resolvePagePath, registerDatabaseTable, 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';
@@ -60,6 +60,11 @@ const module$1 = defineNuxtModule({
60
60
  const route = (method, path, file) => addServerHandler({ route: `/api/events${path}`, method, handler: resolver.resolve(`./runtime/server/routes/${file}`) });
61
61
  route("get", "", "public/events.get");
62
62
  route("get", "/tickets/me", "public/tickets-me.get");
63
+ registerBootstrapProvider(nuxt, {
64
+ scope: "account",
65
+ key: "events:tickets:mine",
66
+ handler: resolver.resolve("./runtime/server/bootstrap/my-tickets")
67
+ });
63
68
  route("get", "/tickets/:id", "public/ticket.get");
64
69
  route("post", "/tickets/:id/pay", "public/ticket-pay.post");
65
70
  route("post", "/check-in", "public/check-in.post");
@@ -16,12 +16,13 @@ export declare function useEvent(slug: MaybeRefOrGetter<string>): Promise<{
16
16
  url?: string;
17
17
  }>;
18
18
  }>;
19
- export declare function useMyTickets(): {
20
- tickets: any;
21
- status: any;
22
- refresh: any;
23
- pay: (ticketId: string) => Promise<void>;
24
- };
19
+ /**
20
+ * The signed-in user's tickets. Awaitable: `await useMyTickets()` in a page renders them on the server; a widget can
21
+ * use it without `await` and show a skeleton while `loading`.
22
+ *
23
+ * `tickets` is `[]` until loaded and after an error: check `loading` and `error` before showing an empty state.
24
+ */
25
+ export declare function useMyTickets(): any;
25
26
  /** Locale for translated texts: `@nuxtjs/i18n`'s when installed, otherwise `en`. */
26
27
  export declare function useEventsLocale(): any;
27
28
  /** The site's default locale: shown for texts that have no translation in the current locale. */
@@ -32,19 +32,26 @@ export async function useEvent(slug) {
32
32
  }
33
33
  return { event: data, status, error, refresh, purchase };
34
34
  }
35
+ const MY_TICKETS_TTL_MS = 60 * 1e3;
35
36
  export function useMyTickets() {
36
37
  const { loggedIn } = useUserSession();
37
- const { data, status, refresh } = useFetch("/api/events/tickets/me", {
38
+ const fetched = useCachedFetch("/api/events/tickets/me", {
38
39
  key: "events:tickets:mine",
39
- immediate: loggedIn.value,
40
- watch: false,
41
- default: () => []
40
+ ttl: MY_TICKETS_TTL_MS,
41
+ watch: false
42
42
  });
43
+ const { status, error, loading, refresh, clear } = fetched;
44
+ watch(loggedIn, (value) => {
45
+ if (value) refresh();
46
+ else clear();
47
+ });
48
+ const tickets = computed(() => fetched.data.value ?? []);
43
49
  async function pay(ticketId) {
44
50
  const { url } = await $fetch(`/api/events/tickets/${ticketId}/pay`, { method: "POST" });
45
51
  await navigateTo(url, { external: true });
46
52
  }
47
- return { tickets: data, status, refresh, pay };
53
+ const api = { tickets, status, error, loading, refresh, pay };
54
+ return Object.assign(fetched.then(() => api), api);
48
55
  }
49
56
  export function useEventsLocale() {
50
57
  const nuxtApp = useNuxtApp();
@@ -1,14 +1,32 @@
1
1
  <script setup>
2
2
  defineOptions({ name: "EventTicketsPage" });
3
3
  const t = useModuleT("events");
4
- const { tickets, pay } = useMyTickets();
4
+ const { tickets, loading, error, refresh, pay } = await useMyTickets();
5
5
  const links = useEventLinks();
6
6
  </script>
7
7
 
8
8
  <template>
9
9
  <div class="flex flex-col gap-4">
10
+ <div
11
+ v-if="loading"
12
+ class="grid gap-4 md:grid-cols-2"
13
+ aria-busy="true"
14
+ >
15
+ <SectionCard
16
+ v-for="index in 2"
17
+ :key="index"
18
+ loading
19
+ />
20
+ </div>
21
+ <UEmpty
22
+ v-else-if="error && !tickets.length"
23
+ icon="i-lucide-circle-alert"
24
+ :title="t('ticketsLoadFailed', 'Your tickets could not be loaded')"
25
+ :description="errorMessage(error, '')"
26
+ :actions="[{ label: t('tryAgain', 'Try again'), icon: 'i-lucide-rotate-ccw', color: 'neutral', variant: 'outline', onClick: () => refresh() }]"
27
+ />
10
28
  <UEmpty
11
- v-if="!tickets.length"
29
+ v-else-if="!tickets.length"
12
30
  icon="i-lucide-ticket"
13
31
  :title="t('noTicketsTitle', 'No tickets yet')"
14
32
  :description="t('noTickets', 'You do not have any tickets yet.')"
@@ -19,9 +19,12 @@ const typeColumns = computed(() => [
19
19
  { id: "addons", header: t("admin.addonGroups", "Add-ons") },
20
20
  { id: "actions" }
21
21
  ]);
22
- const { data: membershipOptions } = useLazyFetch("/api/events/admin/membership-options", { default: () => ({ available: false, options: [] }) });
23
- const membershipEnabled = computed(() => membershipOptions.value.available);
24
- const plans = computed(() => membershipOptions.value.options.map((option) => ({ label: option.label, value: option.value })));
22
+ const { data: membershipOptions, loading: optionsLoading } = useCachedFetch("/api/events/admin/membership-options", {
23
+ key: "events:admin:membership-options",
24
+ ttl: 5 * 6e4
25
+ });
26
+ const membershipEnabled = computed(() => membershipOptions.value?.available === true);
27
+ const plans = computed(() => (membershipOptions.value?.options ?? []).map((option) => ({ label: option.label, value: option.value })));
25
28
  const planLabel = (key) => plans.value.find((plan) => plan.value === key)?.label ?? key;
26
29
  const describeRule = (value) => value === "any" ? t("admin.ruleAny", "Any membership") : Array.isArray(value) ? value.map(planLabel).join(", ") : "";
27
30
  const typeDialog = useTemplateRef("typeDialog");
@@ -89,7 +92,14 @@ const menu = (ticketType) => [
89
92
  {{ row.original.capacity ?? "\u2013" }}
90
93
  </template>
91
94
  <template #access-cell="{ row }">
92
- <div class="flex flex-wrap gap-1">
95
+ <USkeleton
96
+ v-if="optionsLoading && (row.original.requiresMembership || row.original.excludesMembership)"
97
+ class="h-5 w-28"
98
+ />
99
+ <div
100
+ v-else
101
+ class="flex flex-wrap gap-1"
102
+ >
93
103
  <UBadge
94
104
  v-if="row.original.requiresMembership"
95
105
  variant="subtle"
@@ -229,6 +229,8 @@
229
229
  },
230
230
  "listDescription": "Find et arrangement og få din billet.",
231
231
  "noTicketsTitle": "Ingen billetter endnu",
232
+ "ticketsLoadFailed": "Dine billetter kunne ikke indlæses",
233
+ "tryAgain": "Prøv igen",
232
234
  "browseEvents": "Se arrangementer"
233
235
  }
234
236
  }
@@ -229,6 +229,8 @@
229
229
  },
230
230
  "listDescription": "Find an event and get your ticket.",
231
231
  "noTicketsTitle": "No tickets yet",
232
+ "ticketsLoadFailed": "Your tickets could not be loaded",
233
+ "tryAgain": "Try again",
232
234
  "browseEvents": "Browse events"
233
235
  }
234
236
  }
@@ -0,0 +1,7 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { TicketDto } from '../../../types.js';
3
+ /**
4
+ * The caller's tickets: the body of `GET /api/events/tickets/me` and the account bootstrap's `events:tickets:mine`
5
+ * (the key `useMyTickets()` reads), so both always return the same thing.
6
+ */
7
+ export default function myTickets(event: H3Event): Promise<TicketDto[]>;
@@ -0,0 +1,4 @@
1
+ export default async function myTickets(event) {
2
+ const user = await requireUser(event, { scopes: ["tickets:read"] });
3
+ return listUserTickets(user.id);
4
+ }
@@ -1,4 +1,2 @@
1
- export default defineEventHandler(async (event) => {
2
- const user = await requireUser(event, { scopes: ["tickets:read"] });
3
- return listUserTickets(user.id);
4
- });
1
+ import myTickets from "../../bootstrap/my-tickets.js";
2
+ export default defineEventHandler(myTickets);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frigear-nu/events",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Events with ticket types, addons, capacity, membership requirements, Stripe payments and QR check-in for the frigear ecosystem, built on @frigear-nu/payments.",
5
5
  "homepage": "https://docs.frigear.dev/modules/events",
6
6
  "repository": {
@@ -30,15 +30,15 @@
30
30
  "playground"
31
31
  ],
32
32
  "dependencies": {
33
- "@frigear-nu/admin": "0.1.1",
34
- "@frigear-nu/module-kit": "1.1.1",
35
- "@frigear-nu/payments": "0.1.1",
36
- "@frigear-nu/stripe": "0.1.1",
37
- "@frigear-nu/subscriptions": "0.1.1",
38
- "@frigear-nu/ui": "0.1.1",
39
- "@frigear-nu/auth": "0.1.2",
40
- "@frigear-nu/mcp": "0.1.1",
41
- "@frigear-nu/users": "1.1.2",
33
+ "@frigear-nu/admin": "0.1.2",
34
+ "@frigear-nu/module-kit": "1.1.2",
35
+ "@frigear-nu/payments": "0.1.2",
36
+ "@frigear-nu/stripe": "0.1.2",
37
+ "@frigear-nu/subscriptions": "0.1.2",
38
+ "@frigear-nu/ui": "0.1.2",
39
+ "@frigear-nu/auth": "0.1.3",
40
+ "@frigear-nu/mcp": "0.1.2",
41
+ "@frigear-nu/users": "1.1.3",
42
42
  "@libsql/client": "^0.18.0",
43
43
  "@nuxt/kit": "^4.6.1",
44
44
  "@vueuse/core": "^15.0.0",
@@ -51,9 +51,9 @@
51
51
  "zod": "^4.6.5"
52
52
  },
53
53
  "peerDependencies": {
54
- "@frigear-nu/menu": "0.1.1",
55
- "@frigear-nu/widget": "0.1.1",
56
- "@nuxt/ui": "^4.11.2",
54
+ "@frigear-nu/menu": "0.1.2",
55
+ "@frigear-nu/widget": "0.1.2",
56
+ "@nuxt/ui": "^4.12.0",
57
57
  "@nuxthub/core": "^0.10.8"
58
58
  },
59
59
  "peerDependenciesMeta": {
@@ -68,14 +68,14 @@
68
68
  }
69
69
  },
70
70
  "devDependencies": {
71
- "@frigear-nu/menu": "0.1.1",
72
- "@frigear-nu/widget": "0.1.1",
71
+ "@frigear-nu/menu": "0.1.2",
72
+ "@frigear-nu/widget": "0.1.2",
73
73
  "@nuxt/devtools": "^3.4.2",
74
74
  "@nuxt/eslint-config": "^1.17.0",
75
75
  "@nuxt/module-builder": "^1.0.3",
76
76
  "@nuxt/schema": "^4.6.1",
77
77
  "@nuxt/test-utils": "^4.3.2",
78
- "@nuxt/ui": "^4.11.2",
78
+ "@nuxt/ui": "^4.12.0",
79
79
  "@nuxthub/core": "^0.10.8",
80
80
  "@types/node": "^26.6.5",
81
81
  "changelogen": "^0.6.2",