mirror of
https://github.com/Ichitux/lambada-fiesta-live.git
synced 2026-06-10 01:14:57 +02:00
233 lines
11 KiB
TypeScript
233 lines
11 KiB
TypeScript
import { useState } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { MIXED_BOOKING_PACKAGES, ROOM_TYPES, getFullPassPrice } from "@/data/event-data";
|
|
import type { RoomType } from "@/data/event-data";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Check, Star, Circle, CheckCircle2, ExternalLink } from "lucide-react";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
const FEATURED_PASS = "full";
|
|
const NON_HOTEL_FEE = 50;
|
|
const PARTY_PRICE = 25;
|
|
|
|
const MixedBookingSection = () => {
|
|
const { t } = useTranslation();
|
|
const fullPassPricing = getFullPassPrice();
|
|
const [selectedRooms, setSelectedRooms] = useState<Record<string, RoomType>>({
|
|
full: "individual",
|
|
party: "individual",
|
|
});
|
|
const [wantsHotel, setWantsHotel] = useState<Record<string, boolean | null>>({
|
|
full: null,
|
|
party: null,
|
|
});
|
|
|
|
return (
|
|
<section
|
|
id="mixed-booking"
|
|
className="section-padding bg-background scroll-mt-24 relative z-10 -mt-[40px] pt-[120px]"
|
|
style={{ borderRadius: "0 100% 0 0 / 0 120px 0 0" }}
|
|
>
|
|
<div className="container mx-auto">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: false, amount: 0.15 }}
|
|
className="text-center mb-12"
|
|
>
|
|
<h2 className="font-display text-4xl md:text-5xl lg:text-7xl break-words font-bold pt-4 pb-10 leading-[1.8] text-gradient">
|
|
{t("mixedBooking.title")}
|
|
</h2>
|
|
<p className="text-muted-foreground max-w-2xl mx-auto">{t("mixedBooking.subtitle")}</p>
|
|
</motion.div>
|
|
|
|
<div className="grid sm:grid-cols-2 gap-6 lg:gap-8 max-w-4xl mx-auto items-stretch">
|
|
{MIXED_BOOKING_PACKAGES.map((pkg, i) => {
|
|
const selectedRoom = selectedRooms[pkg.id] || "individual";
|
|
const hasHotel = wantsHotel[pkg.id] === true;
|
|
// Use dynamic pricing for Full Pass, fixed price for Party Pass
|
|
// Only Full Pass has the +50€ non-hotel fee
|
|
let basePrice = pkg.id === "full" ? fullPassPricing.price : PARTY_PRICE;
|
|
let roomPrice = 0;
|
|
if (hasHotel) {
|
|
roomPrice = pkg.roomPrices[selectedRoom as keyof typeof pkg.roomPrices];
|
|
}
|
|
|
|
const passPrice = pkg.id === "full"
|
|
? (hasHotel ? basePrice : basePrice + NON_HOTEL_FEE)
|
|
: basePrice;
|
|
const price = passPrice + roomPrice;
|
|
|
|
const isFeatured = pkg.id === FEATURED_PASS;
|
|
const isLastPrice = pkg.id === "full" && fullPassPricing.isLastPrice;
|
|
const showNonHotelNote = pkg.id === "full" && !hasHotel && basePrice > 0;
|
|
const features = t(`mixedBooking.${pkg.id}Features`).split("|").map((f: string) => f.trim());
|
|
|
|
return (
|
|
<motion.div
|
|
key={pkg.id}
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: false, amount: 0.15 }}
|
|
transition={{ delay: i * 0.1 }}
|
|
className={`relative flex h-full min-h-[520px] flex-col rounded-2xl overflow-hidden transition-shadow duration-300 ${
|
|
isFeatured
|
|
? "shadow-elevated border-2 border-primary/30"
|
|
: "shadow-card hover:shadow-elevated border border-border"
|
|
}`}
|
|
>
|
|
<div className="absolute top-4 right-4 z-10 flex flex-col gap-2 items-end">
|
|
{isFeatured && (
|
|
<span className="inline-flex items-center gap-1 bg-gradient-tropical text-primary-foreground text-xs font-bold px-3 py-1 rounded-full shadow-md">
|
|
<Star className="w-3 h-3 fill-current" />
|
|
{t("mixedBooking.popular")}
|
|
</span>
|
|
)}
|
|
{isLastPrice && (
|
|
<span className="inline-flex items-center gap-1 bg-red-600 text-white text-xs font-bold px-3 py-1 rounded-full shadow-md">
|
|
{t("mixedBooking.lastPrice")}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className={`px-6 pt-8 pb-4 text-center min-h-[160px] flex flex-col justify-between ${
|
|
isFeatured ? "bg-gradient-tropical" : "bg-secondary/10"
|
|
}`}>
|
|
<div>
|
|
<h3 className={`font-display text-2xl md:text-3xl font-bold mb-1 ${
|
|
isFeatured ? "text-primary-foreground" : "text-foreground"
|
|
}`}>
|
|
{t(`mixedBooking.passTypes.${pkg.id}`)}
|
|
</h3>
|
|
<p className={`text-sm ${
|
|
isFeatured ? "text-primary-foreground/80" : "text-muted-foreground"
|
|
}`}>
|
|
{t(`mixedBooking.${pkg.id}Description`)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="px-6 pt-6 pb-4 text-center bg-card">
|
|
<p className="text-4xl md:text-5xl font-bold text-primary">
|
|
{price > 0 ? `${price}€` : t("mixedBooking.priceTBD")}
|
|
</p>
|
|
<p className="text-sm font-medium text-foreground mt-2">
|
|
{t(`mixedBooking.passTypes.${pkg.id}`)}: {passPrice}€ {pkg.id === "full" ? t("mixedBooking.perPerson") : t("mixedBooking.perParty")}
|
|
</p>
|
|
{showNonHotelNote && (
|
|
<p className="text-xs text-red-600 font-medium mt-2">
|
|
{t("mixedBooking.nonHotelNote")}
|
|
</p>
|
|
)}
|
|
{hasHotel && (
|
|
<p className="text-sm font-medium text-foreground mt-2">
|
|
{t(`mixedBooking.roomTypes.${selectedRoom}`)}: {roomPrice}€
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="px-6 pt-4 pb-2 bg-card">
|
|
<Label className="block text-sm font-medium text-foreground mb-3">
|
|
{t("mixedBooking.hotelQuestion")}
|
|
</Label>
|
|
<div className="flex gap-3 mb-4">
|
|
<button
|
|
onClick={() => setWantsHotel((prev) => ({ ...prev, [pkg.id]: true }))}
|
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl border-2 text-sm font-medium transition-all duration-200 ${
|
|
hasHotel
|
|
? "border-primary bg-primary/5 text-primary"
|
|
: "border-input bg-background text-muted-foreground hover:border-primary/30"
|
|
}`}
|
|
>
|
|
<CheckCircle2 className={`w-5 h-5 ${hasHotel ? "text-primary" : "text-muted-foreground"}`} />
|
|
{t("mixedBooking.hotelYes")}
|
|
</button>
|
|
<button
|
|
onClick={() => setWantsHotel((prev) => ({ ...prev, [pkg.id]: false }))}
|
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl border-2 text-sm font-medium transition-all duration-200 ${
|
|
!hasHotel
|
|
? "border-primary bg-primary/5 text-primary"
|
|
: "border-input bg-background text-muted-foreground hover:border-primary/30"
|
|
}`}
|
|
>
|
|
<Circle className={`w-5 h-5 ${!hasHotel ? "text-primary" : "text-muted-foreground"}`} />
|
|
{t("mixedBooking.hotelNo")}
|
|
</button>
|
|
</div>
|
|
|
|
{hasHotel && (
|
|
<>
|
|
<Label className="block text-sm font-medium text-foreground mb-2">
|
|
{t("mixedBooking.selectRoom")}
|
|
</Label>
|
|
<Select
|
|
value={selectedRoom}
|
|
onValueChange={(value) =>
|
|
setSelectedRooms((prev) => ({
|
|
...prev,
|
|
[pkg.id]: value as RoomType,
|
|
}))
|
|
}
|
|
>
|
|
<SelectTrigger className={`w-full rounded-xl border px-4 py-3 text-sm text-foreground shadow-sm transition-colors duration-200 ${
|
|
isFeatured
|
|
? "border-primary/30 bg-primary/5 hover:border-primary/50"
|
|
: "border-input bg-background hover:border-primary/30"
|
|
}`}>
|
|
<SelectValue placeholder={t("mixedBooking.selectRoom")} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{ROOM_TYPES.map((room) => (
|
|
<SelectItem key={room.id} value={room.id}>
|
|
{t(`mixedBooking.roomTypes.${room.id}`)}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<div className="px-6 pt-4 pb-6 flex-1 flex flex-col justify-between bg-card">
|
|
<ul className="space-y-2.5 mb-6">
|
|
{features.map((feature: string, idx: number) => (
|
|
<li key={idx} className="flex items-start gap-2.5 text-sm text-foreground/80">
|
|
<Check className={`w-4 h-4 mt-0.5 flex-shrink-0 ${
|
|
isFeatured ? "text-primary" : "text-secondary"
|
|
}`} />
|
|
<span>{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<Button
|
|
asChild
|
|
className={`w-full rounded-xl font-medium py-2.5 ${
|
|
isFeatured
|
|
? "bg-gradient-tropical hover:bg-gradient-tropical/90 text-primary-foreground"
|
|
: "bg-primary hover:bg-primary/90 text-primary-foreground"
|
|
}`}
|
|
>
|
|
<a
|
|
href="https://hotel-donangel.com/zouklambada-barcelona-2026/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center justify-center gap-2"
|
|
>
|
|
{t("mixedBooking.goToReservations")}
|
|
<ExternalLink className="w-4 h-4" />
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default MixedBookingSection;
|