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

@ -0,0 +1,192 @@
<template>
<div
class="ticket-card rounded-xl border p-6 transition-all duration-200"
:class="[
isSelected
? 'border-primary bg-primary/5'
: 'border-ghost-600 bg-ghost-800/50',
isAvailable && !alreadyRegistered
? 'hover:border-primary/50 cursor-pointer'
: 'opacity-60 cursor-not-allowed',
]"
@click="handleClick"
>
<!-- Ticket Header -->
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-lg font-semibold text-ghost-100">
{{ ticketInfo.name }}
</h3>
<p v-if="ticketInfo.description" class="text-sm text-ghost-300 mt-1">
{{ ticketInfo.description }}
</p>
</div>
<!-- Badge -->
<div v-if="ticketInfo.isMember" class="flex-shrink-0 ml-4">
<span
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
>
Members Only
</span>
</div>
</div>
<!-- Price Display -->
<div class="mb-4">
<div class="flex items-baseline gap-2">
<span
class="text-3xl font-bold"
:class="ticketInfo.isFree ? 'text-green-400' : 'text-ghost-100'"
>
{{ ticketInfo.formattedPrice }}
</span>
<!-- 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"
>
Early Bird
</span>
</div>
<!-- Regular Price (if early bird) -->
<div
v-if="ticketInfo.isEarlyBird && ticketInfo.formattedRegularPrice"
class="mt-1"
>
<span class="text-sm text-ghost-400 line-through">
Regular: {{ ticketInfo.formattedRegularPrice }}
</span>
</div>
<!-- Early Bird Countdown -->
<div
v-if="ticketInfo.isEarlyBird && ticketInfo.earlyBirdDeadline"
class="mt-2 text-xs text-amber-400"
>
<Icon name="heroicons:clock" class="w-4 h-4 inline mr-1" />
Early bird ends {{ formatDeadline(ticketInfo.earlyBirdDeadline) }}
</div>
</div>
<!-- Member Savings -->
<div
v-if="ticketInfo.publicTicket && ticketInfo.memberSavings > 0"
class="mb-4 p-3 bg-green-900/20 rounded-lg border border-green-800"
>
<p class="text-sm text-green-400">
<Icon name="heroicons:check-circle" class="w-4 h-4 inline mr-1" />
You save {{ formatPrice(ticketInfo.memberSavings) }} as a member!
</p>
<p class="text-xs text-ghost-400 mt-1">
Public price: {{ ticketInfo.publicTicket.formattedPrice }}
</p>
</div>
<!-- Availability -->
<div class="flex items-center justify-between text-sm">
<div>
<span
v-if="alreadyRegistered"
class="text-green-400 flex items-center gap-1"
>
<Icon name="heroicons:check-circle-solid" class="w-4 h-4" />
You're registered
</span>
<span
v-else-if="!isAvailable"
class="text-red-400 flex items-center gap-1"
>
<Icon name="heroicons:x-circle-solid" class="w-4 h-4" />
Sold Out
</span>
<span v-else-if="ticketInfo.remaining !== null" class="text-ghost-300">
{{ ticketInfo.remaining }} remaining
</span>
<span v-else class="text-ghost-300"> Unlimited availability </span>
</div>
<!-- Selection Indicator -->
<div v-if="isSelected && isAvailable && !alreadyRegistered">
<Icon name="heroicons:check-circle-solid" class="w-5 h-5 text-primary" />
</div>
</div>
<!-- Waitlist Option -->
<div
v-if="!isAvailable && ticketInfo.waitlistAvailable && !alreadyRegistered"
class="mt-4 pt-4 border-t border-ghost-600"
>
<UButton
color="gray"
size="sm"
block
@click.stop="$emit('join-waitlist')"
>
Join Waitlist
</UButton>
</div>
</div>
</template>
<script setup>
const props = defineProps({
ticketInfo: {
type: Object,
required: true,
},
isSelected: {
type: Boolean,
default: false,
},
isAvailable: {
type: Boolean,
default: true,
},
alreadyRegistered: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["select", "join-waitlist"]);
const handleClick = () => {
if (props.isAvailable && !props.alreadyRegistered) {
emit("select");
}
};
const formatDeadline = (deadline) => {
const date = new Date(deadline);
const now = new Date();
const diff = date - now;
// If less than 24 hours, show hours
if (diff < 24 * 60 * 60 * 1000) {
const hours = Math.floor(diff / (60 * 60 * 1000));
return `in ${hours} hour${hours !== 1 ? "s" : ""}`;
}
// Otherwise show date
return `on ${date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})}`;
};
const formatPrice = (amount) => {
return new Intl.NumberFormat("en-CA", {
style: "currency",
currency: "CAD",
}).format(amount);
};
</script>
<style scoped>
.ticket-card {
position: relative;
}
</style>