193 lines
6.0 KiB
TypeScript
193 lines
6.0 KiB
TypeScript
/**
|
|
* Centralized event configuration for CoFi registration.
|
|
*
|
|
* All event-specific values (pricing, dates, accommodation, branding)
|
|
* live here instead of being scattered across components and API routes.
|
|
* To adapt this app for a new event, edit this file only.
|
|
*/
|
|
|
|
// ── Event basics ──────────────────────────────────────────────────
|
|
|
|
export const EVENT_NAME = "CoFi"
|
|
export const EVENT_FULL_NAME = "Collaborative Finance"
|
|
export const EVENT_YEAR = 2026
|
|
export const EVENT_TAGLINE = "Reimagining finance for the commons"
|
|
export const EVENT_DATES = "TBD, 2026"
|
|
export const EVENT_LOCATION = "TBD"
|
|
export const EVENT_SHORT = `${EVENT_NAME} ${EVENT_YEAR}`
|
|
|
|
// ── Ticket pricing tiers (EUR) ────────────────────────────────────
|
|
|
|
interface PricingTier {
|
|
label: string
|
|
price: number
|
|
/** Tier is active if today < cutoff date (ISO string, exclusive) */
|
|
cutoff: string
|
|
}
|
|
|
|
export const PRICING_TIERS: PricingTier[] = [
|
|
{ label: "Early bird", price: 100, cutoff: "2026-04-01" },
|
|
{ label: "Regular", price: 200, cutoff: "2026-06-01" },
|
|
{ label: "Late", price: 250, cutoff: "2099-12-31" },
|
|
]
|
|
|
|
/** Returns the currently active pricing tier based on today's date */
|
|
export function getCurrentTier(): PricingTier {
|
|
const now = new Date().toISOString().slice(0, 10)
|
|
return PRICING_TIERS.find((t) => now < t.cutoff) ?? PRICING_TIERS[PRICING_TIERS.length - 1]
|
|
}
|
|
|
|
/** Human-readable pricing summary for display */
|
|
export function getPricingSummary(): string {
|
|
return PRICING_TIERS.map((t) => `€${t.price} ${t.label}`).join(" · ")
|
|
}
|
|
|
|
// ── Processing fee ────────────────────────────────────────────────
|
|
|
|
export const PROCESSING_FEE_PERCENT = 0.02 // 2% to cover Mollie fees
|
|
|
|
// ── Accommodation ─────────────────────────────────────────────────
|
|
|
|
export interface AccommodationOption {
|
|
id: string
|
|
label: string
|
|
price: number
|
|
nightlyRate: number
|
|
venue: string
|
|
venueKey: string
|
|
description?: string
|
|
}
|
|
|
|
export interface AccommodationVenue {
|
|
key: string
|
|
name: string
|
|
description: string
|
|
options: AccommodationOption[]
|
|
}
|
|
|
|
export const ACCOMMODATION_VENUES: AccommodationVenue[] = [
|
|
{
|
|
key: "venue-a",
|
|
name: "Venue A",
|
|
description: "TBD — Primary event venue accommodation.",
|
|
options: [
|
|
{
|
|
id: "va-shared",
|
|
label: "Bed in shared room",
|
|
price: 280,
|
|
nightlyRate: 40,
|
|
venue: "Venue A",
|
|
venueKey: "venue-a",
|
|
},
|
|
{
|
|
id: "va-double",
|
|
label: "Bed in double room",
|
|
price: 350,
|
|
nightlyRate: 50,
|
|
venue: "Venue A",
|
|
venueKey: "venue-a",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: "venue-b",
|
|
name: "Venue B",
|
|
description: "TBD — Secondary accommodation option.",
|
|
options: [
|
|
{
|
|
id: "vb-single",
|
|
label: "Single room",
|
|
price: 560,
|
|
nightlyRate: 80,
|
|
venue: "Venue B",
|
|
venueKey: "venue-b",
|
|
},
|
|
{
|
|
id: "vb-double",
|
|
label: "Double room (per person)",
|
|
price: 350,
|
|
nightlyRate: 50,
|
|
venue: "Venue B",
|
|
venueKey: "venue-b",
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
/** Flat map of accommodation ID → option for quick lookup */
|
|
export const ACCOMMODATION_MAP: Record<string, AccommodationOption> = Object.fromEntries(
|
|
ACCOMMODATION_VENUES.flatMap((v) => v.options.map((o) => [o.id, o]))
|
|
)
|
|
|
|
/** Number of nights (used in display) */
|
|
export const ACCOMMODATION_NIGHTS = 7
|
|
|
|
// ── Booking sheet criteria (maps accommodation IDs to bed search criteria) ──
|
|
|
|
export interface BookingCriteria {
|
|
venue: string
|
|
bedTypes: string[]
|
|
roomFilter?: (room: string) => boolean
|
|
}
|
|
|
|
/**
|
|
* Map accommodation option IDs to booking sheet search criteria.
|
|
* Update this when you configure the actual booking spreadsheet.
|
|
*/
|
|
export const BOOKING_CRITERIA: Record<string, BookingCriteria> = {
|
|
"va-shared": {
|
|
venue: "Venue A",
|
|
bedTypes: ["bunk up", "bunk down", "single"],
|
|
},
|
|
"va-double": {
|
|
venue: "Venue A",
|
|
bedTypes: ["double", "double (shared)"],
|
|
},
|
|
"vb-single": {
|
|
venue: "Venue B",
|
|
bedTypes: ["double"],
|
|
},
|
|
"vb-double": {
|
|
venue: "Venue B",
|
|
bedTypes: ["single", "double (shared)"],
|
|
},
|
|
}
|
|
|
|
// ── Form field toggles ────────────────────────────────────────────
|
|
|
|
export const FORM_FIELDS = {
|
|
dietary: true,
|
|
howHeard: true,
|
|
crewConsent: true,
|
|
wantFood: true,
|
|
}
|
|
|
|
// ── Email branding ────────────────────────────────────────────────
|
|
|
|
export const EMAIL_BRANDING = {
|
|
primaryColor: "#2563eb", // blue-600
|
|
accentColor: "#0d9488", // teal-600
|
|
headerText: "You're In!",
|
|
taglineColor: "#1e40af", // blue-800
|
|
highlightBg: "#eff6ff", // blue-50
|
|
highlightBorder: "#2563eb", // blue-600
|
|
fromDefault: `${EVENT_NAME} <newsletter@collaborative-finance.net>`,
|
|
internalNotifyDefault: "cofi.gathering@gmail.com",
|
|
}
|
|
|
|
// ── External links ────────────────────────────────────────────────
|
|
|
|
export const LINKS = {
|
|
website: "https://www.collaborative-finance.net",
|
|
register: "https://register.collaborative-finance.net",
|
|
telegram: "", // TBD — set when community channel is created
|
|
community: "", // TBD — set when community channel is created
|
|
contactEmail: "cofi.gathering@gmail.com",
|
|
}
|
|
|
|
// ── Payment description template ──────────────────────────────────
|
|
|
|
export function buildPaymentDescription(parts: string[]): string {
|
|
return `${EVENT_SHORT} Registration — ${parts.join(" + ")}`
|
|
}
|