fix: use private helcimApiToken for all server-side Helcim API calls
This commit is contained in:
parent
ccd1d0783a
commit
d31b5b4dac
53 changed files with 1755 additions and 572 deletions
|
|
@ -45,7 +45,7 @@
|
|||
<!-- Early Bird Badge -->
|
||||
<span
|
||||
v-if="ticketInfo.isEarlyBird"
|
||||
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
|
||||
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-candlelight-900/20 text-candlelight-600 dark:bg-candlelight-900/35 dark:text-candlelight-400"
|
||||
>
|
||||
Early Bird
|
||||
</span>
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
<!-- Early Bird Countdown -->
|
||||
<div
|
||||
v-if="ticketInfo.isEarlyBird && ticketInfo.earlyBirdDeadline"
|
||||
class="mt-2 text-xs text-amber-400"
|
||||
class="mt-2 text-xs text-candlelight-500 dark:text-candlelight-400"
|
||||
>
|
||||
<Icon name="heroicons:clock" class="w-4 h-4 inline mr-1" />
|
||||
Early bird ends {{ formatDeadline(ticketInfo.earlyBirdDeadline) }}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
<template>
|
||||
<aside class="events-mini">
|
||||
<div class="em-label">Upcoming</div>
|
||||
<div v-for="event in events" :key="event._id" class="em-item">
|
||||
<span class="em-date">{{ formatDate(event.date) }}</span>
|
||||
<NuxtLink :to="`/events/${event._id}`" class="em-title">{{ event.title }}</NuxtLink>
|
||||
<span
|
||||
v-if="event.circle"
|
||||
class="em-circle"
|
||||
:style="{ color: `var(--c-${event.circle})` }"
|
||||
>{{ event.circle }}</span>
|
||||
<div class="em-inset">
|
||||
<div class="em-label">Upcoming</div>
|
||||
</div>
|
||||
<div v-if="events?.length" class="em-rows">
|
||||
<div v-for="event in events" :key="event._id" class="em-item">
|
||||
<div class="em-inset em-item-body">
|
||||
<span class="em-date">{{ formatDate(event.date) }}</span>
|
||||
<NuxtLink :to="`/events/${event._id}`" class="em-title">{{ event.title }}</NuxtLink>
|
||||
<span
|
||||
v-if="event.circle"
|
||||
class="em-circle"
|
||||
:style="{ color: `var(--c-${event.circle})` }"
|
||||
>{{ event.circle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="em-rows">
|
||||
<div class="em-item em-item--plain">
|
||||
<div class="em-inset em-empty">No upcoming events</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="em-inset em-link-wrap">
|
||||
<NuxtLink to="/events" class="em-link">All events →</NuxtLink>
|
||||
</div>
|
||||
<div v-if="!events?.length" class="em-empty">No upcoming events</div>
|
||||
<NuxtLink to="/events" class="em-link">All events →</NuxtLink>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
|
|
@ -29,9 +41,17 @@ const formatDate = (dateStr) => {
|
|||
|
||||
<style scoped>
|
||||
.events-mini {
|
||||
padding: 24px 20px;
|
||||
border-left: 1px dashed var(--border);
|
||||
box-sizing: border-box;
|
||||
align-self: stretch;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
padding: 24px 0;
|
||||
border-left: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.em-inset {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.em-label {
|
||||
|
|
@ -43,14 +63,23 @@ const formatDate = (dateStr) => {
|
|||
}
|
||||
|
||||
.em-item {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.em-item:last-child {
|
||||
.em-rows .em-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.em-item-body {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.em-item--plain .em-empty {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.em-date {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
|
|
@ -77,9 +106,12 @@ const formatDate = (dateStr) => {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.em-link {
|
||||
display: block;
|
||||
.em-link-wrap {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.em-link {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
color: var(--candle);
|
||||
}
|
||||
|
|
@ -87,7 +119,6 @@ const formatDate = (dateStr) => {
|
|||
.em-empty {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,22 @@
|
|||
|
||||
<!-- Content -->
|
||||
<div v-else-if="passInfo">
|
||||
<!-- Series Pass Card -->
|
||||
<!-- Already Registered State -->
|
||||
<div v-if="passInfo.alreadyRegistered" class="dashed-box p-6">
|
||||
<div class="section-label mb-2">Series Pass</div>
|
||||
<p class="text-[--text]">You're registered for this series.</p>
|
||||
<p v-if="passInfo.registration?.eventsIncluded !== undefined" class="text-[--text-dim] text-sm mt-1">
|
||||
Registered for {{ passInfo.registration.eventsIncluded }} event{{ passInfo.registration.eventsIncluded !== 1 ? 's' : '' }} in this series.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Series Pass Card (only when ticket data is available) -->
|
||||
<EventSeriesTicketCard
|
||||
v-else-if="passInfo.ticket"
|
||||
:ticket="passInfo.ticket"
|
||||
:availability="passInfo.availability"
|
||||
:available="passInfo.available"
|
||||
:already-registered="passInfo.alreadyRegistered"
|
||||
:already-registered="false"
|
||||
:is-member="passInfo.memberInfo?.isMember"
|
||||
:total-events="seriesInfo.totalEvents"
|
||||
:events="seriesEvents"
|
||||
|
|
@ -172,7 +182,7 @@ const props = defineProps({
|
|||
const emit = defineEmits(["purchase-success", "purchase-error"]);
|
||||
|
||||
const toast = useToast();
|
||||
const { initializePayment, verifyPayment } = useHelcimPay();
|
||||
const { initializeTicketPayment, verifyPayment } = useHelcimPay();
|
||||
|
||||
// State
|
||||
const loading = ref(true);
|
||||
|
|
@ -188,19 +198,29 @@ const form = ref({
|
|||
|
||||
const isLoggedIn = computed(() => !!props.userEmail);
|
||||
|
||||
// Fetch series pass info on mount
|
||||
// Fetch series pass info on mount, then re-fetch if userEmail becomes available (auth loads after mount)
|
||||
onMounted(async () => {
|
||||
await fetchPassInfo();
|
||||
});
|
||||
|
||||
watch(() => props.userEmail, async (newEmail, oldEmail) => {
|
||||
if (newEmail && !oldEmail) {
|
||||
form.value.email = newEmail;
|
||||
form.value.name = props.userName || form.value.name;
|
||||
await fetchPassInfo();
|
||||
}
|
||||
});
|
||||
|
||||
const fetchPassInfo = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await $fetch(
|
||||
`/api/series/${props.seriesId}/tickets/available`
|
||||
);
|
||||
const email = form.value.email || props.userEmail;
|
||||
const url = email
|
||||
? `/api/series/${props.seriesId}/tickets/available?email=${encodeURIComponent(email)}`
|
||||
: `/api/series/${props.seriesId}/tickets/available`;
|
||||
const response = await $fetch(url);
|
||||
|
||||
passInfo.value = response;
|
||||
|
||||
|
|
@ -244,15 +264,11 @@ const handleSubmit = async () => {
|
|||
paymentProcessing.value = true;
|
||||
|
||||
// Initialize Helcim payment for series pass
|
||||
await initializePayment(
|
||||
await initializeTicketPayment(
|
||||
props.seriesId,
|
||||
form.value.email,
|
||||
passInfo.value.ticket.price,
|
||||
passInfo.value.ticket.currency || "CAD",
|
||||
{
|
||||
type: "series_pass",
|
||||
seriesId: props.seriesId,
|
||||
seriesTitle: props.seriesInfo.title,
|
||||
}
|
||||
props.seriesInfo.title,
|
||||
);
|
||||
|
||||
// Show Helcim modal and complete payment
|
||||
|
|
@ -267,15 +283,17 @@ const handleSubmit = async () => {
|
|||
}
|
||||
|
||||
// Complete series pass purchase
|
||||
const purchaseBody = {
|
||||
name: form.value.name,
|
||||
email: form.value.email,
|
||||
};
|
||||
if (transactionId) purchaseBody.paymentId = transactionId;
|
||||
|
||||
const purchaseResponse = await $fetch(
|
||||
`/api/series/${props.seriesId}/tickets/purchase`,
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
name: form.value.name,
|
||||
email: form.value.email,
|
||||
paymentId: transactionId,
|
||||
},
|
||||
body: purchaseBody,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue