Add landing page

This commit is contained in:
Jennie Robinson Faber 2025-11-03 11:17:51 +00:00
parent 3fea484585
commit bce86ee840
47 changed files with 7119 additions and 439 deletions

View file

@ -160,14 +160,14 @@
<!-- Series Description -->
<div
v-if="event.series?.isSeriesEvent && event.series.description"
class="mb-6 p-4 bg-purple-500/5 rounded-lg border border-purple-500/20"
class="event-series-description mb-6 p-4 bg-ghost-800/30 dark:bg-ghost-700/20 rounded-lg border border-ghost-600 dark:border-ghost-600"
>
<h3
class="text-lg font-semibold text-purple-800 dark:text-purple-200 mb-2"
class="event-series-description__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
>
About the {{ event.series.title }} Series
</h3>
<p class="text-ghost-200">
<p class="event-series-description__text text-ghost-200">
{{ event.series.description }}
</p>
</div>
@ -227,160 +227,180 @@
<!-- Registration Section -->
<div v-if="!event.isCancelled">
<!-- Already Registered Status -->
<div v-if="registrationStatus === 'registered'">
<!-- Use new ticket system if tickets are enabled -->
<EventTicketPurchase
v-if="event.tickets?.enabled"
:event-id="event._id || event.id"
:event-start-date="event.startDate"
:event-title="event.title"
:user-email="memberData?.email"
@success="handleTicketSuccess"
@error="handleTicketError"
/>
<!-- Legacy registration system (for events without tickets enabled) -->
<div v-else>
<!-- Already Registered Status -->
<div v-if="registrationStatus === 'registered'">
<div
class="p-4 bg-green-100 dark:bg-green-900/20 rounded-lg border border-green-400 dark:border-green-800 mb-6"
>
<div
class="flex flex-col md:flex-row md:items-start md:justify-between gap-4"
>
<div>
<p
class="font-semibold text-green-800 dark:text-green-300"
>
You're registered!
</p>
<p class="text-sm text-green-700 dark:text-green-400">
We've sent a confirmation to your email
</p>
</div>
<UButton
color="error"
size="md"
@click="handleCancelRegistration"
:loading="isCancelling"
>
Cancel Registration
</UButton>
</div>
</div>
</div>
<!-- Logged In - Can Register -->
<div
class="p-4 bg-green-100 dark:bg-green-900/20 rounded-lg border border-green-400 dark:border-green-800 mb-6"
v-else-if="memberData && (!event.membersOnly || isMember)"
class="text-center"
>
<p class="text-lg text-ghost-200 mb-6">
You are logged in, {{ memberData.name }}.
</p>
<UButton
color="primary"
size="xl"
@click="handleRegistration"
:loading="isRegistering"
class="px-12 py-4"
>
{{ isRegistering ? "Registering..." : "Register Now" }}
</UButton>
</div>
<!-- Member Gate Warning -->
<div
v-else-if="event.membersOnly && !isMember"
class="text-center"
>
<div
class="flex flex-col md:flex-row md:items-start md:justify-between gap-4"
class="p-6 bg-amber-900/20 rounded-lg border border-amber-800 mb-6"
>
<p class="font-semibold text-amber-300 text-lg mb-2">
Membership Required
</p>
<p class="text-amber-400">
This event is exclusive to Ghost Guild members. Join any
circle to gain access.
</p>
</div>
<NuxtLink to="/join">
<UButton color="primary" size="xl" class="px-12 py-4">
Become a Member to Register
</UButton>
</NuxtLink>
</div>
<!-- Not Logged In - Show Registration Form -->
<div v-else>
<h3 class="text-xl font-bold text-ghost-100 mb-6">
Register for This Event
</h3>
<form @submit.prevent="handleRegistration" class="space-y-4">
<div>
<p class="font-semibold text-green-800 dark:text-green-300">
You're registered!
</p>
<p class="text-sm text-green-700 dark:text-green-400">
We've sent a confirmation to your email
</p>
</div>
<UButton
color="error"
size="md"
@click="handleCancelRegistration"
:loading="isCancelling"
>
Cancel Registration
</UButton>
</div>
</div>
</div>
<!-- Logged In - Can Register -->
<div
v-else-if="memberData && (!event.membersOnly || isMember)"
class="text-center"
>
<p class="text-lg text-ghost-200 mb-6">
You are logged in, {{ memberData.name }}.
</p>
<UButton
color="primary"
size="xl"
@click="handleRegistration"
:loading="isRegistering"
class="px-12 py-4"
>
{{ isRegistering ? "Registering..." : "Register Now" }}
</UButton>
</div>
<!-- Member Gate Warning -->
<div v-else-if="event.membersOnly && !isMember" class="text-center">
<div
class="p-6 bg-amber-900/20 rounded-lg border border-amber-800 mb-6"
>
<p class="font-semibold text-amber-300 text-lg mb-2">
Membership Required
</p>
<p class="text-amber-400">
This event is exclusive to Ghost Guild members. Join any
circle to gain access.
</p>
</div>
<NuxtLink to="/join">
<UButton color="primary" size="xl" class="px-12 py-4">
Become a Member to Register
</UButton>
</NuxtLink>
</div>
<!-- Not Logged In - Show Registration Form -->
<div v-else>
<h3 class="text-xl font-bold text-ghost-100 mb-6">
Register for This Event
</h3>
<form @submit.prevent="handleRegistration" class="space-y-4">
<div>
<label
for="name"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Full Name
</label>
<UInput
id="name"
v-model="registrationForm.name"
type="text"
required
placeholder="Enter your full name"
/>
</div>
<div>
<label
for="email"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Email Address
</label>
<UInput
id="email"
v-model="registrationForm.email"
type="email"
required
placeholder="Enter your email"
/>
</div>
<div>
<label
for="membershipLevel"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Membership Status
</label>
<USelect
id="membershipLevel"
v-model="registrationForm.membershipLevel"
:options="membershipOptions"
/>
</div>
<div class="pt-4">
<UButton
type="submit"
color="primary"
size="lg"
block
:loading="isRegistering"
>
{{
isRegistering ? "Registering..." : "Register for Event"
}}
</UButton>
</div>
</form>
</div>
<!-- Event Capacity -->
<div
v-if="event.maxAttendees"
class="mt-6 pt-6 border-t border-ghost-700"
>
<div class="flex items-center justify-between">
<span class="text-sm text-ghost-300">Event Capacity</span>
<div class="flex items-center space-x-2">
<span class="text-sm font-semibold text-ghost-100">
{{ event.registeredCount || 0 }} / {{ event.maxAttendees }}
</span>
<div
class="w-24 h-2 bg-ghost-700 rounded-full overflow-hidden"
>
<div
class="h-full bg-blue-500 rounded-full"
:style="`width: ${((event.registeredCount || 0) / event.maxAttendees) * 100}%`"
<label
for="name"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Full Name
</label>
<UInput
id="name"
v-model="registrationForm.name"
type="text"
required
placeholder="Enter your full name"
/>
</div>
<div>
<label
for="email"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Email Address
</label>
<UInput
id="email"
v-model="registrationForm.email"
type="email"
required
placeholder="Enter your email"
/>
</div>
<div>
<label
for="membershipLevel"
class="block text-sm font-medium text-ghost-200 mb-2"
>
Membership Status
</label>
<USelect
id="membershipLevel"
v-model="registrationForm.membershipLevel"
:options="membershipOptions"
/>
</div>
<div class="pt-4">
<UButton
type="submit"
color="primary"
size="lg"
block
:loading="isRegistering"
>
{{
isRegistering ? "Registering..." : "Register for Event"
}}
</UButton>
</div>
</form>
</div>
<!-- Event Capacity -->
<div
v-if="event.maxAttendees"
class="mt-6 pt-6 border-t border-ghost-700"
>
<div class="flex items-center justify-between">
<span class="text-sm text-ghost-300">Event Capacity</span>
<div class="flex items-center space-x-2">
<span class="text-sm font-semibold text-ghost-100">
{{ event.registeredCount || 0 }} /
{{ event.maxAttendees }}
</span>
<div
class="w-24 h-2 bg-ghost-700 rounded-full overflow-hidden"
>
<div
class="h-full bg-blue-500 rounded-full"
:style="`width: ${((event.registeredCount || 0) / event.maxAttendees) * 100}%`"
/>
</div>
</div>
</div>
</div>
</div>
@ -649,6 +669,20 @@ const handleCancelRegistration = async () => {
}
};
// Handle ticket purchase success
const handleTicketSuccess = (response) => {
console.log("Ticket purchased successfully:", response);
// Update registered count if needed
if (event.value.registeredCount !== undefined) {
event.value.registeredCount++;
}
};
// Handle ticket purchase error
const handleTicketError = (error) => {
console.error("Ticket purchase failed:", error);
};
// SEO Meta
useHead(() => ({
title: event.value

View file

@ -60,7 +60,6 @@
<div v-if="event.series?.isSeriesEvent" class="mt-2">
<EventSeriesBadge
:title="event.series.title"
:description="event.series.description"
:position="event.series.position"
:total-events="event.series.totalEvents"
:series-id="event.series.id"
@ -126,7 +125,7 @@
</section>
<!-- Event Series -->
<div v-if="activeSeries.length > 0" class="text-center mb-12">
<div v-if="activeSeries.length > 0" class="text-center my-12">
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
Current Event Series
</h2>
@ -140,24 +139,24 @@
v-for="series in activeSeries.slice(0, 6)"
:key="series.id"
:to="`/series/${series.id}`"
class="block bg-gradient-to-r from-purple-500/10 to-blue-500/10 rounded-xl p-6 border border-purple-500/30 hover:border-purple-500/50 hover:from-purple-500/15 hover:to-blue-500/15 transition-all duration-300"
class="series-list-item block bg-ghost-800/50 dark:bg-ghost-700/30 rounded-xl p-6 border border-ghost-600 dark:border-ghost-600 hover:border-ghost-500 hover:bg-ghost-800/70 dark:hover:bg-ghost-700/50 transition-all duration-300"
>
<div class="flex items-start justify-between mb-4">
<div class="flex items-center gap-2">
<span
class="text-sm font-semibold text-purple-700 dark:text-purple-300"
class="series-list-item__label text-sm font-semibold text-ghost-300 dark:text-ghost-300"
>
Event Series
</span>
<span
class="inline-flex items-center px-2 py-0.5 rounded-md bg-purple-500/20 text-sm font-medium text-purple-700 dark:text-purple-300"
class="series-list-item__count inline-flex items-center px-2 py-0.5 rounded-md bg-ghost-700/50 dark:bg-ghost-600/50 text-sm font-medium text-ghost-200 dark:text-ghost-200"
>
{{ series.eventCount }} events
</span>
</div>
<span
:class="[
'inline-flex items-center px-2 py-1 rounded-full text-xs font-medium',
'series-list-item__status inline-flex items-center px-2 py-1 rounded-full text-xs font-medium',
series.status === 'active'
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
: series.status === 'upcoming'
@ -170,46 +169,51 @@
</div>
<h3
class="text-lg font-semibold text-purple-800 dark:text-purple-200 mb-2"
class="series-list-item__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
>
{{ series.title }}
</h3>
<p
class="text-sm text-purple-600 dark:text-purple-400 mb-4 line-clamp-2"
class="series-list-item__description text-sm text-ghost-300 dark:text-ghost-300 mb-4 line-clamp-2"
>
{{ series.description }}
</p>
<div class="space-y-2 mb-4">
<div class="series-list-item__events space-y-2 mb-4">
<div
v-for="(event, index) in series.events.slice(0, 3)"
:key="event.id"
class="flex items-center justify-between text-xs"
class="series-list-item__event flex items-center justify-between text-xs"
>
<div class="flex items-center gap-2">
<div
class="w-6 h-6 bg-purple-500/20 text-purple-700 dark:text-purple-300 rounded-full flex items-center justify-center text-xs font-medium border border-purple-500/30"
class="series-list-item__event-number w-6 h-6 bg-ghost-700/50 dark:bg-ghost-600/50 text-ghost-200 dark:text-ghost-200 rounded-full flex items-center justify-center text-xs font-medium border border-ghost-600 dark:border-ghost-500"
>
{{ event.series?.position || index + 1 }}
</div>
<span class="text-purple-700 dark:text-purple-300 truncate">{{
event.title
}}</span>
<span
class="series-list-item__event-title text-ghost-200 dark:text-ghost-200 truncate"
>{{ event.title }}</span
>
</div>
<span class="text-purple-600 dark:text-purple-400">
<span
class="series-list-item__event-date text-ghost-300 dark:text-ghost-300"
>
{{ formatEventDate(event.startDate) }}
</span>
</div>
<div
v-if="series.events.length > 3"
class="text-xs text-purple-600 dark:text-purple-400 text-center pt-1"
class="series-list-item__more-events text-xs text-ghost-300 dark:text-ghost-300 text-center pt-1"
>
+{{ series.events.length - 3 }} more events
</div>
</div>
<div class="text-sm text-purple-600 dark:text-purple-400">
<div
class="series-list-item__date-range text-sm text-ghost-300 dark:text-ghost-300"
>
{{ formatDateRange(series.startDate, series.endDate) }}
</div>
</NuxtLink>