Files
web/packages/booking-flow/lib/components/EnterDetails/Summary/UI/index.tsx
Bianca Widstam 200cf3f617 Merged in fix/BOOK-584-red-price-strikethrough (pull request #3298)
fix(BOOK-584): show discounted if specialrate or member

* fix(BOOK-584): show discounted if specialrate or member


Approved-by: Anton Gunnarsson
2025-12-08 14:23:44 +00:00

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>
)
}