"use client" import { useRouter } from "next/navigation" import { useSession } from "next-auth/react" import { useState, useTransition } from "react" import { useIntl } from "react-intl" import { dt } from "@/lib/dt" import { useRatesStore } from "@/stores/select-rate" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { isValidClientSession } from "@/utils/clientSession" import { formatPrice } from "@/utils/numberFormatting" import MobileSummary from "./MobileSummary" import { getTotalPrice } from "./utils" import styles from "./rateSummary.module.css" import { RateEnum } from "@/types/enums/rate" import { RateTypeEnum } from "@/types/enums/rateType" export default function RateSummary() { const { bookingCode, bookingRooms, dates, isFetchingPackages, rateSummary, roomsAvailability, searchParams, } = useRatesStore((state) => ({ bookingCode: state.booking.bookingCode, bookingRooms: state.booking.rooms, dates: { checkInDate: state.booking.fromDate, checkOutDate: state.booking.toDate, }, isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages), rateSummary: state.rateSummary, roomsAvailability: state.roomsAvailability, searchParams: state.searchParams, })) const { data: session } = useSession() const isUserLoggedIn = isValidClientSession(session) const [isSubmitting, setIsSubmitting] = useState(false) const intl = useIntl() const router = useRouter() const params = new URLSearchParams(searchParams) const [_, startTransition] = useTransition() if (!roomsAvailability) { return null } const checkInDate = new Date(dates.checkInDate) const checkOutDate = new Date(dates.checkOutDate) const nights = dt(checkOutDate).diff(dt(checkInDate), "days") const totalNights = intl.formatMessage( { defaultMessage: "{totalNights, plural, one {# night} other {# nights}}", }, { totalNights: nights } ) const totalAdults = intl.formatMessage( { defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}", }, { totalAdults: bookingRooms.reduce((acc, room) => acc + room.adults, 0) } ) const childrenInOneOrMoreRooms = bookingRooms.some( (room) => room.childrenInRoom?.length ) const childrenInroom = intl.formatMessage( { defaultMessage: "{totalChildren, plural, one {# child} other {# children}}", }, { totalChildren: bookingRooms.reduce( (acc, room) => acc + (room.childrenInRoom?.length ?? 0), 0 ), } ) const totalChildren = childrenInOneOrMoreRooms ? `, ${childrenInroom}` : "" const totalRooms = intl.formatMessage( { defaultMessage: "{totalRooms, plural, one {# room} other {# rooms}}", }, { totalRooms: bookingRooms.length } ) const summaryPriceText = `${totalNights}, ${totalAdults}${totalChildren}, ${totalRooms}` const totalRoomsRequired = bookingRooms.length const isAllRoomsSelected = rateSummary.filter((rate) => rate !== null).length === totalRoomsRequired const hasMemberRates = rateSummary.some( (room) => room && "member" in room.product && room.product.member ) const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn const freeCancelation = intl.formatMessage({ defaultMessage: "Free cancellation", }) const nonRefundable = intl.formatMessage({ defaultMessage: "Non-refundable", }) const freeBooking = intl.formatMessage({ defaultMessage: "Free rebooking", }) const payLater = intl.formatMessage({ defaultMessage: "Pay later", }) const payNow = intl.formatMessage({ defaultMessage: "Pay now", }) function getRateDetails(rate: RateEnum) { switch (rate) { case RateEnum.change: return `${freeBooking}, ${payNow}` case RateEnum.flex: return `${freeCancelation}, ${payLater}` case RateEnum.save: default: return `${nonRefundable}, ${payNow}` } } function handleSubmit(e: React.FormEvent) { e.preventDefault() setIsSubmitting(true) startTransition(() => { router.push(`details?${params}`) }) } if (!rateSummary.length || isFetchingPackages) { return null } const isBookingCodeRate = rateSummary.some( (rate) => rate && "public" in rate.product && rate.product.public?.rateType !== RateTypeEnum.Regular ) const isVoucherRate = rateSummary.some( (rate) => rate && "voucher" in rate.product ) const isCorporateChequeRate = rateSummary.some( (rate) => rate && "corporateCheque" in rate.product ) const showDiscounted = isUserLoggedIn || isBookingCodeRate || isVoucherRate || isCorporateChequeRate const mainRoomProduct = rateSummary[0] const totalPriceToShow = getTotalPrice( mainRoomProduct, rateSummary, isUserLoggedIn ) const rateProduct = rateSummary.find((rate) => rate?.product)?.product if (!totalPriceToShow || !rateProduct) { return null } let mainRoomCurrency = "" if ("member" in rateProduct && rateProduct.member?.localPrice) { mainRoomCurrency = rateProduct.member.localPrice.currency } if ( !mainRoomCurrency && "public" in rateProduct && rateProduct.public?.localPrice ) { mainRoomCurrency = rateProduct.public.localPrice.currency } // attribute data-footer-spacing used to add spacing // beneath footer to be able to show entire footer upon // scrolling down to the bottom of the page return (
{rateSummary.map((room, index) => { if (!room) { return (
{intl.formatMessage( { defaultMessage: "Room {roomIndex}", }, { roomIndex: index + 1 } )} {intl.formatMessage({ defaultMessage: "Select room", })}
) } return (
{rateSummary.length > 1 ? ( <> {intl.formatMessage( { defaultMessage: "Room {roomIndex}", }, { roomIndex: index + 1 } )} {room.roomType} {getRateDetails(room.rate)} ) : ( <> {room.roomType} {getRateDetails(room.rate)} )}
) })} {/* Render unselected rooms */} {Array.from({ length: totalRoomsRequired - rateSummary.length, }).map((_, index) => (
{intl.formatMessage( { defaultMessage: "Room {roomIndex}", }, { roomIndex: rateSummary.length + index + 1 } )} {intl.formatMessage({ defaultMessage: "Select room", })}
))}
{showMemberDiscountBanner && (
{ if (!rate) { return total } const { packages: roomPackages, product } = rate const memberExists = "member" in product && product.member const publicExists = "public" in product && product.public if (!memberExists) { if (!publicExists) { return total } } const price = product.member?.localPrice.pricePerStay || product.public?.localPrice.pricePerStay if (!price) { return total } const selectedPackagesPrice = roomPackages.reduce( (acc, pkg) => acc + pkg.localPrice.totalPrice, 0 ) return total + price + selectedPackagesPrice }, 0), currency: mainRoomCurrency, }} />
)}
{intl.formatMessage( { defaultMessage: "Total price (incl VAT)", }, { b: (str) => {str} } )} {summaryPriceText}
{formatPrice( intl, totalPriceToShow.local.price, totalPriceToShow.local.currency, totalPriceToShow.local.additionalPrice, totalPriceToShow.local.additionalPriceCurrency )} {bookingCode && totalPriceToShow.local.regularPrice && ( {formatPrice( intl, totalPriceToShow.local.regularPrice, totalPriceToShow.local.currency )} )} {totalPriceToShow.requested ? ( {intl.formatMessage( { defaultMessage: "Approx. {value}", }, { value: formatPrice( intl, totalPriceToShow.requested.price, totalPriceToShow.requested.currency, totalPriceToShow.requested.additionalPrice, totalPriceToShow.requested.additionalPriceCurrency ), } )} ) : null}
{intl.formatMessage({ defaultMessage: "Total price", })} {formatPrice( intl, totalPriceToShow.local.price, totalPriceToShow.local.currency, totalPriceToShow.local.additionalPrice, totalPriceToShow.local.additionalPriceCurrency )} {summaryPriceText}
) }