Add landing page
This commit is contained in:
parent
3fea484585
commit
bce86ee840
47 changed files with 7119 additions and 439 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue