feat(SW-718) Refactor select rate to support multiroom
This commit is contained in:
182
components/HotelReservation/SelectRate/RateSummary/index.tsx
Normal file
182
components/HotelReservation/SelectRate/RateSummary/index.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
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 Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
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({
|
||||
rateSummary,
|
||||
isUserLoggedIn,
|
||||
packages,
|
||||
roomsAvailability,
|
||||
}: RateSummaryProps) {
|
||||
const intl = useIntl()
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(true), 0)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
const {
|
||||
member,
|
||||
public: publicRate,
|
||||
features,
|
||||
roomType,
|
||||
priceName,
|
||||
priceTerm,
|
||||
} = rateSummary
|
||||
|
||||
const isPetRoomSelected = features.some(
|
||||
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
const petRoomPackage = packages?.find(
|
||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
const petRoomLocalPrice =
|
||||
isPetRoomSelected && petRoomPackage?.localPrice.totalPrice
|
||||
? Number(petRoomPackage?.localPrice.totalPrice)
|
||||
: 0
|
||||
const petRoomRequestedPrice =
|
||||
isPetRoomSelected && petRoomPackage?.requestedPrice.totalPrice
|
||||
? Number(petRoomPackage?.requestedPrice.totalPrice)
|
||||
: 0
|
||||
|
||||
const priceToShow = isUserLoggedIn && member ? member : publicRate
|
||||
|
||||
const totalPriceToShow = {
|
||||
localPrice: {
|
||||
currency: priceToShow.localPrice.currency,
|
||||
price: priceToShow.localPrice.pricePerStay + petRoomLocalPrice,
|
||||
},
|
||||
requestedPrice: !priceToShow.requestedPrice
|
||||
? undefined
|
||||
: {
|
||||
currency: priceToShow.requestedPrice.currency,
|
||||
price:
|
||||
priceToShow.requestedPrice.pricePerStay + petRoomRequestedPrice,
|
||||
},
|
||||
}
|
||||
|
||||
const checkInDate = new Date(roomsAvailability.checkInDate)
|
||||
const checkOutDate = new Date(roomsAvailability.checkOutDate)
|
||||
const nights = dt(checkOutDate).diff(dt(checkInDate), "days")
|
||||
|
||||
const showMemberDiscountBanner = member && !isUserLoggedIn
|
||||
|
||||
const summaryPriceText = `${intl.formatMessage(
|
||||
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||
{ totalNights: nights }
|
||||
)}, ${intl.formatMessage(
|
||||
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||
{ totalAdults: roomsAvailability.occupancy?.adults }
|
||||
)}${
|
||||
roomsAvailability.occupancy?.children?.length
|
||||
? `, ${intl.formatMessage(
|
||||
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
|
||||
{ totalChildren: roomsAvailability.occupancy.children.length }
|
||||
)}`
|
||||
: ""
|
||||
}`
|
||||
|
||||
return (
|
||||
<div className={styles.summary} data-visible={isVisible}>
|
||||
{showMemberDiscountBanner && <SignupPromoMobile />}
|
||||
<div className={styles.content}>
|
||||
<div className={styles.summaryText}>
|
||||
<Subtitle color="uiTextHighContrast">{roomType}</Subtitle>
|
||||
<Body color="uiTextMediumContrast">{`${priceName}, ${priceTerm}`}</Body>
|
||||
</div>
|
||||
<div className={styles.summaryPriceContainer}>
|
||||
{showMemberDiscountBanner && (
|
||||
<div className={styles.promoContainer}>
|
||||
<SignupPromoDesktop
|
||||
memberPrice={{
|
||||
amount: member.localPrice.pricePerStay + petRoomLocalPrice,
|
||||
currency: member.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.localPrice.price,
|
||||
totalPriceToShow.localPrice.currency
|
||||
)}
|
||||
</Subtitle>
|
||||
{totalPriceToShow?.requestedPrice ? (
|
||||
<Body color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Approx. {value}" },
|
||||
{
|
||||
value: formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.requestedPrice.price,
|
||||
totalPriceToShow.requestedPrice.currency
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Body>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.summaryPriceTextMobile}>
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({ id: "Total price" })}
|
||||
</Caption>
|
||||
<Subtitle color={isUserLoggedIn ? "red" : "uiTextHighContrast"}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.localPrice.price,
|
||||
totalPriceToShow.localPrice.currency
|
||||
)}
|
||||
</Subtitle>
|
||||
<Footnote
|
||||
color="uiTextMediumContrast"
|
||||
className={styles.summaryPriceTextMobile}
|
||||
>
|
||||
{summaryPriceText}
|
||||
</Footnote>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
theme="base"
|
||||
className={styles.continueButton}
|
||||
>
|
||||
{intl.formatMessage({ id: "Continue" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
.summary {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
bottom: -100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
padding: 0 0 var(--Spacing-x5);
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
transition: bottom 300ms ease-in-out;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--max-width-page);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary[data-visible="true"] {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.summaryPriceContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x4);
|
||||
padding-top: var(--Spacing-x2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.promoContainer {
|
||||
display: none;
|
||||
max-width: 264px;
|
||||
}
|
||||
.summaryPrice {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.petInfo {
|
||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-left: var(--Spacing-x2);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summaryText {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summaryPriceTextDesktop {
|
||||
align-self: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.continueButton {
|
||||
margin-left: auto;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.summaryPriceTextMobile {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.summary {
|
||||
padding: var(--Spacing-x3) 0 var(--Spacing-x5);
|
||||
}
|
||||
.content {
|
||||
flex-direction: row;
|
||||
}
|
||||
.petInfo,
|
||||
.promoContainer,
|
||||
.summaryText,
|
||||
.summaryPriceTextDesktop {
|
||||
display: block;
|
||||
}
|
||||
.summaryPriceTextMobile {
|
||||
display: none;
|
||||
}
|
||||
.summaryPrice,
|
||||
.continueButton {
|
||||
width: auto;
|
||||
}
|
||||
.summaryPriceContainer {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user