feat: add dynamic pricing tiers to CCG registration
Replace hardcoded €80 ticket price with date-based tiers: - Early bird €80 (until Mar 31) - Regular €120 (Apr 1 – Jun 30) - Late €150 (Jul 1+) Both the register page and checkout API now compute the current tier at request time, so prices update automatically on cutoff dates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cdaf09e14d
commit
a6c8f0a477
|
|
@ -11,8 +11,19 @@ function getMollie() {
|
||||||
return mollieClient
|
return mollieClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic pricing configuration (in EUR)
|
// Dynamic pricing tiers (in EUR)
|
||||||
const TICKET_PRICE = 80 // €80 early bird
|
const PRICING_TIERS = [
|
||||||
|
{ label: "Early bird", price: 80, cutoff: "2026-03-31" },
|
||||||
|
{ label: "Regular", price: 120, cutoff: "2026-07-01" },
|
||||||
|
{ label: "Late", price: 150, cutoff: "2099-12-31" },
|
||||||
|
]
|
||||||
|
|
||||||
|
function getCurrentTicketPrice(): number {
|
||||||
|
const now = new Date().toISOString().slice(0, 10)
|
||||||
|
const tier = PRICING_TIERS.find((t) => now < t.cutoff) ?? PRICING_TIERS[PRICING_TIERS.length - 1]
|
||||||
|
return tier.price
|
||||||
|
}
|
||||||
|
|
||||||
const PROCESSING_FEE_PERCENT = 0.02 // 2% to cover Mollie payment processing fees
|
const PROCESSING_FEE_PERCENT = 0.02 // 2% to cover Mollie payment processing fees
|
||||||
|
|
||||||
// Accommodation prices per person for 7 nights
|
// Accommodation prices per person for 7 nights
|
||||||
|
|
@ -39,8 +50,9 @@ export async function POST(request: NextRequest) {
|
||||||
const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null
|
const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null
|
||||||
|
|
||||||
// Calculate subtotal
|
// Calculate subtotal
|
||||||
let subtotal = TICKET_PRICE
|
const ticketPrice = getCurrentTicketPrice()
|
||||||
const descriptionParts = ["CCG 2026 Ticket (€80)"]
|
let subtotal = ticketPrice
|
||||||
|
const descriptionParts = [`CCG 2026 Ticket (€${ticketPrice})`]
|
||||||
|
|
||||||
if (includeAccommodation) {
|
if (includeAccommodation) {
|
||||||
const accom = ACCOMMODATION_PRICES[accommodationType]
|
const accom = ACCOMMODATION_PRICES[accommodationType]
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,15 @@ export default function RegisterPage() {
|
||||||
dietaryOther: "",
|
dietaryOther: "",
|
||||||
crewConsent: "",
|
crewConsent: "",
|
||||||
})
|
})
|
||||||
const baseTicketPrice = 80 // Early bird price €80
|
// Dynamic pricing tiers
|
||||||
|
const pricingTiers = [
|
||||||
|
{ label: "Early bird", price: 80, cutoff: "2026-03-31" },
|
||||||
|
{ label: "Regular", price: 120, cutoff: "2026-07-01" },
|
||||||
|
{ label: "Late", price: 150, cutoff: "2099-12-31" },
|
||||||
|
]
|
||||||
|
const now = new Date().toISOString().slice(0, 10)
|
||||||
|
const currentTier = pricingTiers.find((t) => now < t.cutoff) ?? pricingTiers[pricingTiers.length - 1]
|
||||||
|
const baseTicketPrice = currentTier.price
|
||||||
|
|
||||||
const accommodationPrices: Record<string, { label: string; price: number }> = {
|
const accommodationPrices: Record<string, { label: string; price: number }> = {
|
||||||
"ch-multi": { label: "Bed in shared room (Commons Hub)", price: 279.30 },
|
"ch-multi": { label: "Bed in shared room (Commons Hub)", price: 279.30 },
|
||||||
|
|
@ -133,7 +141,7 @@ export default function RegisterPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Event Registration</CardTitle>
|
<CardTitle>Event Registration</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Early bird pricing available until March 31, 2026
|
{currentTier.label} pricing — €{currentTier.price}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -143,7 +151,9 @@ export default function RegisterPage() {
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">CCG 2026 Ticket</div>
|
<div className="font-medium">CCG 2026 Ticket</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
€80 Early bird (until Mar 31) · €120 Regular (Apr-Jun) · €150 Late (after Jul 1)
|
{pricingTiers.map((t, i) => (
|
||||||
|
<span key={t.label}>{i > 0 ? " · " : ""}€{t.price} {t.label}{t === currentTier ? " (current)" : ""}</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
CCA members: Bring two newcomers, get a free ticket!
|
CCA members: Bring two newcomers, get a free ticket!
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue