Event fixes
This commit is contained in:
parent
707447fc88
commit
19d519b153
5 changed files with 696 additions and 606 deletions
|
|
@ -125,153 +125,15 @@
|
|||
<div v-if="!event.isCancelled" class="event-aside">
|
||||
<!-- Ticket System -->
|
||||
<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"
|
||||
:user-name="memberData?.name"
|
||||
@success="handleTicketSuccess"
|
||||
@error="handleTicketError"
|
||||
/>
|
||||
|
||||
<!-- Legacy Registration -->
|
||||
<template v-else>
|
||||
<!-- Already Registered -->
|
||||
<div v-if="registrationStatus === 'registered'" class="dashed-box">
|
||||
<div class="box-title">Registration</div>
|
||||
<p class="reg-status" style="color: var(--green)">
|
||||
You're registered!
|
||||
</p>
|
||||
<p class="reg-price">Confirmation sent to your email</p>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
:disabled="isCancelling"
|
||||
@click="handleCancelRegistration"
|
||||
>
|
||||
{{ isCancelling ? "Cancelling..." : "Cancel Registration" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Member Status Issues -->
|
||||
<div v-else-if="memberData && !canRSVP" class="dashed-box">
|
||||
<div class="box-title">Registration</div>
|
||||
<p class="reg-status" style="color: var(--ember)">
|
||||
{{ statusConfig.label }}
|
||||
</p>
|
||||
<p class="reg-price">{{ getRSVPMessage() }}</p>
|
||||
<NuxtLink
|
||||
v-if="isPendingPayment"
|
||||
to="#"
|
||||
@click.prevent="completePayment"
|
||||
>
|
||||
<button class="btn btn-primary" :disabled="isProcessingPayment">
|
||||
{{ isProcessingPayment ? "Processing..." : "Complete Payment" }}
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Members-Only Gate -->
|
||||
<div
|
||||
v-else-if="event.membersOnly && memberData && !isMember"
|
||||
class="dashed-box"
|
||||
>
|
||||
<div class="box-title">Registration</div>
|
||||
<p class="reg-status" style="color: var(--ember)">
|
||||
Membership Required
|
||||
</p>
|
||||
<p class="reg-price">This event is exclusive to members.</p>
|
||||
<NuxtLink to="/join"
|
||||
><button class="btn btn-primary">
|
||||
Become a Member
|
||||
</button></NuxtLink
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Can Register (logged in) -->
|
||||
<div
|
||||
v-else-if="memberData && (!event.membersOnly || isMember)"
|
||||
class="dashed-box"
|
||||
>
|
||||
<div class="box-title">Registration</div>
|
||||
<div v-if="event.maxAttendees" class="reg-status">
|
||||
{{ event.maxAttendees - (event.registeredCount || 0) }} spots
|
||||
remaining
|
||||
</div>
|
||||
<div class="reg-price">Free for members</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="isRegistering"
|
||||
@click="handleRegistration"
|
||||
>
|
||||
{{ isRegistering ? "Registering..." : "Register for this event" }}
|
||||
</button>
|
||||
<a
|
||||
:href="`/api/events/${route.params.slug}/calendar`"
|
||||
download
|
||||
class="cal-link"
|
||||
>Add to calendar</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Not Logged In -->
|
||||
<div v-else class="dashed-box">
|
||||
<div class="box-title">Registration</div>
|
||||
<p v-if="!event.membersOnly" class="reg-open">Open to everyone — no membership required</p>
|
||||
<form @submit.prevent="handleRegistration">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input v-model="registrationForm.name" type="text" required />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input v-model="registrationForm.email" type="email" required />
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
:disabled="isRegistering"
|
||||
>
|
||||
{{ isRegistering ? "Registering..." : "Register for Event" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Waitlist -->
|
||||
<div
|
||||
v-if="event.tickets?.waitlist?.enabled && isEventFull"
|
||||
class="dashed-box"
|
||||
>
|
||||
<div class="box-title">Waitlist</div>
|
||||
<div v-if="isOnWaitlist">
|
||||
<p class="reg-status">
|
||||
You're on the waitlist (#{{ waitlistPosition }})
|
||||
</p>
|
||||
<button
|
||||
class="btn"
|
||||
@click="handleLeaveWaitlist"
|
||||
:disabled="isJoiningWaitlist"
|
||||
>
|
||||
Leave Waitlist
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="reg-status" style="color: var(--ember)">
|
||||
This event is full
|
||||
</p>
|
||||
<form @submit.prevent="handleJoinWaitlist">
|
||||
<div v-if="!memberData" class="field">
|
||||
<label>Email</label>
|
||||
<input v-model="waitlistForm.email" type="email" required />
|
||||
</div>
|
||||
<button type="submit" class="btn" :disabled="isJoiningWaitlist">
|
||||
{{ isJoiningWaitlist ? "Joining..." : "Join Waitlist" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Event Details Box -->
|
||||
<div class="dashed-box">
|
||||
<div class="box-title">Event Details</div>
|
||||
|
|
@ -322,129 +184,16 @@ if (error.value?.statusCode === 404) {
|
|||
throw createError({ statusCode: 404, statusMessage: "Event not found" });
|
||||
}
|
||||
|
||||
const { isMember, memberData, checkMemberStatus } = useAuth();
|
||||
const {
|
||||
isPendingPayment,
|
||||
isSuspended,
|
||||
isCancelled,
|
||||
canRSVP,
|
||||
statusConfig,
|
||||
getRSVPMessage,
|
||||
} = useMemberStatus();
|
||||
const { completePayment, isProcessingPayment } = useMemberPayment();
|
||||
const { memberData, checkMemberStatus } = useAuth();
|
||||
const { trackGoal, isComplete } = useOnboarding();
|
||||
|
||||
onMounted(async () => {
|
||||
await checkMemberStatus();
|
||||
if (memberData.value) {
|
||||
if (!isComplete.value) {
|
||||
trackGoal('eventPageVisited');
|
||||
}
|
||||
registrationForm.value.name = memberData.value.name;
|
||||
registrationForm.value.email = memberData.value.email;
|
||||
registrationForm.value.membershipLevel =
|
||||
memberData.value.membershipLevel || "non-member";
|
||||
await checkRegistrationStatus();
|
||||
checkWaitlistStatus();
|
||||
if (memberData.value && !isComplete.value) {
|
||||
trackGoal('eventPageVisited');
|
||||
}
|
||||
});
|
||||
|
||||
const checkRegistrationStatus = async () => {
|
||||
if (!memberData.value?.email) return;
|
||||
try {
|
||||
const response = await $fetch(
|
||||
`/api/events/${route.params.slug}/check-registration`,
|
||||
{
|
||||
method: "POST",
|
||||
body: { email: memberData.value.email },
|
||||
},
|
||||
);
|
||||
if (response.isRegistered) registrationStatus.value = "registered";
|
||||
} catch (err) {
|
||||
console.error("Failed to check registration status:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const registrationForm = ref({
|
||||
name: "",
|
||||
email: "",
|
||||
membershipLevel: "non-member",
|
||||
});
|
||||
const isRegistering = ref(false);
|
||||
const isCancelling = ref(false);
|
||||
const registrationStatus = ref("not-registered");
|
||||
const isJoiningWaitlist = ref(false);
|
||||
const isOnWaitlist = ref(false);
|
||||
const waitlistPosition = ref(0);
|
||||
const waitlistForm = ref({ email: "" });
|
||||
|
||||
const isEventFull = computed(() => {
|
||||
if (!event.value?.maxAttendees) return false;
|
||||
return (event.value.registeredCount || 0) >= event.value.maxAttendees;
|
||||
});
|
||||
|
||||
const checkWaitlistStatus = () => {
|
||||
const email = memberData.value?.email || waitlistForm.value.email;
|
||||
if (!email || !event.value?.tickets?.waitlist?.enabled) return;
|
||||
const entries = event.value.tickets.waitlist.entries || [];
|
||||
const idx = entries.findIndex(
|
||||
(e) => e.email.toLowerCase() === email.toLowerCase(),
|
||||
);
|
||||
if (idx !== -1) {
|
||||
isOnWaitlist.value = true;
|
||||
waitlistPosition.value = idx + 1;
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoinWaitlist = async () => {
|
||||
isJoiningWaitlist.value = true;
|
||||
try {
|
||||
const email = memberData.value?.email || waitlistForm.value.email;
|
||||
const name = memberData.value?.name || "Guest";
|
||||
const response = await $fetch(`/api/events/${route.params.slug}/waitlist`, {
|
||||
method: "POST",
|
||||
body: { email, name },
|
||||
});
|
||||
isOnWaitlist.value = true;
|
||||
waitlistPosition.value = response.position;
|
||||
toast.add({
|
||||
title: "Added to Waitlist",
|
||||
description: `You're #${response.position} on the waitlist.`,
|
||||
color: "orange",
|
||||
});
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Couldn't Join Waitlist",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
});
|
||||
} finally {
|
||||
isJoiningWaitlist.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeaveWaitlist = async () => {
|
||||
isJoiningWaitlist.value = true;
|
||||
try {
|
||||
const email = memberData.value?.email || waitlistForm.value.email;
|
||||
await $fetch(`/api/events/${route.params.slug}/waitlist`, {
|
||||
method: "DELETE",
|
||||
body: { email },
|
||||
});
|
||||
isOnWaitlist.value = false;
|
||||
waitlistPosition.value = 0;
|
||||
toast.add({ title: "Removed from Waitlist", color: "blue" });
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "Failed to leave waitlist.",
|
||||
color: "red",
|
||||
});
|
||||
} finally {
|
||||
isJoiningWaitlist.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
const d = new Date(dateStr);
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
|
|
@ -464,54 +213,6 @@ const formatTime = (start, end) => {
|
|||
return `${fmt.format(new Date(start))} – ${fmt.format(new Date(end))}`;
|
||||
};
|
||||
|
||||
const handleRegistration = async () => {
|
||||
isRegistering.value = true;
|
||||
try {
|
||||
await $fetch(`/api/events/${route.params.slug}/register`, {
|
||||
method: "POST",
|
||||
body: registrationForm.value,
|
||||
});
|
||||
registrationStatus.value = "registered";
|
||||
toast.add({
|
||||
title: "Registered!",
|
||||
description: `You're registered for ${event.value.title}.`,
|
||||
color: "green",
|
||||
});
|
||||
if (event.value.registeredCount !== undefined)
|
||||
event.value.registeredCount++;
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Registration Failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
});
|
||||
} finally {
|
||||
isRegistering.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelRegistration = async () => {
|
||||
isCancelling.value = true;
|
||||
try {
|
||||
await $fetch(`/api/events/${route.params.slug}/cancel-registration`, {
|
||||
method: "POST",
|
||||
body: { email: registrationForm.value.email || memberData.value?.email },
|
||||
});
|
||||
registrationStatus.value = "not-registered";
|
||||
toast.add({ title: "Registration Cancelled", color: "blue" });
|
||||
if (event.value.registeredCount !== undefined)
|
||||
event.value.registeredCount--;
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Cancellation Failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
});
|
||||
} finally {
|
||||
isCancelling.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleTicketSuccess = () => {
|
||||
if (event.value.registeredCount !== undefined) event.value.registeredCount++;
|
||||
};
|
||||
|
|
@ -680,28 +381,6 @@ useHead(() => ({
|
|||
color: var(--text-faint);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.reg-status {
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.reg-price {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reg-open {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cal-link {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--candle);
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue