feat(events): markdown body, registered indicator, drop capacity counters

- Public event detail page renders description/series-description/content
  as markdown via useMarkdown, with prose styles; agenda becomes an
  unordered list with the same custom bullets.
- Events list API returns `isRegistered` per event (derived from the
  requester's registrations, ignoring cancelled rows), and the list page
  shows a "Registered" tag. Stops exposing the full registrations array
  in the list response.
- Removes the seats/sold-out/limited capacity UI from list and detail
  pages.
- EventTicketPurchase: minor template formatting (self-closing inputs,
  prettier wrapping).
This commit is contained in:
Jennie Robinson Faber 2026-05-21 17:50:48 +01:00
parent 2ffaf0ef09
commit 622cc8e53b
4 changed files with 176 additions and 118 deletions

View file

@ -38,14 +38,14 @@
<!-- Already Registered -->
<div v-else-if="ticketInfo?.alreadyRegistered" class="ticket-panel">
<div class="box-title">Registration</div>
<p class="ticket-status" style="color: var(--green)">
You're Registered!
</p>
<p class="ticket-detail">
<template v-if="ticketInfo.viaSeriesPass">
You have access to this event via your series pass for
<strong>{{ ticketInfo.series?.title }}</strong>.
<strong>{{ ticketInfo.series?.title }}</strong
>.
</template>
<template v-else>
You're all set for this event. Check your email for confirmation
@ -70,13 +70,11 @@
<!-- Registration (logged-in member) -->
<div
v-if="ticketInfo.available && !ticketInfo.alreadyRegistered && isLoggedIn"
v-if="
ticketInfo.available && !ticketInfo.alreadyRegistered && isLoggedIn
"
class="ticket-panel"
>
<div class="box-title">
{{ ticketInfo.isFree ? "Register" : "Purchase Ticket" }}
</div>
<p
v-if="ticketInfo.isMember && ticketInfo.isFree"
class="ticket-notice"
@ -90,8 +88,7 @@
class="ticket-notice"
style="color: var(--candle)"
>
Payment of {{ ticketInfo.formattedPrice }} will be processed
securely
Payment of {{ ticketInfo.formattedPrice }} will be processed securely
</p>
<button
@ -129,7 +126,7 @@
autocomplete="name"
required
:disabled="processing"
>
/>
</div>
<div class="field">
@ -142,7 +139,7 @@
autocomplete="email"
required
:disabled="processing"
>
/>
</div>
<p
@ -160,11 +157,15 @@
v-model="form.createAccount"
type="checkbox"
:disabled="processing"
/>
<span
>Create a free guest account so I can manage my
registration</span
>
<span>Create a free guest account so I can manage my registration</span>
</label>
<p class="field-hint consent-hint">
Guest accounts let you view your tickets and register faster next time. We won't add you to member communications.
Guest accounts let you view your tickets and register faster next
time. We won't add you to member communications.
</p>
</div>
@ -190,24 +191,18 @@
class="ticket-panel"
>
<div class="box-title">Waitlist</div>
<p class="ticket-status" style="color: var(--ember)">
Event Sold Out
</p>
<p class="ticket-status" style="color: var(--ember)">Event Sold Out</p>
<p class="ticket-detail">
This event is currently at capacity. Join the waitlist to be notified
if spots become available.
</p>
<button class="btn" @click="handleJoinWaitlist">
Join Waitlist
</button>
<button class="btn" @click="handleJoinWaitlist">Join Waitlist</button>
</div>
<!-- Sold Out (No Waitlist) -->
<div v-else-if="!ticketInfo.available" class="ticket-panel">
<div class="box-title">Tickets</div>
<p class="ticket-status" style="color: var(--ember)">
Event Sold Out
</p>
<p class="ticket-status" style="color: var(--ember)">Event Sold Out</p>
<p class="ticket-detail">
Unfortunately, this event is at capacity and no longer accepting
registrations.
@ -311,7 +306,9 @@ const fetchTicketInfo = async (emailOverride = null) => {
}
// Regular ticket availability check
const params = effectiveEmail ? `?email=${encodeURIComponent(effectiveEmail)}` : "";
const params = effectiveEmail
? `?email=${encodeURIComponent(effectiveEmail)}`
: "";
const response = await $fetch(
`/api/events/${props.eventId}/tickets/available${params}`,
);