fix(BOOK-584): show discounted if specialrate or member * fix(BOOK-584): show discounted if specialrate or member Approved-by: Anton Gunnarsson
256 lines
8.2 KiB
TypeScript
256 lines
8.2 KiB
TypeScript
"use client"
|
|
|
|
import { cx } from "class-variance-authority"
|
|
import { useIntl } from "react-intl"
|
|
import { useMediaQuery } from "usehooks-ts"
|
|
|
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
|
import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
|
import Body from "@scandic-hotels/design-system/Body"
|
|
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
|
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
|
|
import useLang from "../../../../hooks/useLang"
|
|
import { useEnterDetailsStore } from "../../../../stores/enter-details"
|
|
import PriceDetailsModal from "../../../PriceDetailsModal"
|
|
import SignupPromoDesktop from "../../../SignupPromo/Desktop"
|
|
import { mapToPrice } from "./mapToPrice"
|
|
import Room from "./Room"
|
|
import { getMemberPrice, isDiscounted } from "./utils"
|
|
|
|
import styles from "./ui.module.css"
|
|
|
|
export default function SummaryUI({
|
|
isUserLoggedIn,
|
|
}: {
|
|
isUserLoggedIn: boolean
|
|
}) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
const isDesktop = useMediaQuery("(min-width: 1367px)")
|
|
|
|
const {
|
|
booking,
|
|
defaultCurrency,
|
|
hotelOffersBreakfast,
|
|
rooms,
|
|
totalPrice,
|
|
vat,
|
|
toggleSummaryOpen,
|
|
} = useEnterDetailsStore((state) => ({
|
|
booking: state.booking,
|
|
defaultCurrency: state.defaultCurrency,
|
|
hotelOffersBreakfast: state.hotelOffersBreakfast,
|
|
rooms: state.rooms,
|
|
totalPrice: state.totalPrice,
|
|
vat: state.vat,
|
|
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
|
}))
|
|
|
|
const nights = dt(booking.toDate).diff(booking.fromDate, "days")
|
|
|
|
const nightsMsg = intl.formatMessage(
|
|
{
|
|
id: "booking.numberOfNights",
|
|
defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
|
|
},
|
|
{ totalNights: nights }
|
|
)
|
|
|
|
function handleToggleSummary() {
|
|
if (toggleSummaryOpen) {
|
|
toggleSummaryOpen()
|
|
}
|
|
}
|
|
|
|
const roomOneGuest = rooms[0].room.guest
|
|
const showSignupPromo =
|
|
rooms.length === 1 &&
|
|
!isUserLoggedIn &&
|
|
!roomOneGuest.membershipNo &&
|
|
!roomOneGuest.join
|
|
|
|
const roomOneMemberPrice = getMemberPrice(rooms[0].room.roomRate)
|
|
|
|
const roomOneRoomRate = rooms[0].room.roomRate
|
|
const isVoucherRate = "voucher" in roomOneRoomRate
|
|
|
|
const priceDetailsRooms = mapToPrice(rooms, isUserLoggedIn)
|
|
const isAllCampaignRate = rooms.every(
|
|
(room) => room.room.roomRate.rateDefinition.isCampaignRate
|
|
)
|
|
|
|
const containsCampaignRate = rooms.find(
|
|
(r) => r && r.room.roomRate.rateDefinition.isCampaignRate
|
|
)
|
|
const showDiscounted =
|
|
isUserLoggedIn || rooms.some((room) => isDiscounted(room.room))
|
|
|
|
const totalCurrency = isVoucherRate
|
|
? CurrencyEnum.Voucher
|
|
: totalPrice.local.currency
|
|
|
|
return (
|
|
<section className={styles.summary}>
|
|
<header
|
|
className={styles.header}
|
|
role="button"
|
|
onClick={isDesktop ? undefined : handleToggleSummary}
|
|
>
|
|
<Subtitle className={styles.title} type="two">
|
|
{intl.formatMessage({
|
|
id: "booking.bookingSummary",
|
|
defaultMessage: "Booking summary",
|
|
})}
|
|
</Subtitle>
|
|
<Body className={styles.date} color="baseTextMediumContrast">
|
|
{dt(booking.fromDate).locale(lang).format(longDateFormat[lang])}
|
|
<MaterialIcon
|
|
icon="arrow_right"
|
|
color="Icon/Interactive/Secondary"
|
|
size={15}
|
|
/>
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{dt(booking.toDate).locale(lang).format(longDateFormat[lang])} (
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{nightsMsg})
|
|
</Body>
|
|
<MaterialIcon
|
|
className={styles.chevronIcon}
|
|
icon="keyboard_arrow_down"
|
|
size={30}
|
|
color="CurrentColor"
|
|
/>
|
|
</header>
|
|
<Divider color="Border/Divider/Subtle" />
|
|
{rooms.map(({ room }, idx) => (
|
|
<Room
|
|
key={idx}
|
|
defaultCurrency={defaultCurrency}
|
|
room={room}
|
|
roomNumber={idx + 1}
|
|
roomCount={rooms.length}
|
|
isUserLoggedIn={isUserLoggedIn}
|
|
nightsCount={nights}
|
|
showBookingCodeChip={
|
|
rooms.length !== 1 &&
|
|
(room.roomRate.rateDefinition.isCampaignRate ||
|
|
!!room.roomRate.bookingCode)
|
|
}
|
|
hotelOffersBreakfast={hotelOffersBreakfast}
|
|
/>
|
|
))}
|
|
|
|
<div>
|
|
<div className={styles.entry}>
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage(
|
|
{
|
|
id: "booking.totalPriceInclVat",
|
|
defaultMessage: "<b>Total price</b> (incl VAT)",
|
|
},
|
|
{
|
|
b: (str) => (
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<span>{str}</span>
|
|
</Typography>
|
|
),
|
|
}
|
|
)}
|
|
</p>
|
|
</Typography>
|
|
{totalPrice.requested ? (
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<p className={styles.approxPrice}>
|
|
{intl.formatMessage(
|
|
{
|
|
id: "booking.approxValue",
|
|
defaultMessage: "Approx. {value}",
|
|
},
|
|
{
|
|
value: formatPrice(
|
|
intl,
|
|
totalPrice.requested.price,
|
|
totalPrice.requested.currency,
|
|
totalPrice.requested.additionalPrice,
|
|
totalPrice.requested.additionalPriceCurrency
|
|
),
|
|
}
|
|
)}
|
|
</p>
|
|
</Typography>
|
|
) : null}
|
|
</div>
|
|
<div className={styles.prices}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<span
|
|
className={cx(styles.price, {
|
|
[styles.discounted]: showDiscounted,
|
|
})}
|
|
data-testid="total-price"
|
|
>
|
|
{formatPrice(
|
|
intl,
|
|
totalPrice.local.price,
|
|
totalCurrency,
|
|
totalPrice.local.additionalPrice,
|
|
totalPrice.local.additionalPriceCurrency
|
|
)}
|
|
</span>
|
|
</Typography>
|
|
{showDiscounted && totalPrice.local.regularPrice ? (
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
<s className={styles.strikeThroughRate}>
|
|
{formatPrice(
|
|
intl,
|
|
totalPrice.local.regularPrice,
|
|
totalPrice.local.currency
|
|
)}
|
|
</s>
|
|
</p>
|
|
</Typography>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.ctaWrapper}>
|
|
<PriceDetailsModal
|
|
bookingCode={booking.bookingCode}
|
|
defaultCurrency={defaultCurrency}
|
|
fromDate={booking.fromDate}
|
|
rooms={priceDetailsRooms}
|
|
toDate={booking.toDate}
|
|
totalPrice={totalPrice}
|
|
vat={vat}
|
|
isCampaignRate={!!containsCampaignRate}
|
|
hotelOffersBreakfast={hotelOffersBreakfast}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{rooms.length === 1 && (isAllCampaignRate || booking.bookingCode) && (
|
|
<BookingCodeChip
|
|
isCampaign={isAllCampaignRate}
|
|
bookingCode={booking.bookingCode}
|
|
alignCenter
|
|
/>
|
|
)}
|
|
<Divider className={styles.bottomDivider} color="Border/Divider/Subtle" />
|
|
{showSignupPromo && roomOneMemberPrice && !isUserLoggedIn ? (
|
|
<SignupPromoDesktop
|
|
memberPrice={roomOneMemberPrice}
|
|
badgeContent="✌️"
|
|
isEnterDetailsPage
|
|
/>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|