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
|
|
@ -30,10 +30,6 @@
|
|||
<div v-if="event.circle" class="event-meta-item">
|
||||
<CircleBadge :circle="event.circle" />
|
||||
</div>
|
||||
<div v-if="event.maxAttendees" class="event-meta-item">
|
||||
<span class="meta-label">Capacity</span>
|
||||
{{ event.registeredCount || 0 }}/{{ event.maxAttendees }} seats
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -85,7 +81,7 @@
|
|||
<!-- Description -->
|
||||
<div class="section">
|
||||
<h2>About This Event</h2>
|
||||
<p>{{ event.description }}</p>
|
||||
<div class="prose" v-html="renderMarkdown(event.description)" />
|
||||
</div>
|
||||
|
||||
<!-- Series Description -->
|
||||
|
|
@ -94,17 +90,23 @@
|
|||
class="section"
|
||||
>
|
||||
<h2>About the {{ event.series.title }} Series</h2>
|
||||
<p>{{ event.series.description }}</p>
|
||||
<div class="prose" v-html="renderMarkdown(event.series.description)" />
|
||||
</div>
|
||||
|
||||
<!-- Additional Information -->
|
||||
<div v-if="event.content" class="section">
|
||||
<h2>Additional Information</h2>
|
||||
<div class="prose" v-html="renderMarkdown(event.content)" />
|
||||
</div>
|
||||
|
||||
<!-- Agenda -->
|
||||
<div v-if="event.agenda?.length" class="section">
|
||||
<h2>Agenda</h2>
|
||||
<ol class="agenda-list">
|
||||
<ul class="agenda-list">
|
||||
<li v-for="(item, index) in event.agenda" :key="index">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ol>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Speakers -->
|
||||
|
|
@ -143,7 +145,7 @@
|
|||
<div class="box-title">Event Details</div>
|
||||
<div v-if="event.eventType" class="detail-row">
|
||||
<span class="detail-key">Type</span>
|
||||
<span class="detail-val">{{ event.eventType }}</span>
|
||||
<span class="detail-val">{{ eventTypeLabel(event.eventType) }}</span>
|
||||
</div>
|
||||
<div v-if="event.membersOnly" class="detail-row">
|
||||
<span class="detail-key">Members only</span>
|
||||
|
|
@ -169,6 +171,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { eventTypeLabel } from "~/config/eventTypes";
|
||||
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
|
||||
|
|
@ -190,6 +194,7 @@ if (error.value?.statusCode === 404) {
|
|||
|
||||
const { memberData, checkMemberStatus } = useAuth();
|
||||
const { trackGoal, isComplete } = useOnboarding();
|
||||
const { render: renderMarkdown } = useMarkdown();
|
||||
|
||||
onMounted(async () => {
|
||||
await checkMemberStatus();
|
||||
|
|
@ -232,16 +237,12 @@ const handleTicketError = (err) => {
|
|||
console.error("Ticket purchase failed:", err);
|
||||
};
|
||||
|
||||
useHead(() => ({
|
||||
title: event.value
|
||||
? `${event.value.title} - Ghost Guild Events`
|
||||
: "Event - Ghost Guild",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content: event.value?.description || "View event details and register",
|
||||
},
|
||||
],
|
||||
useSiteMeta(() => ({
|
||||
title: event.value ? `${event.value.title} · Events` : "Event",
|
||||
description:
|
||||
event.value?.description || "View event details and register.",
|
||||
type: "article",
|
||||
image: event.value?.slug ? `/og/events/${event.value.slug}.png` : undefined,
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
|
@ -349,12 +350,79 @@ useHead(() => ({
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
.section p {
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.prose {
|
||||
font-size: 14px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
.prose :deep(p) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.prose :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.prose :deep(a) {
|
||||
color: var(--ember);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.prose :deep(strong) {
|
||||
color: var(--text-bright);
|
||||
}
|
||||
.prose :deep(ul),
|
||||
.prose :deep(ol) {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
.prose :deep(ul li),
|
||||
.prose :deep(ol li) {
|
||||
position: relative;
|
||||
padding: 2px 0 2px 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.prose :deep(ul li::before) {
|
||||
content: "›";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
color: var(--candle-faint);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.prose :deep(ol) {
|
||||
counter-reset: prose-item;
|
||||
}
|
||||
.prose :deep(ol li) {
|
||||
counter-increment: prose-item;
|
||||
padding-left: 28px;
|
||||
}
|
||||
.prose :deep(ol li::before) {
|
||||
content: counter(prose-item) ".";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
color: var(--candle-faint);
|
||||
}
|
||||
.prose :deep(blockquote) {
|
||||
border-left: 2px solid var(--candle-faint);
|
||||
padding-left: 12px;
|
||||
margin: 12px 0;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
.prose :deep(code) {
|
||||
font-family: "Commit Mono", monospace;
|
||||
background: var(--input-bg);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.circle-badges {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
|
@ -367,10 +435,27 @@ useHead(() => ({
|
|||
}
|
||||
|
||||
.agenda-list {
|
||||
padding-left: 20px;
|
||||
font-size: 12px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-dim);
|
||||
line-height: 2;
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
.agenda-list li {
|
||||
position: relative;
|
||||
padding: 2px 0 2px 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.agenda-list li::before {
|
||||
content: "›";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
color: var(--candle-faint);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.speaker {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue