Files
web/components/HotelReservation/SelectRate/RateSummary/index.tsx
Pontus Dreij c863294919 Merged in feat/SW-1055-Accordion-for-summary-bar-in-mobile-on-Select-Rate (pull request #1283)
Feat/SW-1055 Accordion for summary bar in mobile on Select Rate

* feat(SW-1055) created mobile summary for select rate

* feat(SW-1055) Added summary for mobile (accordion)


Approved-by: Tobias Johansson
2025-02-10 15:11:31 +00:00

211 lines
7.4 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
import SignupPromoMobile from "@/components/HotelReservation/SignupPromo/Mobile"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { formatPrice } from "@/utils/numberFormatting"
import MobileSummary from "./MobileSummary"
import { calculateTotalPrice } from "./utils"
import styles from "./rateSummary.module.css"
import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
export default function RateSummary({
isUserLoggedIn,
packages,
roomsAvailability,
booking,
vat,
}: RateSummaryProps) {
const intl = useIntl()
const [isVisible, setIsVisible] = useState(false)
const { getSelectedRateSummary } = useRateSelectionStore()
const { rooms } = booking
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 0)
return () => clearTimeout(timer)
}, [])
const selectedRateSummary = getSelectedRateSummary()
const totalRoomsRequired = rooms?.length || 1
if (selectedRateSummary.length === 0) return null
const petRoomPackage = packages?.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)
const totalPriceToShow = calculateTotalPrice(
selectedRateSummary,
isUserLoggedIn,
petRoomPackage
)
const isAllRoomsSelected = selectedRateSummary.length === totalRoomsRequired
const checkInDate = new Date(roomsAvailability.checkInDate)
const checkOutDate = new Date(roomsAvailability.checkOutDate)
const nights = dt(checkOutDate).diff(dt(checkInDate), "days")
const hasMemberRates = selectedRateSummary.some((room) => room.member)
const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn
const summaryPriceText = `${intl.formatMessage(
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
{ totalNights: nights }
)}, ${intl.formatMessage(
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
{ totalAdults: rooms.reduce((acc, room) => acc + room.adults, 0) }
)}${
rooms.some((room) => room.childrenInRoom?.length)
? `, ${intl.formatMessage(
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
{
totalChildren: rooms.reduce(
(acc, room) => acc + (room.childrenInRoom?.length ?? 0),
0
),
}
)}`
: ""
}, ${intl.formatMessage(
{ id: "{totalRooms, plural, one {# room} other {# rooms}}" },
{
totalRooms: rooms.length,
}
)}`
return (
<div className={styles.summary} data-visible={isVisible}>
<div className={styles.content}>
<div className={styles.summaryText}>
{selectedRateSummary.map((room, index) => (
<div key={index} className={styles.roomSummary}>
<Subtitle color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: index + 1 }
)}
</Subtitle>
<Body color="uiTextMediumContrast">{room.roomType}</Body>
<Caption color="uiTextMediumContrast">{`${room.priceName}, ${room.priceTerm}`}</Caption>
</div>
))}
{/* Render unselected rooms */}
{Array.from({
length: totalRoomsRequired - selectedRateSummary.length,
}).map((_, index) => (
<div key={`unselected-${index}`} className={styles.roomSummary}>
<Subtitle color="uiTextPlaceholder">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: selectedRateSummary.length + index + 1 }
)}
</Subtitle>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Select room" })}
</Body>
</div>
))}
</div>
<div className={styles.summaryPriceContainer}>
{showMemberDiscountBanner && (
<div className={styles.promoContainer}>
<SignupPromoDesktop
memberPrice={{
amount: selectedRateSummary.reduce((total, room) => {
const memberPrice =
room.member?.localPrice.pricePerStay ?? 0
const isPetRoom = room.features.some(
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
)
const petRoomPrice =
isPetRoom && petRoomPackage
? Number(petRoomPackage.localPrice.totalPrice || 0)
: 0
return total + memberPrice + petRoomPrice
}, 0),
currency:
selectedRateSummary[0].member?.localPrice.currency ??
selectedRateSummary[0].public.localPrice.currency,
}}
/>
</div>
)}
<div className={styles.summaryPriceTextDesktop}>
<Body>
{intl.formatMessage<React.ReactNode>(
{ id: "<b>Total price</b> (incl VAT)" },
{ b: (str) => <b>{str}</b> }
)}
</Body>
<Caption color="uiTextMediumContrast">{summaryPriceText}</Caption>
</div>
<div className={styles.summaryPrice}>
<div className={styles.summaryPriceTextDesktop}>
<Subtitle
color={isUserLoggedIn ? "red" : "uiTextHighContrast"}
textAlign="right"
>
{formatPrice(
intl,
totalPriceToShow.local.price,
totalPriceToShow.local.currency
)}
</Subtitle>
{totalPriceToShow?.requested ? (
<Body color="uiTextMediumContrast">
{intl.formatMessage(
{ id: "Approx. {value}" },
{
value: formatPrice(
intl,
totalPriceToShow.requested.price,
totalPriceToShow.requested.currency
),
}
)}
</Body>
) : null}
</div>
</div>
<Button
intent="primary"
theme="base"
type="submit"
className={styles.continueButton}
disabled={!isAllRoomsSelected}
>
{intl.formatMessage({ id: "Continue" })}
</Button>
</div>
</div>
<div className={styles.mobileSummary}>
{showMemberDiscountBanner ? <SignupPromoMobile /> : null}
<MobileSummary
totalPriceToShow={totalPriceToShow}
isAllRoomsSelected={isAllRoomsSelected}
booking={booking}
isUserLoggedIn={isUserLoggedIn}
vat={vat}
roomsAvailability={roomsAvailability}
/>
</div>
</div>
)
}