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:
parent
2ffaf0ef09
commit
622cc8e53b
4 changed files with 176 additions and 118 deletions
|
|
@ -45,34 +45,21 @@
|
|||
<span v-if="event.isCancelled" class="cancelled-tag"
|
||||
>cancelled</span
|
||||
>
|
||||
<span v-if="event.isRegistered" class="registered-tag"
|
||||
>Registered</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="event.tagline" class="event-tagline">
|
||||
{{ event.tagline }}
|
||||
</div>
|
||||
<div class="event-sub">
|
||||
<span v-if="event.eventType" class="event-type-tag">{{
|
||||
event.eventType
|
||||
eventTypeLabel(event.eventType)
|
||||
}}</span>
|
||||
<span v-if="event.eventType" class="sep">·</span>
|
||||
<span class="event-location">{{ formatLocation(event) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="event-capacity">
|
||||
<template v-if="event.maxAttendees">
|
||||
<span :class="{ 'seats-warn': isAlmostFull(event) }">
|
||||
{{ event.registeredCount || 0 }}/{{ event.maxAttendees }} seats
|
||||
</span>
|
||||
<span v-if="isSoldOut(event)" class="capacity-badge sold-out"
|
||||
>Sold out</span
|
||||
>
|
||||
<span
|
||||
v-else-if="isAlmostFull(event)"
|
||||
class="capacity-badge limited"
|
||||
>Limited tickets</span
|
||||
>
|
||||
</template>
|
||||
<template v-else>Open</template>
|
||||
</span>
|
||||
<div class="event-badges">
|
||||
<span v-if="event.membersOnly" class="members-badge">Members</span>
|
||||
<CircleBadge v-if="event.circle" :circle="event.circle" />
|
||||
|
|
@ -119,15 +106,20 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { EVENT_TYPES, eventTypeLabel } from "~/config/eventTypes";
|
||||
|
||||
useSiteMeta({
|
||||
title: "Events",
|
||||
description:
|
||||
"Workshops, meetups, and gatherings for game developers practicing cooperative models. Some events are open to the public; others are for Ghost Guild members.",
|
||||
});
|
||||
|
||||
const activeFilter = ref("all");
|
||||
const includePastEvents = ref(false);
|
||||
|
||||
const filterOptions = [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Workshops", value: "workshop" },
|
||||
{ label: "Community", value: "community" },
|
||||
{ label: "Social", value: "social" },
|
||||
{ label: "Showcase", value: "showcase" },
|
||||
...EVENT_TYPES.map((t) => ({ label: t.label, value: t.value })),
|
||||
];
|
||||
|
||||
const { data: eventsData } = await useFetch("/api/events");
|
||||
|
|
@ -168,6 +160,7 @@ const formatTime = (event) => {
|
|||
return new Date(event.startDate).toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
timeZoneName: "short",
|
||||
timeZone: event.displayTimezone || "America/Toronto",
|
||||
});
|
||||
};
|
||||
|
|
@ -181,16 +174,6 @@ const formatLocation = (event) => {
|
|||
return event.location;
|
||||
};
|
||||
|
||||
const isSoldOut = (event) => {
|
||||
if (!event.maxAttendees) return false;
|
||||
return (event.registeredCount || 0) >= event.maxAttendees;
|
||||
};
|
||||
|
||||
const isAlmostFull = (event) => {
|
||||
if (!event.maxAttendees) return false;
|
||||
if (isSoldOut(event)) return false;
|
||||
return (event.registeredCount || 0) / event.maxAttendees >= 0.8;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -221,7 +204,7 @@ const isAlmostFull = (event) => {
|
|||
|
||||
.event-row {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 1fr auto auto;
|
||||
grid-template-columns: 90px 1fr auto;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
padding: 14px 0;
|
||||
|
|
@ -293,6 +276,16 @@ const isAlmostFull = (event) => {
|
|||
line-height: 1.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.registered-tag {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: var(--candle);
|
||||
border: 1px solid currentColor;
|
||||
padding: 1px 5px;
|
||||
line-height: 1.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-tagline {
|
||||
font-size: 11px;
|
||||
|
|
@ -321,35 +314,6 @@ const isAlmostFull = (event) => {
|
|||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.event-capacity {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
white-space: nowrap;
|
||||
padding-top: 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.seats-warn {
|
||||
color: var(--ember);
|
||||
}
|
||||
.capacity-badge {
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
padding: 1px 5px;
|
||||
border: 1px dashed currentColor;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.capacity-badge.limited {
|
||||
color: var(--ember);
|
||||
}
|
||||
.capacity-badge.sold-out {
|
||||
color: var(--text-faint);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.event-badges {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -481,7 +445,6 @@ const isAlmostFull = (event) => {
|
|||
grid-template-columns: 70px 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
.event-capacity,
|
||||
.event-badges {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue