Event fixes
This commit is contained in:
parent
707447fc88
commit
19d519b153
5 changed files with 696 additions and 606 deletions
|
|
@ -1,76 +1,58 @@
|
|||
<template>
|
||||
<div class="event-ticket-purchase">
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="text-center py-8">
|
||||
<div
|
||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
||||
></div>
|
||||
<p class="text-guild-300">Loading ticket information...</p>
|
||||
<div v-if="loading" class="ticket-panel">
|
||||
<div class="box-title">Tickets</div>
|
||||
<p class="ticket-status">Loading ticket information...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="p-6 bg-ember-900/20 rounded-xl border border-ember-800"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-ember-300 mb-2">
|
||||
<div v-else-if="error" class="ticket-panel">
|
||||
<div class="box-title">Tickets</div>
|
||||
<p class="ticket-status" style="color: var(--ember)">
|
||||
Unable to Load Tickets
|
||||
</h3>
|
||||
<p class="text-ember-400">{{ error }}</p>
|
||||
</p>
|
||||
<p class="ticket-detail">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Series Pass Required -->
|
||||
<div
|
||||
v-else-if="ticketInfo?.requiresSeriesPass"
|
||||
class="p-6 bg-candlelight-900/20 rounded-xl border border-candlelight-800"
|
||||
>
|
||||
<h3
|
||||
class="text-lg font-semibold text-candlelight-300 mb-2 flex items-center gap-2"
|
||||
>
|
||||
<Icon name="heroicons:ticket" class="w-6 h-6" />
|
||||
<div v-else-if="ticketInfo?.requiresSeriesPass" class="ticket-panel">
|
||||
<div class="box-title">Tickets</div>
|
||||
<p class="ticket-status" style="color: var(--candle)">
|
||||
Series Pass Required
|
||||
</h3>
|
||||
<p class="text-candlelight-400 mb-4">
|
||||
</p>
|
||||
<p class="ticket-detail">
|
||||
This event is part of
|
||||
<strong>{{ ticketInfo.series?.title }}</strong> and requires a series
|
||||
pass to attend.
|
||||
</p>
|
||||
<p class="text-sm text-guild-300 mb-6">
|
||||
<p class="ticket-hint">
|
||||
Purchase a series pass to get access to all events in this series.
|
||||
</p>
|
||||
<UButton
|
||||
<NuxtLink
|
||||
:to="`/series/${ticketInfo.series?.slug || ticketInfo.series?.id}`"
|
||||
color="primary"
|
||||
size="lg"
|
||||
block
|
||||
>
|
||||
View Series & Purchase Pass
|
||||
</UButton>
|
||||
<button class="btn btn-primary">View Series & Purchase Pass</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Already Registered -->
|
||||
<div
|
||||
v-else-if="ticketInfo?.alreadyRegistered"
|
||||
class="p-6 bg-candlelight-900/20 rounded-xl border border-candlelight-800"
|
||||
>
|
||||
<h3
|
||||
class="text-lg font-semibold text-candlelight-300 mb-2 flex items-center gap-2"
|
||||
>
|
||||
<Icon name="heroicons:check-circle-solid" class="w-6 h-6" />
|
||||
<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!
|
||||
</h3>
|
||||
<p class="text-candlelight-400 mb-4">
|
||||
</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
|
||||
details.
|
||||
</template>
|
||||
</p>
|
||||
<p class="text-sm text-guild-300">
|
||||
<p class="ticket-hint">
|
||||
See you on {{ formatEventDate(eventStartDate) }}!
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -83,128 +65,130 @@
|
|||
:is-selected="true"
|
||||
:is-available="ticketInfo.available"
|
||||
:already-registered="ticketInfo.alreadyRegistered"
|
||||
class="mb-6"
|
||||
@join-waitlist="handleJoinWaitlist"
|
||||
/>
|
||||
|
||||
<!-- Registration Form -->
|
||||
<div v-if="ticketInfo.available && !ticketInfo.alreadyRegistered">
|
||||
<h3 class="text-xl font-bold text-guild-100 mb-4">
|
||||
<!-- Registration (logged-in member) -->
|
||||
<div
|
||||
v-if="ticketInfo.available && !ticketInfo.alreadyRegistered && isLoggedIn"
|
||||
class="ticket-panel"
|
||||
>
|
||||
<div class="box-title">
|
||||
{{ ticketInfo.isFree ? "Register" : "Purchase Ticket" }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="space-y-4">
|
||||
<!-- Name Field -->
|
||||
<div>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-guild-200 mb-2"
|
||||
>
|
||||
Full Name
|
||||
</label>
|
||||
<UInput
|
||||
id="name"
|
||||
<p
|
||||
v-if="ticketInfo.isMember && ticketInfo.isFree"
|
||||
class="ticket-notice"
|
||||
style="color: var(--candle)"
|
||||
>
|
||||
This event is free for Ghost Guild members
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="!ticketInfo.isFree"
|
||||
class="ticket-notice"
|
||||
style="color: var(--candle)"
|
||||
>
|
||||
Payment of {{ ticketInfo.formattedPrice }} will be processed
|
||||
securely
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="processing"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{
|
||||
processing
|
||||
? "Processing..."
|
||||
: ticketInfo.isFree
|
||||
? "Register for this event"
|
||||
: `Pay ${ticketInfo.formattedPrice}`
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Registration Form (guest) -->
|
||||
<div
|
||||
v-else-if="ticketInfo.available && !ticketInfo.alreadyRegistered"
|
||||
class="ticket-panel"
|
||||
>
|
||||
<div class="box-title">
|
||||
{{ ticketInfo.isFree ? "Register" : "Purchase Ticket" }}
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="field">
|
||||
<label>Full Name</label>
|
||||
<input
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Enter your full name"
|
||||
:disabled="processing"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Email Field -->
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-guild-200 mb-2"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<UInput
|
||||
id="email"
|
||||
<div class="field">
|
||||
<label>Email Address</label>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
required
|
||||
placeholder="Enter your email"
|
||||
:disabled="processing || isLoggedIn"
|
||||
:disabled="processing"
|
||||
/>
|
||||
<p v-if="isLoggedIn" class="text-xs text-guild-400 mt-1">
|
||||
Using your member email
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Member Benefits Notice -->
|
||||
<div
|
||||
v-if="ticketInfo.isMember && ticketInfo.isFree"
|
||||
class="p-4 bg-candlelight-900/20 rounded-lg border border-candlelight-800"
|
||||
>
|
||||
<p class="text-sm text-candlelight-300 flex items-center gap-2">
|
||||
<Icon name="heroicons:sparkles" class="w-4 h-4" />
|
||||
This event is free for Ghost Guild members
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Payment Required Notice -->
|
||||
<div
|
||||
<p
|
||||
v-if="!ticketInfo.isFree"
|
||||
class="p-4 bg-candlelight-900/20 rounded-lg border border-candlelight-800"
|
||||
class="ticket-notice"
|
||||
style="color: var(--candle)"
|
||||
>
|
||||
<p class="text-sm text-candlelight-300 flex items-center gap-2">
|
||||
<Icon name="heroicons:credit-card" class="w-4 h-4" />
|
||||
Payment of {{ ticketInfo.formattedPrice }} will be processed
|
||||
securely
|
||||
</p>
|
||||
</div>
|
||||
Payment of {{ ticketInfo.formattedPrice }} will be processed
|
||||
securely
|
||||
</p>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="pt-4">
|
||||
<UButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
size="lg"
|
||||
block
|
||||
:loading="processing"
|
||||
:disabled="!form.name || !form.email"
|
||||
>
|
||||
{{
|
||||
processing
|
||||
? "Processing..."
|
||||
: ticketInfo.isFree
|
||||
? "Complete Registration"
|
||||
: `Pay ${ticketInfo.formattedPrice}`
|
||||
}}
|
||||
</UButton>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
:disabled="processing || !form.name || !form.email"
|
||||
>
|
||||
{{
|
||||
processing
|
||||
? "Processing..."
|
||||
: ticketInfo.isFree
|
||||
? "Complete Registration"
|
||||
: `Pay ${ticketInfo.formattedPrice}`
|
||||
}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Sold Out with Waitlist -->
|
||||
<div
|
||||
v-else-if="!ticketInfo.available && ticketInfo.waitlistAvailable"
|
||||
class="text-center py-8"
|
||||
class="ticket-panel"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:ticket"
|
||||
class="w-16 h-16 text-guild-400 mx-auto mb-4"
|
||||
/>
|
||||
<h3 class="text-xl font-bold text-guild-100 mb-2">Event Sold Out</h3>
|
||||
<p class="text-guild-300 mb-6">
|
||||
<div class="box-title">Waitlist</div>
|
||||
<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>
|
||||
<UButton color="gray" size="lg" @click="handleJoinWaitlist">
|
||||
<button class="btn" @click="handleJoinWaitlist">
|
||||
Join Waitlist
|
||||
</UButton>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sold Out (No Waitlist) -->
|
||||
<div v-else-if="!ticketInfo.available" class="text-center py-8">
|
||||
<Icon
|
||||
name="heroicons:x-circle"
|
||||
class="w-16 h-16 text-ember-400 mx-auto mb-4"
|
||||
/>
|
||||
<h3 class="text-xl font-bold text-guild-100 mb-2">Event Sold Out</h3>
|
||||
<p class="text-guild-300">
|
||||
<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-detail">
|
||||
Unfortunately, this event is at capacity and no longer accepting
|
||||
registrations.
|
||||
</p>
|
||||
|
|
@ -231,6 +215,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
userName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["success", "error"]);
|
||||
|
|
@ -245,7 +233,7 @@ const error = ref(null);
|
|||
const ticketInfo = ref(null);
|
||||
|
||||
const form = ref({
|
||||
name: "",
|
||||
name: props.userName || "",
|
||||
email: props.userEmail || "",
|
||||
});
|
||||
|
||||
|
|
@ -270,7 +258,6 @@ const fetchTicketInfo = async () => {
|
|||
|
||||
if (seriesAccess.requiresSeriesPass) {
|
||||
if (seriesAccess.hasSeriesPass) {
|
||||
// User has series pass - show as already registered
|
||||
ticketInfo.value = {
|
||||
available: true,
|
||||
alreadyRegistered: true,
|
||||
|
|
@ -281,7 +268,6 @@ const fetchTicketInfo = async () => {
|
|||
loading.value = false;
|
||||
return;
|
||||
} else {
|
||||
// User needs to buy series pass
|
||||
ticketInfo.value = {
|
||||
available: false,
|
||||
requiresSeriesPass: true,
|
||||
|
|
@ -293,7 +279,6 @@ const fetchTicketInfo = async () => {
|
|||
}
|
||||
}
|
||||
} catch (seriesErr) {
|
||||
// If series check fails, continue with regular ticket check
|
||||
console.warn("Series access check failed:", seriesErr);
|
||||
}
|
||||
}
|
||||
|
|
@ -320,9 +305,7 @@ const handleSubmit = async () => {
|
|||
try {
|
||||
let transactionId = null;
|
||||
|
||||
// If payment is required, initialize Helcim and process payment
|
||||
if (!ticketInfo.value.isFree) {
|
||||
// Initialize Helcim payment
|
||||
await initializeTicketPayment(
|
||||
props.eventId,
|
||||
form.value.email,
|
||||
|
|
@ -330,14 +313,12 @@ const handleSubmit = async () => {
|
|||
props.eventTitle,
|
||||
);
|
||||
|
||||
// Show Helcim modal and complete payment
|
||||
const paymentResult = await verifyPayment();
|
||||
|
||||
if (!paymentResult.success) {
|
||||
throw new Error("Payment was not completed");
|
||||
}
|
||||
|
||||
// For purchase transactions, we get a transactionId
|
||||
transactionId = paymentResult.transactionId;
|
||||
|
||||
if (!transactionId) {
|
||||
|
|
@ -345,7 +326,6 @@ const handleSubmit = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Purchase ticket
|
||||
const response = await $fetch(
|
||||
`/api/events/${props.eventId}/tickets/purchase`,
|
||||
{
|
||||
|
|
@ -358,18 +338,15 @@ const handleSubmit = async () => {
|
|||
},
|
||||
);
|
||||
|
||||
// Success!
|
||||
toast.add({
|
||||
title: "Success!",
|
||||
description: ticketInfo.value.isFree
|
||||
? "You're registered for this event"
|
||||
: "Ticket purchased successfully!",
|
||||
color: "green",
|
||||
color: "success",
|
||||
});
|
||||
|
||||
emit("success", response);
|
||||
|
||||
// Refresh ticket info to show registered state
|
||||
await fetchTicketInfo();
|
||||
} catch (err) {
|
||||
console.error("Error purchasing ticket:", err);
|
||||
|
|
@ -382,7 +359,7 @@ const handleSubmit = async () => {
|
|||
toast.add({
|
||||
title: "Registration Failed",
|
||||
description: errorMessage,
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
|
||||
emit("error", err);
|
||||
|
|
@ -393,11 +370,10 @@ const handleSubmit = async () => {
|
|||
};
|
||||
|
||||
const handleJoinWaitlist = () => {
|
||||
// TODO: Implement waitlist functionality
|
||||
toast.add({
|
||||
title: "Waitlist",
|
||||
description: "Waitlist functionality coming soon!",
|
||||
color: "blue",
|
||||
color: "info",
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -410,3 +386,37 @@ const formatEventDate = (date) => {
|
|||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ticket-panel {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.ticket-status {
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.ticket-detail {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.ticket-hint {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ticket-notice {
|
||||
font-size: 11px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
font-size: 10px;
|
||||
color: var(--text-faint);
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue