MyEventsPage disables the buy button at half capacity (same bug as events#34) #173

Closed
opened 2026-09-27 21:04:39 +00:00 by padreug · 0 comments
Owner

MyEventsPage.vue subtracts sold from amount_tickets, but those count the same tickets — the backend decrements amount_tickets on every sale and increments sold. Doing both removes each sale twice.

<!-- :176 -->
<span class="text-foreground">{{ event.amount_tickets - event.sold }}</span>

<!-- :190 -->
:disabled="
  event.status !== 'approved' ||
  event.amount_tickets <= event.sold ||
  !isAuthenticated
"

This is the frontend copy of aiolabs/events#34. The backend half shipped in aiolabs/events#59 (v1.6.1-aio.17), so the server now sells correctly while the webapp refuses to let people buy.

Impact

Because amount_tickets + sold is the original capacity, amount_tickets <= sold first becomes true at exactly the halfway point. So the purchase button disables itself once an event has sold half its seats, and "Tickets Available" under-reports the whole way there.

Measured on aio-demo, 16 of 24 live events are in the affected range and 3 are already past the threshold:

Tech Meetup            9 remaining,  11 sold  ->  shows -2,  button DISABLED
pups leash training    2 remaining,   3 sold  ->  shows -1,  button DISABLED
Art therapy            5 remaining,   5 sold  ->  shows  0,  button DISABLED
Transhumance           9 remaining,   8 sold  ->  shows  1

The backend confirms those are sellable — POST /events/api/v1/tickets/KVhCGUrGFP6UyBuEaRA5Zd with quantity 10 returns "Only 9 ticket(s) remaining", not sold out.

Fix

amount_tickets is the remaining count. Don't subtract:

<span class="text-foreground">{{ event.amount_tickets }}</span>

:disabled="
  event.status !== 'approved' ||
  event.amount_tickets < 1 ||
  !isAuthenticated
"

Worth grepping for the same pattern elsewhere in the module while in there. EventDetailPage.vue and EventCard.vue read ticketInfo.available (straight from the tickets_available tag) and are correct; this page reads the REST Event shape instead, which is how it diverged.

Second, smaller change — needed to unblock events#34

CreateEventDialog.vue:432 omits the field on a falsy value:

if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets

So a capacity of 0 is dropped from the payload entirely. aiolabs/events wants to make amount_tickets required (ge=1) — the other half of events#34 — and that would 422 this dialog for exactly the case users currently pick. Changing the check to an explicit !== undefined (and eventually requiring ≥1 in the zod schema, currently .min(0).default(0)) unblocks it.

Ordering: this issue ships first, then the events-side ge=1.

Context

Zero-capacity events are being fixed backend-side in aiolabs/events#62 — they previously advertised "Unlimited tickets" on the card while the purchase returned 410. The available === undefined → "Unlimited tickets" branch in EventDetailPage.vue stays as the fallback for NIP-52 events published by other clients, which is deliberate.

`MyEventsPage.vue` subtracts `sold` from `amount_tickets`, but those count the same tickets — the backend decrements `amount_tickets` on every sale *and* increments `sold`. Doing both removes each sale twice. ```vue <!-- :176 --> <span class="text-foreground">{{ event.amount_tickets - event.sold }}</span> <!-- :190 --> :disabled=" event.status !== 'approved' || event.amount_tickets <= event.sold || !isAuthenticated " ``` This is the frontend copy of `aiolabs/events#34`. The backend half shipped in `aiolabs/events#59` (`v1.6.1-aio.17`), so **the server now sells correctly while the webapp refuses to let people buy.** ## Impact Because `amount_tickets + sold` is the original capacity, `amount_tickets <= sold` first becomes true at exactly the **halfway point**. So the purchase button disables itself once an event has sold half its seats, and "Tickets Available" under-reports the whole way there. Measured on aio-demo, 16 of 24 live events are in the affected range and 3 are already past the threshold: ``` Tech Meetup 9 remaining, 11 sold -> shows -2, button DISABLED pups leash training 2 remaining, 3 sold -> shows -1, button DISABLED Art therapy 5 remaining, 5 sold -> shows 0, button DISABLED Transhumance 9 remaining, 8 sold -> shows 1 ``` The backend confirms those are sellable — `POST /events/api/v1/tickets/KVhCGUrGFP6UyBuEaRA5Zd` with quantity 10 returns *"Only 9 ticket(s) remaining"*, not sold out. ## Fix `amount_tickets` **is** the remaining count. Don't subtract: ```vue <span class="text-foreground">{{ event.amount_tickets }}</span> :disabled=" event.status !== 'approved' || event.amount_tickets < 1 || !isAuthenticated " ``` Worth grepping for the same pattern elsewhere in the module while in there. `EventDetailPage.vue` and `EventCard.vue` read `ticketInfo.available` (straight from the `tickets_available` tag) and are correct; this page reads the REST `Event` shape instead, which is how it diverged. ## Second, smaller change — needed to unblock events#34 `CreateEventDialog.vue:432` omits the field on a falsy value: ```js if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets ``` So a capacity of `0` is dropped from the payload entirely. `aiolabs/events` wants to make `amount_tickets` required (`ge=1`) — the other half of events#34 — and that would **422 this dialog** for exactly the case users currently pick. Changing the check to an explicit `!== undefined` (and eventually requiring ≥1 in the zod schema, currently `.min(0).default(0)`) unblocks it. Ordering: this issue ships first, then the events-side `ge=1`. ## Context Zero-capacity events are being fixed backend-side in `aiolabs/events#62` — they previously advertised "Unlimited tickets" on the card while the purchase returned 410. The `available === undefined → "Unlimited tickets"` branch in `EventDetailPage.vue` stays as the fallback for NIP-52 events published by other clients, which is deliberate.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/webapp#173
No description provided.