mirror of
https://github.com/Ichitux/lambada-fiesta-live.git
synced 2026-05-16 09:42:20 +02:00
Changes in module "mixed reservations"
All checks were successful
Deploy NPM app / Deploy NPM (push) Successful in 1m23s
All checks were successful
Deploy NPM app / Deploy NPM (push) Successful in 1m23s
This commit is contained in:
@@ -1,20 +1,26 @@
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { MIXED_BOOKING_PACKAGES, ROOM_TYPES } from "@/data/event-data";
|
||||
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 } from "lucide-react";
|
||||
import { Check, Star, Circle, CheckCircle2 } from "lucide-react";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
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",
|
||||
single: "individual",
|
||||
});
|
||||
const [wantsHotel, setWantsHotel] = useState<Record<string, boolean | null>>({
|
||||
full: null,
|
||||
party: null,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -36,11 +42,26 @@ const MixedBookingSection = () => {
|
||||
<p className="text-muted-foreground max-w-2xl mx-auto">{t("mixedBooking.subtitle")}</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8 max-w-5xl mx-auto items-stretch">
|
||||
<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 price = pkg.roomPrices[selectedRoom];
|
||||
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 (
|
||||
@@ -56,14 +77,19 @@ const MixedBookingSection = () => {
|
||||
: "shadow-card hover:shadow-elevated border border-border"
|
||||
}`}
|
||||
>
|
||||
{isFeatured && (
|
||||
<div className="absolute top-4 right-4 z-10">
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{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"
|
||||
@@ -86,39 +112,81 @@ const MixedBookingSection = () => {
|
||||
<p className="text-4xl md:text-5xl font-bold text-primary">
|
||||
{price > 0 ? `${price}€` : t("mixedBooking.priceTBD")}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{t("mixedBooking.perPerson")}
|
||||
<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-2">
|
||||
{t("mixedBooking.selectRoom")}
|
||||
<Label className="block text-sm font-medium text-foreground mb-3">
|
||||
{t("mixedBooking.hotelQuestion")}
|
||||
</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 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">
|
||||
|
||||
Reference in New Issue
Block a user