@frigear-nu/events 0.0.10 → 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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -4
- package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +8 -8
- package/dist/runtime/app/components/EventAdminAddonGroups.vue +46 -55
- package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +8 -8
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +2 -2
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +3 -4
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +2 -2
- package/dist/runtime/app/components/EventCard.vue +7 -12
- package/dist/runtime/app/components/EventFormSlideover.d.vue.ts +13 -0
- package/dist/runtime/app/components/EventFormSlideover.vue +70 -0
- package/dist/runtime/app/components/EventFormSlideover.vue.d.ts +13 -0
- package/dist/runtime/app/components/EventTicketCard.vue +5 -7
- package/dist/runtime/app/components/EventTicketPicker.vue +5 -6
- package/dist/runtime/app/composables/index.d.ts +2 -0
- package/dist/runtime/app/composables/index.js +2 -0
- package/dist/runtime/app/composables/useEventActions.d.ts +26 -0
- package/dist/runtime/app/composables/useEventActions.js +61 -0
- package/dist/runtime/app/composables/useEventLinks.d.ts +12 -0
- package/dist/runtime/app/composables/useEventLinks.js +5 -0
- package/dist/runtime/app/composables/useEvents.d.ts +5 -2
- package/dist/runtime/app/pages/admin-event.vue +33 -21
- package/dist/runtime/app/pages/admin.vue +61 -135
- package/dist/runtime/app/pages/event.d.vue.ts +2 -2
- package/dist/runtime/app/pages/event.vue +113 -152
- package/dist/runtime/app/pages/event.vue.d.ts +2 -2
- package/dist/runtime/app/pages/events.vue +33 -31
- package/dist/runtime/app/pages/tickets.vue +21 -28
- package/dist/runtime/app/tabs/Attendees.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Attendees.vue +64 -60
- package/dist/runtime/app/tabs/Attendees.vue.d.ts +2 -2
- package/dist/runtime/app/tabs/Details.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Details.vue +4 -5
- package/dist/runtime/app/tabs/Details.vue.d.ts +2 -2
- package/dist/runtime/app/tabs/Tickets.d.vue.ts +2 -2
- package/dist/runtime/app/tabs/Tickets.vue +92 -99
- package/dist/runtime/app/tabs/Tickets.vue.d.ts +2 -2
- package/dist/runtime/app/utils/admin-event.d.ts +9 -12
- package/dist/runtime/app/utils/admin-event.js +0 -17
- package/dist/runtime/app/widgets/UpcomingEvents.vue +7 -9
- package/dist/runtime/app/widgets/UpcomingEventsStat.vue +5 -6
- package/dist/runtime/i18n/lang/da.json +111 -3
- package/dist/runtime/i18n/lang/en.json +111 -3
- package/dist/runtime/menus.js +2 -4
- package/dist/runtime/server/utils/admin.d.ts +5 -5
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +1 -0
- package/dist/runtime/utils/links.d.ts +21 -0
- package/dist/runtime/utils/links.js +12 -0
- package/package.json +21 -21
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { withMdcData } from "@frigear-nu/ui/markdown";
|
|
3
|
-
import { formatMoney as formatPrice } from "@frigear-nu/ui/money";
|
|
4
3
|
import { DEFAULT_CURRENCY, ticketMdcData } from "../../utils";
|
|
5
4
|
const props = defineProps({
|
|
6
5
|
ticketTypes: { type: Array, required: true },
|
|
@@ -9,7 +8,7 @@ const props = defineProps({
|
|
|
9
8
|
});
|
|
10
9
|
const emit = defineEmits(["purchase"]);
|
|
11
10
|
const t = useModuleT("events");
|
|
12
|
-
const
|
|
11
|
+
const format = useFormat();
|
|
13
12
|
const hasMdc = typeof resolveComponent("MDC") !== "string";
|
|
14
13
|
const tr = useEventsTranslate();
|
|
15
14
|
const selectedId = ref();
|
|
@@ -83,7 +82,7 @@ function toggle(groupId, addonId, multiple) {
|
|
|
83
82
|
<span
|
|
84
83
|
v-if="!type.hidePrice"
|
|
85
84
|
class="ml-auto font-bold text-primary"
|
|
86
|
-
>{{ type.price === 0 ? t("picker.free", "Free") :
|
|
85
|
+
>{{ type.price === 0 ? t("picker.free", "Free") : format.money(type.price, type.currency) }}</span>
|
|
87
86
|
</div>
|
|
88
87
|
<div
|
|
89
88
|
v-if="type.description"
|
|
@@ -91,7 +90,7 @@ function toggle(groupId, addonId, multiple) {
|
|
|
91
90
|
>
|
|
92
91
|
<MDC
|
|
93
92
|
v-if="hasMdc"
|
|
94
|
-
:value="withMdcData(tr(type.description), ticketMdcData(type, tr(type.name), locale))"
|
|
93
|
+
:value="withMdcData(tr(type.description), ticketMdcData(type, tr(type.name), format.locale.value))"
|
|
95
94
|
class="prose prose-sm max-w-none text-muted"
|
|
96
95
|
/>
|
|
97
96
|
<template v-else>
|
|
@@ -128,7 +127,7 @@ function toggle(groupId, addonId, multiple) {
|
|
|
128
127
|
>
|
|
129
128
|
{{ t("picker.soldOut", "Sold out") }}
|
|
130
129
|
</UBadge>
|
|
131
|
-
<span class="ml-auto text-sm">{{ addon.price === 0 ? t("picker.included", "Included") : `+ ${
|
|
130
|
+
<span class="ml-auto text-sm">{{ addon.price === 0 ? t("picker.included", "Included") : `+ ${format.money(addon.price, selected.currency)}` }}</span>
|
|
132
131
|
</button>
|
|
133
132
|
</fieldset>
|
|
134
133
|
|
|
@@ -140,7 +139,7 @@ function toggle(groupId, addonId, multiple) {
|
|
|
140
139
|
:disabled="!selected || !isAvailable(selected) || missingRequired"
|
|
141
140
|
@click="emit('purchase', selected.id, addonIds)"
|
|
142
141
|
>
|
|
143
|
-
{{ total === 0 ? t("picker.getFree", "Get free ticket") : `${t("picker.buy", "Buy ticket")} \xB7 ${
|
|
142
|
+
{{ total === 0 ? t("picker.getFree", "Get free ticket") : `${t("picker.buy", "Buy ticket")} \xB7 ${format.money(total, selected?.currency ?? DEFAULT_CURRENCY)}` }}
|
|
144
143
|
</UButton>
|
|
145
144
|
<UAlert
|
|
146
145
|
v-else
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DropdownMenuItem } from '@nuxt/ui';
|
|
2
|
+
import type { StaffEventSummary } from '../utils/admin-event.js';
|
|
3
|
+
export type EventMenuOptions = {
|
|
4
|
+
/** Items for the middle group, between the edits and the delete action. */
|
|
5
|
+
extra?: DropdownMenuItem[];
|
|
6
|
+
/** Runs after the event was deleted (refresh a list, leave the detail page). */
|
|
7
|
+
onRemoved?: () => unknown;
|
|
8
|
+
/** Leave out "Open", e.g. on the event's own page. Defaults to `true`. */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Leave out "View public page", e.g. when the page shows its own button. Defaults to `true`. */
|
|
11
|
+
viewPublic?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* What staff can do with an event, shared by the list and the event page: `create` (a slideover that resolves with the
|
|
15
|
+
* new event, or `false` when cancelled), `duplicate`, `remove` (confirms first) and `menu(event)`, the row menu in the
|
|
16
|
+
* same order everywhere: Open, View public page, Duplicate | extras | Delete.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useEventActions(): {
|
|
19
|
+
create: () => Promise<{
|
|
20
|
+
id: string;
|
|
21
|
+
slug: string;
|
|
22
|
+
} | false | undefined>;
|
|
23
|
+
duplicate: (event: StaffEventSummary) => Promise<any>;
|
|
24
|
+
remove: (event: StaffEventSummary) => Promise<boolean>;
|
|
25
|
+
menu: (event: StaffEventSummary, options?: EventMenuOptions) => DropdownMenuItem[][];
|
|
26
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import EventFormSlideover from "../components/EventFormSlideover.vue";
|
|
2
|
+
export function useEventActions() {
|
|
3
|
+
const t = useModuleT("events");
|
|
4
|
+
const overlay = useOverlay();
|
|
5
|
+
const confirm = useConfirmDialog();
|
|
6
|
+
const { run } = useAction();
|
|
7
|
+
const links = useEventLinks();
|
|
8
|
+
const tr = useEventsTranslate();
|
|
9
|
+
const hasPublicPage = !!useRuntimeConfig().public.events?.pages?.event;
|
|
10
|
+
async function create() {
|
|
11
|
+
return overlay.create(EventFormSlideover, { destroyOnClose: true }).open();
|
|
12
|
+
}
|
|
13
|
+
async function duplicate(event) {
|
|
14
|
+
const copy = await run(
|
|
15
|
+
() => $fetch(`/api/events/admin/events/${event.id}/clone`, { method: "POST" }),
|
|
16
|
+
{ success: t("admin.duplicated", "Event duplicated as a draft") }
|
|
17
|
+
);
|
|
18
|
+
if (copy) await navigateTo(links.adminEvent(copy.id));
|
|
19
|
+
return copy;
|
|
20
|
+
}
|
|
21
|
+
async function remove(event) {
|
|
22
|
+
const name = tr(event.name);
|
|
23
|
+
const confirmed = await confirm({
|
|
24
|
+
title: t("admin.deleteTitle", "Delete {name}?", { name }),
|
|
25
|
+
description: t("admin.deleteDescription", "Its ticket types and add-ons are deleted too. Events with paid tickets cannot be deleted."),
|
|
26
|
+
confirmText: t("admin.deleteAction", "Delete event"),
|
|
27
|
+
destructive: true
|
|
28
|
+
});
|
|
29
|
+
if (!confirmed) return false;
|
|
30
|
+
const done = await run(
|
|
31
|
+
() => $fetch(`/api/events/admin/events/${event.id}`, { method: "DELETE" }),
|
|
32
|
+
{ success: t("admin.deleted", "Deleted {name}", { name }), error: t("admin.deleteFailed", "Could not delete the event") }
|
|
33
|
+
);
|
|
34
|
+
return done !== void 0;
|
|
35
|
+
}
|
|
36
|
+
function menu(event, options = {}) {
|
|
37
|
+
const main = [];
|
|
38
|
+
if (options.open !== false) {
|
|
39
|
+
main.push({ label: t("admin.open", "Open"), icon: "i-lucide-arrow-right", to: links.adminEvent(event.id) });
|
|
40
|
+
}
|
|
41
|
+
if (options.viewPublic !== false && hasPublicPage && event.status === "published") {
|
|
42
|
+
main.push({ label: t("admin.viewPublic", "View public page"), icon: "i-lucide-external-link", to: links.event(event.slug), target: "_blank" });
|
|
43
|
+
}
|
|
44
|
+
const edits = [
|
|
45
|
+
{ label: t("admin.clone", "Duplicate"), icon: "i-lucide-copy", onSelect: () => {
|
|
46
|
+
void duplicate(event);
|
|
47
|
+
} },
|
|
48
|
+
...options.extra ?? []
|
|
49
|
+
];
|
|
50
|
+
const destructive = [{
|
|
51
|
+
label: t("admin.deleteAction", "Delete event"),
|
|
52
|
+
icon: "i-lucide-trash-2",
|
|
53
|
+
color: "error",
|
|
54
|
+
onSelect: async () => {
|
|
55
|
+
if (await remove(event)) await options.onRemoved?.();
|
|
56
|
+
}
|
|
57
|
+
}];
|
|
58
|
+
return [main, edits, destructive].filter((group) => group.length);
|
|
59
|
+
}
|
|
60
|
+
return { create, duplicate, remove, menu };
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-aware links to the event pages (`/events`, `/events/:slug`, `/account/tickets`, the admin list and event
|
|
3
|
+
* page). Always use these instead of building the path by hand: they keep the locale prefix (`/en/events` stays under
|
|
4
|
+
* `/en`) and follow `events.settings.pages`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useEventLinks(): {
|
|
7
|
+
events: () => string;
|
|
8
|
+
event: (slug: string) => string;
|
|
9
|
+
tickets: () => string;
|
|
10
|
+
admin: () => string;
|
|
11
|
+
adminEvent: (id: string) => string;
|
|
12
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Translated } from '../../../types.js';
|
|
1
|
+
import type { TicketDto, Translated } from '../../../types.js';
|
|
2
2
|
export declare function useEvents(): Promise<{
|
|
3
3
|
events: any;
|
|
4
4
|
grouped: any;
|
|
@@ -11,7 +11,10 @@ export declare function useEvent(slug: MaybeRefOrGetter<string>): Promise<{
|
|
|
11
11
|
status: any;
|
|
12
12
|
error: any;
|
|
13
13
|
refresh: any;
|
|
14
|
-
purchase: (ticketTypeId: string, addonIds?: string[]) => Promise<
|
|
14
|
+
purchase: (ticketTypeId: string, addonIds?: string[]) => Promise<{
|
|
15
|
+
ticket: TicketDto;
|
|
16
|
+
url?: string;
|
|
17
|
+
}>;
|
|
15
18
|
}>;
|
|
16
19
|
export declare function useMyTickets(): {
|
|
17
20
|
tickets: any;
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
import AttendeesTab from "../tabs/Attendees.vue";
|
|
3
3
|
import DetailsTab from "../tabs/Details.vue";
|
|
4
4
|
import TicketsTab from "../tabs/Tickets.vue";
|
|
5
|
-
import { toLocalInput
|
|
5
|
+
import { toLocalInput } from "../utils/admin-event";
|
|
6
6
|
defineOptions({ name: "EventsAdminEventPage" });
|
|
7
7
|
const t = useModuleT("events");
|
|
8
8
|
const route = useRoute();
|
|
9
9
|
const tr = useEventsTranslate();
|
|
10
|
-
const
|
|
10
|
+
const links = useEventLinks();
|
|
11
|
+
const actions = useEventActions();
|
|
12
|
+
const hasPublicPage = !!useRuntimeConfig().public.events?.pages?.event;
|
|
11
13
|
const id = computed(() => String(route.params.id));
|
|
12
|
-
const { busy, run } =
|
|
14
|
+
const { busy, run } = useAction();
|
|
13
15
|
const { data: event, refresh } = await useFetch(() => `/api/events/admin/events/${id.value}`);
|
|
14
16
|
const Tabs = resolveComponent("WidgetTabs");
|
|
15
17
|
const tabs = typeof Tabs === "string" ? void 0 : Tabs;
|
|
@@ -31,7 +33,6 @@ watch(event, (value) => {
|
|
|
31
33
|
});
|
|
32
34
|
}, { immediate: true });
|
|
33
35
|
const statuses = computed(() => ["draft", "published", "archived"].map((value) => ({ value, label: t(`admin.statuses.${value}`, value.charAt(0).toUpperCase() + value.slice(1)) })));
|
|
34
|
-
const statusColor = { draft: "warning", published: "success", archived: "neutral" };
|
|
35
36
|
const saveFacts = () => run(async () => {
|
|
36
37
|
await $fetch(`/api/events/admin/events/${id.value}`, {
|
|
37
38
|
method: "PATCH",
|
|
@@ -44,33 +45,43 @@ const saveFacts = () => run(async () => {
|
|
|
44
45
|
}
|
|
45
46
|
});
|
|
46
47
|
await refresh();
|
|
47
|
-
}, t("admin.saved", "Saved."));
|
|
48
|
+
}, { success: t("admin.saved", "Saved.") });
|
|
48
49
|
</script>
|
|
49
50
|
|
|
50
51
|
<template>
|
|
51
|
-
<
|
|
52
|
-
|
|
52
|
+
<div
|
|
53
|
+
v-if="event"
|
|
54
|
+
class="flex flex-col gap-4"
|
|
55
|
+
>
|
|
56
|
+
<PageHeading
|
|
53
57
|
:title="tr(event.name)"
|
|
54
|
-
:
|
|
58
|
+
:breadcrumb="[{ label: t('admin.title', 'Events'), to: links.admin() }, { label: tr(event.name) }]"
|
|
55
59
|
>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
<template #badge>
|
|
61
|
+
<UBadge
|
|
62
|
+
variant="subtle"
|
|
63
|
+
:color="statusColor(event.status)"
|
|
64
|
+
:label="t(`admin.statuses.${event.status}`, event.status)"
|
|
65
|
+
/>
|
|
66
|
+
</template>
|
|
61
67
|
<template #actions>
|
|
62
68
|
<UButton
|
|
63
|
-
v-if="event.status === 'published' &&
|
|
64
|
-
:to="
|
|
69
|
+
v-if="event.status === 'published' && hasPublicPage"
|
|
70
|
+
:to="links.event(event.slug)"
|
|
65
71
|
variant="outline"
|
|
66
72
|
color="neutral"
|
|
67
73
|
icon="i-lucide-external-link"
|
|
68
74
|
:label="t('admin.viewPublic', 'View public page')"
|
|
69
75
|
/>
|
|
76
|
+
<RowMenu
|
|
77
|
+
variant="outline"
|
|
78
|
+
:subject="tr(event.name)"
|
|
79
|
+
:items="actions.menu(event, { open: false, viewPublic: false, onRemoved: () => navigateTo(links.admin()) })"
|
|
80
|
+
/>
|
|
70
81
|
</template>
|
|
71
|
-
</
|
|
82
|
+
</PageHeading>
|
|
72
83
|
|
|
73
|
-
<
|
|
84
|
+
<SectionCard :title="t('admin.keyFacts', 'Event')">
|
|
74
85
|
<form
|
|
75
86
|
id="admin-event-facts"
|
|
76
87
|
class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
|
|
@@ -115,11 +126,11 @@ const saveFacts = () => run(async () => {
|
|
|
115
126
|
type="submit"
|
|
116
127
|
form="admin-event-facts"
|
|
117
128
|
:loading="busy"
|
|
118
|
-
:label="t('admin.
|
|
129
|
+
:label="t('admin.saveEvent', 'Save event')"
|
|
119
130
|
/>
|
|
120
131
|
</div>
|
|
121
132
|
</template>
|
|
122
|
-
</
|
|
133
|
+
</SectionCard>
|
|
123
134
|
|
|
124
135
|
<component
|
|
125
136
|
:is="tabs"
|
|
@@ -142,10 +153,11 @@ const saveFacts = () => run(async () => {
|
|
|
142
153
|
@refresh="refresh"
|
|
143
154
|
/>
|
|
144
155
|
</template>
|
|
145
|
-
</
|
|
146
|
-
<
|
|
156
|
+
</div>
|
|
157
|
+
<UEmpty
|
|
147
158
|
v-else
|
|
148
159
|
icon="i-lucide-calendar-x"
|
|
149
160
|
:title="t('notFound', 'Event not found')"
|
|
161
|
+
:actions="[{ label: t('admin.allEvents', 'All events'), to: links.admin(), color: 'neutral', variant: 'outline' }]"
|
|
150
162
|
/>
|
|
151
163
|
</template>
|
|
@@ -1,154 +1,80 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineOptions({ name: "EventsAdminPage" });
|
|
3
3
|
const t = useModuleT("events");
|
|
4
|
-
const toast = useToast();
|
|
5
|
-
const locale = useEventsLocale();
|
|
6
4
|
const tr = useEventsTranslate();
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
const format = useFormat();
|
|
6
|
+
const links = useEventLinks();
|
|
7
|
+
const actions = useEventActions();
|
|
8
|
+
const { data: events, status, refresh } = await useFetch("/api/events/admin/events", { default: () => [] });
|
|
9
|
+
const { sorting, header } = useTableSort({ id: "startsAt", desc: true });
|
|
10
|
+
const UBadge = resolveComponent("UBadge");
|
|
11
|
+
const RowMenu = resolveComponent("RowMenu");
|
|
12
|
+
const TableIdentity = resolveComponent("TableIdentity");
|
|
13
|
+
const columns = computed(() => [
|
|
14
|
+
{
|
|
15
|
+
id: "name",
|
|
16
|
+
accessorFn: (row) => tr(row.name),
|
|
17
|
+
header: header("name", t("admin.name", "Name")),
|
|
18
|
+
cell: ({ row }) => h(TableIdentity, { title: tr(row.original.name), description: tr(row.original.location) })
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
accessorKey: "status",
|
|
22
|
+
header: header("status", t("admin.status", "Status")),
|
|
23
|
+
cell: ({ row }) => h(UBadge, { variant: "subtle", color: statusColor(row.original.status), label: t(`admin.statuses.${row.original.status}`, row.original.status) })
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessorKey: "startsAt",
|
|
27
|
+
header: header("startsAt", t("admin.starts", "Starts")),
|
|
28
|
+
cell: ({ row }) => row.original.startsAt ? h("span", { class: "tabular-nums" }, format.dateTime(row.original.startsAt)) : emptyCell()
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "actions",
|
|
32
|
+
meta: { class: { th: "w-0", td: "w-0" } },
|
|
33
|
+
cell: ({ row }) => h(RowMenu, { items: actions.menu(row.original, { onRemoved: refresh }), subject: tr(row.original.name) })
|
|
25
34
|
}
|
|
26
|
-
|
|
27
|
-
const creating = ref(false);
|
|
28
|
-
const saving = ref(false);
|
|
29
|
-
const draft = reactive({ name: void 0, slug: "", startsAt: "", location: void 0 });
|
|
30
|
-
const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
31
|
-
const draftName = computed(() => tr(draft.name));
|
|
32
|
-
watch(draftName, (name, previous) => {
|
|
33
|
-
if (draft.slug === slugify(previous)) draft.slug = slugify(name);
|
|
34
|
-
});
|
|
35
|
+
]);
|
|
35
36
|
async function create() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const created = await $fetch("/api/events/admin/events", {
|
|
39
|
-
method: "POST",
|
|
40
|
-
body: { name: draft.name, slug: draft.slug, startsAt: draft.startsAt, location: draft.location || null, status: "draft" }
|
|
41
|
-
});
|
|
42
|
-
await refresh();
|
|
43
|
-
await navigateTo(eventTo(created.id));
|
|
44
|
-
} catch (cause) {
|
|
45
|
-
toast.add({ title: cause.statusMessage ?? t("admin.error", "Something went wrong."), color: "error" });
|
|
46
|
-
} finally {
|
|
47
|
-
saving.value = false;
|
|
48
|
-
}
|
|
37
|
+
const created = await actions.create();
|
|
38
|
+
if (created) await navigateTo(links.adminEvent(created.id));
|
|
49
39
|
}
|
|
50
40
|
</script>
|
|
51
41
|
|
|
52
42
|
<template>
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
|
|
43
|
+
<div class="flex flex-col gap-4">
|
|
44
|
+
<PageHeading
|
|
45
|
+
:title="t('admin.title', 'Events')"
|
|
46
|
+
:description="t('admin.listDescription', 'Events, their ticket types and attendees.')"
|
|
47
|
+
>
|
|
48
|
+
<template #actions>
|
|
56
49
|
<UButton
|
|
57
50
|
icon="i-lucide-plus"
|
|
58
51
|
:label="t('admin.create', 'New event')"
|
|
59
|
-
@click="
|
|
52
|
+
@click="create"
|
|
60
53
|
/>
|
|
61
54
|
</template>
|
|
62
|
-
</
|
|
55
|
+
</PageHeading>
|
|
63
56
|
|
|
64
|
-
<
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
@select="(_, row2) => navigateTo(eventTo(row2.original.id))"
|
|
57
|
+
<TableCard
|
|
58
|
+
:loading="status === 'pending' && !events.length"
|
|
59
|
+
:refreshing="status === 'pending' && !!events.length"
|
|
68
60
|
>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@click.stop="clone(row.original.id)"
|
|
84
|
-
/>
|
|
85
|
-
</template>
|
|
86
|
-
<template #empty>
|
|
87
|
-
<AdminEmpty
|
|
88
|
-
icon="i-lucide-calendar"
|
|
89
|
-
:title="t('admin.empty', 'No events yet')"
|
|
90
|
-
:description="t('admin.emptyDescription', 'Create the first event to start selling tickets.')"
|
|
91
|
-
/>
|
|
92
|
-
</template>
|
|
93
|
-
</AdminTable>
|
|
94
|
-
|
|
95
|
-
<UModal
|
|
96
|
-
v-model:open="creating"
|
|
97
|
-
:title="t('admin.create', 'New event')"
|
|
98
|
-
>
|
|
99
|
-
<template #body>
|
|
100
|
-
<form
|
|
101
|
-
id="admin-event-form"
|
|
102
|
-
class="flex flex-col gap-4"
|
|
103
|
-
@submit.prevent="create"
|
|
104
|
-
>
|
|
105
|
-
<UFormField :label="t('admin.name', 'Name')">
|
|
106
|
-
<TranslatedInput
|
|
107
|
-
v-model="draft.name"
|
|
108
|
-
required
|
|
109
|
-
/>
|
|
110
|
-
</UFormField>
|
|
111
|
-
<UFormField
|
|
112
|
-
:label="t('admin.slug', 'URL name')"
|
|
113
|
-
:description="t('admin.slugHelp', 'Lowercase letters, numbers and dashes.')"
|
|
114
|
-
>
|
|
115
|
-
<UInput
|
|
116
|
-
v-model="draft.slug"
|
|
117
|
-
class="w-full"
|
|
118
|
-
required
|
|
119
|
-
pattern="[a-z0-9]+(-[a-z0-9]+)*"
|
|
120
|
-
/>
|
|
121
|
-
</UFormField>
|
|
122
|
-
<UFormField :label="t('admin.starts', 'Starts')">
|
|
123
|
-
<UInput
|
|
124
|
-
v-model="draft.startsAt"
|
|
125
|
-
type="datetime-local"
|
|
126
|
-
class="w-full"
|
|
127
|
-
required
|
|
128
|
-
/>
|
|
129
|
-
</UFormField>
|
|
130
|
-
<UFormField :label="t('admin.location', 'Location')">
|
|
131
|
-
<TranslatedInput v-model="draft.location" />
|
|
132
|
-
</UFormField>
|
|
133
|
-
</form>
|
|
134
|
-
</template>
|
|
135
|
-
<template #footer>
|
|
136
|
-
<div class="flex w-full justify-end gap-2">
|
|
137
|
-
<UButton
|
|
138
|
-
variant="subtle"
|
|
139
|
-
color="neutral"
|
|
140
|
-
:label="t('admin.cancel', 'Cancel')"
|
|
141
|
-
@click="creating = false"
|
|
142
|
-
/>
|
|
143
|
-
<UButton
|
|
144
|
-
type="submit"
|
|
145
|
-
form="admin-event-form"
|
|
146
|
-
:loading="saving"
|
|
147
|
-
:disabled="!draftName"
|
|
148
|
-
:label="t('admin.createAction', 'Create draft')"
|
|
61
|
+
<UTable
|
|
62
|
+
v-model:sorting="sorting"
|
|
63
|
+
:data="events"
|
|
64
|
+
:columns="columns"
|
|
65
|
+
:ui="tableUi()"
|
|
66
|
+
@select="(_, row) => navigateTo(links.adminEvent(row.original.id))"
|
|
67
|
+
>
|
|
68
|
+
<template #empty>
|
|
69
|
+
<UEmpty
|
|
70
|
+
variant="naked"
|
|
71
|
+
icon="i-lucide-calendar"
|
|
72
|
+
:title="t('admin.empty', 'No events yet')"
|
|
73
|
+
:description="t('admin.emptyDescription', 'Create the first event to start selling tickets.')"
|
|
74
|
+
:actions="[{ label: t('admin.create', 'New event'), icon: 'i-lucide-plus', onClick: create }]"
|
|
149
75
|
/>
|
|
150
|
-
</
|
|
151
|
-
</
|
|
152
|
-
</
|
|
153
|
-
</
|
|
76
|
+
</template>
|
|
77
|
+
</UTable>
|
|
78
|
+
</TableCard>
|
|
79
|
+
</div>
|
|
154
80
|
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
declare var
|
|
1
|
+
declare var __VLS_91: {
|
|
2
2
|
event: any;
|
|
3
3
|
body: any;
|
|
4
4
|
};
|
|
5
5
|
type __VLS_Slots = {} & {
|
|
6
|
-
body?: (props: typeof
|
|
6
|
+
body?: (props: typeof __VLS_91) => any;
|
|
7
7
|
};
|
|
8
8
|
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
9
9
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|