From b46cdf393c852dc23b7a59fdfe9d9607071cf0ea Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 27 Sep 2026 23:08:59 +0200 Subject: [PATCH] fix(events): stop subtracting sold from amount_tickets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `amount_tickets` IS the remaining count — the backend decrements it on every sale and increments `sold`, so the two describe the same tickets. MyEventsPage subtracted one from the other, removing each sale twice. Because remaining + sold is the original capacity, `amount_tickets <= sold` first becomes true at exactly the halfway point. So the purchase button disabled itself once an event had sold half its seats, and "Tickets Available" under-reported all the way there. The backend half of this shipped as aiolabs/events#59 in v1.6.1-aio.17, which makes the mismatch worse rather than better: the server now sells correctly while the webapp refuses to let anyone buy. On aio-demo three events are past the threshold — "Tech Meetup" has 9 tickets left, shows -2, and the button is dead. The API confirms they are sellable. Also makes ticket capacity a required field, at least 1. A capacity of 0 used to mean "unlimited", but nothing on the backend agreed: both api_get_event and api_ticket_create read `amount_tickets < 1` as sold out, so a 0 produced an event that advertised unlimited tickets on the card and returned 410 to every purchase. aiolabs/events#62 stops publishing that lie; this stops the form creating it. The payload check moves from falsy to `!== undefined`, which is what unblocks the backend making the field required — a falsy check dropped 0 from the request entirely, so a server-side `ge=1` would have 422'd this dialog for precisely the case users were picking. Editing an existing zero-capacity event now opens with the field empty rather than prefilled with 0, so the organiser has to choose. Closes #173 --- .../events/components/CreateEventDialog.vue | 19 +++++++++++++------ src/modules/events/views/MyEventsPage.vue | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index ea10109..7bd43a0 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -146,7 +146,11 @@ const formSchema = toTypedSchema( .array(z.enum(['lightning', 'fiat'])) .min(1, 'Enable at least one payment method'), fiat_currency: z.string().default("USD"), - amount_tickets: z.number().min(0).max(100000).default(0), + // Required, and at least 1. A capacity of 0 used to mean + // "unlimited", but the backend reads `amount_tickets < 1` as sold + // out everywhere (aiolabs/events#34), so a 0 produced an event that + // advertised unlimited tickets and refused every purchase. + amount_tickets: z.number().min(1, 'Set how many tickets are on sale').max(100000), price_per_ticket: z.number().min(0).default(0), email_notifications: z.boolean().default(false), nostr_notifications: z.boolean().default(false), @@ -195,7 +199,7 @@ const form = useForm({ currency: 'sat', payment_methods: ['lightning'], fiat_currency: 'USD', - amount_tickets: 0, + amount_tickets: undefined, price_per_ticket: 0, email_notifications: false, nostr_notifications: false, @@ -306,7 +310,9 @@ async function populateFromEvent(event: TicketedEvent) { currency: event.currency ?? 'sat', payment_methods: paymentMethodsOf(event), fiat_currency: event.fiat_currency ?? 'USD', - amount_tickets: event.amount_tickets ?? 0, + // 0 is no longer a valid capacity, so an existing zero-capacity event + // opens with the field empty and the organiser has to set a real one. + amount_tickets: event.amount_tickets || undefined, price_per_ticket: event.price_per_ticket ?? 0, email_notifications: event.extra?.email_notifications ?? false, nostr_notifications: event.extra?.nostr_notifications ?? false, @@ -429,7 +435,8 @@ const onSubmit = form.handleSubmit(async (formValues) => { if (acceptsFiat && formValues.fiat_currency) { eventData.fiat_currency = formValues.fiat_currency } - if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets + if (formValues.amount_tickets !== undefined) + eventData.amount_tickets = formValues.amount_tickets if (formValues.price_per_ticket) eventData.price_per_ticket = formValues.price_per_ticket if (selectedCategories.value.length > 0) eventData.categories = selectedCategories.value @@ -693,9 +700,9 @@ const handleOpenChange = (open: boolean) => { Tickets - + - 0 = unlimited + Total on sale diff --git a/src/modules/events/views/MyEventsPage.vue b/src/modules/events/views/MyEventsPage.vue index 4eb0c7c..a6a3405 100644 --- a/src/modules/events/views/MyEventsPage.vue +++ b/src/modules/events/views/MyEventsPage.vue @@ -173,7 +173,7 @@ function handleEventChanged() {
Tickets Available: - {{ event.amount_tickets - event.sold }} + {{ event.amount_tickets }}
Price: @@ -187,7 +187,7 @@ function handleEventChanged() { variant="default" :disabled=" event.status !== 'approved' || - event.amount_tickets <= event.sold || + event.amount_tickets < 1 || !isAuthenticated " @click="handlePurchaseClick(event)" -- 2.55.0