Merged in feat/SW-2079-update-booking-page-to-show-points- (pull request #1683)
feat: SW-2079 Show points in confirmation page * feat: SW-2079 Show points in confirmation page * feat: SW-2079 Optimized code * feat: SW-2079 Updated Body to Typography * feat: SW-2079 Multi-room total cost display * feat: SW-2079 Add reward nights condition rate title * feat: SW-2079 Removed extra checks * feat: SW-2079 Optimmized formatPrice function * feat: SW-2079 Typo fix Approved-by: Christian Andolf
This commit is contained in:
@@ -7,24 +7,20 @@ import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import styles from "./paymentDetails.module.css"
|
||||
|
||||
export default function PaymentDetails() {
|
||||
const intl = useIntl()
|
||||
|
||||
const { rooms, currencyCode } = useBookingConfirmationStore((state) => ({
|
||||
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
|
||||
(state) => ({
|
||||
rooms: state.rooms,
|
||||
currencyCode: state.currencyCode,
|
||||
}))
|
||||
formattedTotalCost: state.formattedTotalCost,
|
||||
})
|
||||
)
|
||||
|
||||
const hasAllRoomsLoaded = rooms.every((room) => room)
|
||||
const grandTotal = rooms.reduce((acc, room) => {
|
||||
const reservationTotalPrice = room?.totalPrice || 0
|
||||
return acc + reservationTotalPrice
|
||||
}, 0)
|
||||
|
||||
return (
|
||||
<div className={styles.details}>
|
||||
<Subtitle color="uiTextHighContrast" type="two">
|
||||
@@ -33,11 +29,12 @@ export default function PaymentDetails() {
|
||||
<div className={styles.payment}>
|
||||
{hasAllRoomsLoaded ? (
|
||||
<Body color="uiTextHighContrast">
|
||||
{`${intl.formatMessage({ id: "Total cost" })}: ${formatPrice(
|
||||
intl,
|
||||
grandTotal,
|
||||
currencyCode
|
||||
)}`}
|
||||
{intl.formatMessage(
|
||||
{ id: "Total cost: {amount}" },
|
||||
{
|
||||
amount: formattedTotalCost,
|
||||
}
|
||||
)}
|
||||
</Body>
|
||||
) : (
|
||||
<SkeletonShimmer width={"100%"} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
@@ -61,14 +62,24 @@ function TableSectionHeader({
|
||||
export default function PriceDetailsModal() {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { rooms, currencyCode, vat, fromDate, toDate, bookingCode } =
|
||||
useBookingConfirmationStore((state) => ({
|
||||
const {
|
||||
rooms,
|
||||
currencyCode,
|
||||
vat,
|
||||
fromDate,
|
||||
toDate,
|
||||
bookingCode,
|
||||
isVatCurrency,
|
||||
formattedTotalCost,
|
||||
} = useBookingConfirmationStore((state) => ({
|
||||
rooms: state.rooms,
|
||||
currencyCode: state.currencyCode,
|
||||
vat: state.vat,
|
||||
fromDate: state.fromDate,
|
||||
toDate: state.toDate,
|
||||
bookingCode: state.bookingCode,
|
||||
isVatCurrency: state.isVatCurrency,
|
||||
formattedTotalCost: state.formattedTotalCost,
|
||||
}))
|
||||
|
||||
if (!rooms[0]) {
|
||||
@@ -149,7 +160,7 @@ export default function PriceDetailsModal() {
|
||||
<Row
|
||||
bold
|
||||
label={intl.formatMessage({ id: "Room charge" })}
|
||||
value={formatPrice(intl, room.roomPrice, currencyCode)}
|
||||
value={room.formattedTotalCost}
|
||||
/>
|
||||
</TableSection>
|
||||
|
||||
@@ -200,6 +211,8 @@ export default function PriceDetailsModal() {
|
||||
})}
|
||||
<TableSection>
|
||||
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
||||
{isVatCurrency ? (
|
||||
<>
|
||||
<Row
|
||||
label={intl.formatMessage({ id: "Price excluding VAT" })}
|
||||
value={formatPrice(intl, bookingTotal.priceExVat, currencyCode)}
|
||||
@@ -208,16 +221,18 @@ export default function PriceDetailsModal() {
|
||||
label={intl.formatMessage({ id: "VAT {vat}%" }, { vat })}
|
||||
value={formatPrice(intl, bookingTotal.vatAmount, currencyCode)}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<tr className={styles.row}>
|
||||
<td>
|
||||
<Body textTransform="bold">
|
||||
{intl.formatMessage({ id: "Price including VAT" })}
|
||||
</Body>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span>{intl.formatMessage({ id: "Price including VAT" })}</span>
|
||||
</Typography>
|
||||
</td>
|
||||
<td className={styles.price}>
|
||||
<Body textTransform="bold">
|
||||
{formatPrice(intl, bookingTotal.price, currencyCode)}
|
||||
</Body>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span>{formattedTotalCost}</span>
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
{bookingCode && (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { CancellationRuleEnum, ChildBedTypeEnum } from "@/constants/booking"
|
||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
@@ -10,8 +11,6 @@ import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
import Modal from "@/components/Modal"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import RoomSkeletonLoader from "./RoomSkeletonLoader"
|
||||
@@ -45,29 +44,37 @@ export default function ReceiptRoom({
|
||||
return (
|
||||
<article className={styles.room}>
|
||||
<header className={styles.roomHeader}>
|
||||
<Body color="uiTextHighContrast">{room.name}</Body>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>{room.name}</p>
|
||||
</Typography>
|
||||
{room.rateDefinition.isMemberRate ? (
|
||||
<div className={styles.memberPrice}>
|
||||
<Body color="red">
|
||||
{formatPrice(intl, room.roomPrice, currencyCode)}
|
||||
</Body>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.red}>{room.formattedTotalCost}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : (
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, room.roomPrice, currencyCode)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
)}
|
||||
<Caption color="uiTextMediumContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextMediumContrast}>
|
||||
{intl.formatMessage(
|
||||
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||
{
|
||||
totalAdults: room.adults,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextMediumContrast}>
|
||||
{room.rateDefinition.cancellationText}
|
||||
</Caption>
|
||||
</p>
|
||||
</Typography>
|
||||
<Modal
|
||||
trigger={
|
||||
<Button intent="text" className={styles.termsLink}>
|
||||
@@ -83,7 +90,11 @@ export default function ReceiptRoom({
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
title={room.rateDefinition.cancellationText || ""}
|
||||
title={
|
||||
(room.roomPoints
|
||||
? room.rateDefinition.title
|
||||
: room.rateDefinition.cancellationText) || ""
|
||||
}
|
||||
subtitle={
|
||||
room.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM
|
||||
@@ -93,11 +104,12 @@ export default function ReceiptRoom({
|
||||
>
|
||||
<div className={styles.terms}>
|
||||
{room.rateDefinition.generalTerms?.map((info) => (
|
||||
<Body
|
||||
<Typography
|
||||
key={info}
|
||||
color="uiTextHighContrast"
|
||||
className={styles.termsText}
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
>
|
||||
<span>
|
||||
<MaterialIcon
|
||||
icon="check"
|
||||
color="Icon/Feedback/Success"
|
||||
@@ -105,7 +117,8 @@ export default function ReceiptRoom({
|
||||
className={styles.termsIcon}
|
||||
/>
|
||||
{info}
|
||||
</Body>
|
||||
</span>
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -114,70 +127,98 @@ export default function ReceiptRoom({
|
||||
? room.roomFeatures.map((feature) => (
|
||||
<div className={styles.entry} key={feature.code}>
|
||||
<div>
|
||||
<Body color="uiTextHighContrast">{feature.description}</Body>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, feature.totalPrice, feature.currency)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div className={styles.entry}>
|
||||
<Body color="uiTextHighContrast">{room.bedDescription}</Body>
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>{room.bedDescription}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{childBedCrib ? (
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{intl.formatMessage(
|
||||
{ id: "Crib (child) × {count}" },
|
||||
{ count: childBedCrib.quantity }
|
||||
)}
|
||||
</Body>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{intl.formatMessage({ id: "Based on availability" })}
|
||||
</Caption>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{childBedExtraBed ? (
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{intl.formatMessage(
|
||||
{ id: "Extra bed (child) × {count}" },
|
||||
{
|
||||
count: childBedExtraBed.quantity,
|
||||
}
|
||||
)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{room.breakfast || breakfastIncluded ? (
|
||||
<div className={styles.entry}>
|
||||
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>{intl.formatMessage({ id: "Breakfast buffet" })}</p>
|
||||
</Typography>
|
||||
{breakfastIncluded ? (
|
||||
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.red}>
|
||||
{intl.formatMessage({ id: "Included" })}
|
||||
</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
{room.breakfast && !breakfastIncluded ? (
|
||||
<Body color="uiTextHighContrast">
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
room.breakfast.totalPrice,
|
||||
room.breakfast.currency
|
||||
)}
|
||||
</Body>
|
||||
</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -40,3 +40,15 @@
|
||||
.terms .termsIcon {
|
||||
padding-right: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
|
||||
.uiTextMediumContrast {
|
||||
color: var(--UI-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import PriceDetailsModal from "../../PriceDetailsModal"
|
||||
|
||||
@@ -15,29 +15,27 @@ import styles from "./totalPrice.module.css"
|
||||
|
||||
export default function TotalPrice() {
|
||||
const intl = useIntl()
|
||||
const { rooms, currencyCode } = useBookingConfirmationStore((state) => ({
|
||||
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
|
||||
(state) => ({
|
||||
rooms: state.rooms,
|
||||
currencyCode: state.currencyCode,
|
||||
}))
|
||||
formattedTotalCost: state.formattedTotalCost,
|
||||
})
|
||||
)
|
||||
|
||||
const hasAllRoomsLoaded = rooms.every((room) => room)
|
||||
const grandTotal = rooms.reduce((acc, room) => {
|
||||
const reservationTotalPrice = room?.totalPrice || 0
|
||||
return acc + reservationTotalPrice
|
||||
}, 0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider color="primaryLightSubtle" />
|
||||
<div className={styles.price}>
|
||||
<div className={styles.entry}>
|
||||
<Body textTransform="bold">
|
||||
{intl.formatMessage({ id: "Total price" })}
|
||||
</Body>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>{intl.formatMessage({ id: "Total price" })}</p>
|
||||
</Typography>
|
||||
{hasAllRoomsLoaded ? (
|
||||
<Body textTransform="bold">
|
||||
{formatPrice(intl, grandTotal, currencyCode)}
|
||||
</Body>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>{formattedTotalCost}</p>
|
||||
</Typography>
|
||||
) : (
|
||||
<SkeletonShimmer width={"25%"} />
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import { mapRoomState } from "../../utils"
|
||||
import Room from "../Room"
|
||||
@@ -25,14 +27,36 @@ export function LinkedReservation({
|
||||
confirmationNumber,
|
||||
lang,
|
||||
})
|
||||
const setRoom = useBookingConfirmationStore((state) => state.actions.setRoom)
|
||||
const { setRoom, setFormattedTotalCost, currencyCode, totalBookingPrice } =
|
||||
useBookingConfirmationStore((state) => ({
|
||||
setRoom: state.actions.setRoom,
|
||||
setFormattedTotalCost: state.actions.setFormattedTotalCost,
|
||||
currencyCode: state.currencyCode,
|
||||
totalBookingPrice: state.totalBookingPrice,
|
||||
}))
|
||||
const intl = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.room) {
|
||||
const roomData = mapRoomState(data.booking, data.room)
|
||||
const roomData = mapRoomState(data.booking, data.room, intl)
|
||||
setRoom(roomData, roomIndex)
|
||||
|
||||
const formattedTotalCost = formatPrice(
|
||||
intl,
|
||||
totalBookingPrice,
|
||||
currencyCode
|
||||
)
|
||||
setFormattedTotalCost(formattedTotalCost)
|
||||
}
|
||||
}, [data, roomIndex, setRoom])
|
||||
}, [
|
||||
data,
|
||||
roomIndex,
|
||||
setRoom,
|
||||
intl,
|
||||
totalBookingPrice,
|
||||
currencyCode,
|
||||
setFormattedTotalCost,
|
||||
])
|
||||
|
||||
if (isLoading) {
|
||||
return <LinkedReservationCardSkeleton />
|
||||
|
||||
@@ -10,6 +10,7 @@ import Receipt from "@/components/HotelReservation/BookingConfirmation/Receipt"
|
||||
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
|
||||
import SidePanel from "@/components/HotelReservation/SidePanel"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import { getIntl } from "@/i18n"
|
||||
import BookingConfirmationProvider from "@/providers/BookingConfirmationProvider"
|
||||
|
||||
import Alerts from "./Alerts"
|
||||
@@ -38,6 +39,7 @@ export default async function BookingConfirmation({
|
||||
`${booking.confirmationNumber},${booking.guest.lastName}`
|
||||
)
|
||||
|
||||
const intl = await getIntl()
|
||||
return (
|
||||
<BookingConfirmationProvider
|
||||
bookingCode={booking.bookingCode}
|
||||
@@ -45,7 +47,7 @@ export default async function BookingConfirmation({
|
||||
fromDate={booking.checkInDate}
|
||||
toDate={booking.checkOutDate}
|
||||
rooms={[
|
||||
mapRoomState(booking, room),
|
||||
mapRoomState(booking, room, intl),
|
||||
// null represents "known but not yet fetched rooms" and is used to render placeholders correctly
|
||||
...Array(booking.linkedReservations.length).fill(null),
|
||||
]}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
import type { BookingConfirmationRoom } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
|
||||
|
||||
export function mapRoomState(
|
||||
booking: BookingConfirmationSchema,
|
||||
room: BookingConfirmationRoom
|
||||
room: BookingConfirmationRoom,
|
||||
intl: IntlShape
|
||||
) {
|
||||
const breakfast = booking.packages.find(
|
||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||
@@ -13,6 +19,21 @@ export function mapRoomState(
|
||||
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||
)
|
||||
|
||||
let formattedTotalCost = formatPrice(
|
||||
intl,
|
||||
booking.totalPrice,
|
||||
booking.currencyCode
|
||||
)
|
||||
if (booking.roomPoints) {
|
||||
formattedTotalCost = formatPrice(
|
||||
intl,
|
||||
booking.roomPoints,
|
||||
CurrencyEnum.POINTS,
|
||||
booking.totalPrice,
|
||||
booking.currencyCode
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
adults: booking.adults,
|
||||
bedDescription: room.bedType.description,
|
||||
@@ -23,11 +44,13 @@ export function mapRoomState(
|
||||
childBedPreferences: booking.childBedPreferences,
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
currencyCode: booking.currencyCode,
|
||||
formattedTotalCost,
|
||||
fromDate: booking.checkInDate,
|
||||
name: room.name,
|
||||
packages: booking.packages,
|
||||
rateDefinition: booking.rateDefinition,
|
||||
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
||||
roomPoints: booking.roomPoints,
|
||||
roomPrice: booking.roomPrice,
|
||||
roomTypeCode: booking.roomTypeCode,
|
||||
toDate: booking.checkOutDate,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { formId } from "@/components/HotelReservation/EnterDetails/Payment/Payme
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { formatPriceWithAdditionalPrice } from "@/utils/numberFormatting"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import styles from "./bottomSheet.module.css"
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
||||
>
|
||||
<Caption>{intl.formatMessage({ id: "Total price" })}</Caption>
|
||||
<Subtitle>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPrice.local.price,
|
||||
totalPrice.local.currency,
|
||||
|
||||
@@ -16,10 +16,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import {
|
||||
formatPrice,
|
||||
formatPriceWithAdditionalPrice,
|
||||
} from "@/utils/numberFormatting"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import PriceDetailsTable from "./PriceDetailsTable"
|
||||
|
||||
@@ -202,7 +199,7 @@ export default function SummaryUI({
|
||||
memberPrice.amount,
|
||||
memberPrice.currency
|
||||
)
|
||||
: formatPriceWithAdditionalPrice(
|
||||
: formatPrice(
|
||||
intl,
|
||||
room.roomPrice.perStay.local.price,
|
||||
room.roomPrice.perStay.local.currency,
|
||||
@@ -419,7 +416,7 @@ export default function SummaryUI({
|
||||
</div>
|
||||
<div>
|
||||
<Body textTransform="bold" data-testid="total-price">
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPrice.local.price,
|
||||
totalPrice.local.currency,
|
||||
@@ -444,9 +441,7 @@ export default function SummaryUI({
|
||||
value: formatPrice(
|
||||
intl,
|
||||
totalPrice.requested.price,
|
||||
totalPrice.requested.currency,
|
||||
totalPrice.requested.additionalPrice,
|
||||
totalPrice.requested.additionalPriceCurrency
|
||||
totalPrice.requested.currency
|
||||
),
|
||||
}
|
||||
)}
|
||||
|
||||
@@ -15,10 +15,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import {
|
||||
formatPrice,
|
||||
formatPriceWithAdditionalPrice,
|
||||
} from "@/utils/numberFormatting"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import { isBookingCodeRate } from "./isBookingCodeRate"
|
||||
import PriceDetailsTable from "./PriceDetailsTable"
|
||||
@@ -160,7 +157,7 @@ export default function Summary({
|
||||
<div className={styles.entry}>
|
||||
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
||||
<Body color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
room.roomPrice.perStay.local.price,
|
||||
room.roomPrice.perStay.local.currency,
|
||||
@@ -280,7 +277,7 @@ export default function Summary({
|
||||
textTransform="bold"
|
||||
data-testid="total-price"
|
||||
>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPrice.local.price,
|
||||
totalPrice.local.currency,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useRatesStore } from "@/stores/select-rate"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { formatPriceWithAdditionalPrice } from "@/utils/numberFormatting"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import { isBookingCodeRate } from "./isBookingCodeRate"
|
||||
import { mapRate } from "./mapRate"
|
||||
@@ -107,7 +107,7 @@ export default function MobileSummary({
|
||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||
className={styles.wrappedText}
|
||||
>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.local.price,
|
||||
totalPriceToShow.local.currency,
|
||||
|
||||
@@ -13,10 +13,7 @@ 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,
|
||||
formatPriceWithAdditionalPrice,
|
||||
} from "@/utils/numberFormatting"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import MobileSummary from "./MobileSummary"
|
||||
import {
|
||||
@@ -299,7 +296,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||
textAlign="right"
|
||||
>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.local.price,
|
||||
totalPriceToShow.local.currency,
|
||||
@@ -342,7 +339,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
||||
{intl.formatMessage({ id: "Total price" })}
|
||||
</Caption>
|
||||
<Subtitle color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||
{formatPriceWithAdditionalPrice(
|
||||
{formatPrice(
|
||||
intl,
|
||||
totalPriceToShow.local.price,
|
||||
totalPriceToShow.local.currency,
|
||||
|
||||
@@ -806,7 +806,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Total",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total paid": "Total betalt",
|
||||
"Total points": "Samlet antal point",
|
||||
"Total price": "Samlet pris",
|
||||
|
||||
@@ -804,7 +804,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Gesamt",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total paid": "Gesamt bezahlt",
|
||||
"Total points": "Gesamtpunktzahl",
|
||||
"Total price": "Gesamtpreis",
|
||||
|
||||
@@ -806,7 +806,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Total",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total paid": "Total paid",
|
||||
"Total points": "Total points",
|
||||
"Total price": "Total price",
|
||||
|
||||
@@ -804,7 +804,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Kokonais",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total paid": "Kokonais maksamasi",
|
||||
"Total points": "Kokonaispisteet",
|
||||
"Total price": "Kokonaishinta",
|
||||
|
||||
@@ -801,7 +801,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For å sikre din reservasjon, ber vi om at du gir oss dine betalingskortdetaljer. Vær sikker på at ingen gebyrer vil bli belastet på dette tidspunktet.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Total",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total incl VAT": "Sum inkl mva",
|
||||
"Total paid": "Total betalt",
|
||||
"Total points": "Totale poeng",
|
||||
@@ -877,7 +877,7 @@
|
||||
"When": "Når",
|
||||
"When guaranteeing your booking with a credit card, we will hold the booking until 07:00 the day after check-in.": "Når vi garanterer bestillingen med kredittkort, vil vi holde bestillingen til kl. 07.00 dagen etter innsjekking.",
|
||||
"When guaranteeing your booking, we will hold the booking until 07:00 until the day after check-in. This will provide you as a guest with added flexibility for check-in times.": "Når du garanterer din reservasjon, vil vi holde reservasjonen til 07:00 til dagen etter check-in. Dette vil gi deg som gjest tilføjet fleksibilitet for check-in-tider.",
|
||||
"When you confirm the booking the room will be guaranteed for late arrival. If you fail to arrive without cancelling in advance or if you cancel after 18:00 local time, you will be charged for one reward night.": "",
|
||||
"When you confirm the booking the room will be guaranteed for late arrival. If you fail to arrive without cancelling in advance or if you cancel after 18:00 local time, you will be charged for one reward night.": "When you confirm the booking the room will be guaranteed for late arrival. If you fail to arrive without cancelling in advance or if you cancel after 18:00 local time, you will be charged for one reward night.",
|
||||
"Where should you go next?": "Hvor ønsker du å reise neste gang?",
|
||||
"Where to?": "Hvor skal du?",
|
||||
"Which room class suits you the best?": "Hvilken romklasse passer deg best?",
|
||||
|
||||
@@ -802,7 +802,7 @@
|
||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "För att säkra din bokning ber vi om att du ger oss dina betalkortdetaljer. Välj säker på att ingen avgifter kommer att debiteras just nu.",
|
||||
"Too many failed attempts.": "Too many failed attempts.",
|
||||
"Total": "Totalt",
|
||||
"Total cost": "Total cost",
|
||||
"Total cost: {amount}": "Total cost: {amount}",
|
||||
"Total incl VAT": "Totalt inkl moms",
|
||||
"Total paid": "Total betalt",
|
||||
"Total points": "Poäng totalt",
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { useRef } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { createBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
|
||||
import { BookingConfirmationContext } from "@/contexts/BookingConfirmation"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import type { BookingConfirmationStore } from "@/types/contexts/booking-confirmation"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
import type { BookingConfirmationProviderProps } from "@/types/providers/booking-confirmation"
|
||||
|
||||
export default function BookingConfirmationProvider({
|
||||
@@ -18,9 +21,29 @@ export default function BookingConfirmationProvider({
|
||||
rooms,
|
||||
vat,
|
||||
}: BookingConfirmationProviderProps) {
|
||||
const intl = useIntl()
|
||||
const storeRef = useRef<BookingConfirmationStore>()
|
||||
|
||||
if (!storeRef.current) {
|
||||
const totalBookingPrice = rooms.reduce((acc, room) => {
|
||||
const reservationTotalPrice = room?.totalPrice || 0
|
||||
return acc + reservationTotalPrice
|
||||
}, 0)
|
||||
let formattedTotalCost = formatPrice(intl, totalBookingPrice, currencyCode)
|
||||
const totalBookingPoints = rooms.reduce((acc, room) => {
|
||||
return acc + (room?.roomPoints ?? 0)
|
||||
}, 0)
|
||||
let isVatCurrency = true
|
||||
if (totalBookingPoints) {
|
||||
isVatCurrency = false
|
||||
formattedTotalCost = formatPrice(
|
||||
intl,
|
||||
totalBookingPoints,
|
||||
CurrencyEnum.POINTS,
|
||||
totalBookingPrice,
|
||||
currencyCode
|
||||
)
|
||||
}
|
||||
const initialData = {
|
||||
bookingCode,
|
||||
currencyCode,
|
||||
@@ -28,6 +51,9 @@ export default function BookingConfirmationProvider({
|
||||
toDate,
|
||||
rooms,
|
||||
vat,
|
||||
isVatCurrency,
|
||||
formattedTotalCost,
|
||||
totalBookingPrice,
|
||||
}
|
||||
|
||||
storeRef.current = createBookingConfirmationStore(initialData)
|
||||
|
||||
@@ -16,13 +16,24 @@ export function createBookingConfirmationStore(initialState: InitialState) {
|
||||
fromDate: initialState.fromDate,
|
||||
toDate: initialState.toDate,
|
||||
vat: initialState.vat,
|
||||
formattedTotalCost: initialState.formattedTotalCost,
|
||||
isVatCurrency: initialState.isVatCurrency,
|
||||
totalBookingPrice: initialState.totalBookingPrice,
|
||||
actions: {
|
||||
setRoom: (room, idx) => {
|
||||
set((state) => {
|
||||
const rooms = [...state.rooms]
|
||||
rooms[idx] = room
|
||||
const totalBookingPrice = rooms.reduce((acc, room) => {
|
||||
return acc + (room?.totalPrice ?? 0)
|
||||
}, 0)
|
||||
|
||||
return { rooms }
|
||||
return { rooms, totalBookingPrice }
|
||||
})
|
||||
},
|
||||
setFormattedTotalCost: (updatedFormattedTotalCost: string) => {
|
||||
set((state) => {
|
||||
return { ...state, formattedTotalCost: updatedFormattedTotalCost }
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@ export const useMyStayTotalPriceStore = create<MyStayTotalPriceState>(
|
||||
}, 0)
|
||||
|
||||
const totalPoints = newRooms.reduce((sum, r) => {
|
||||
return sum + r.roomPoints
|
||||
return sum + (r.roomPoints ?? 0)
|
||||
}, 0)
|
||||
|
||||
return {
|
||||
|
||||
@@ -22,8 +22,10 @@ export interface Room {
|
||||
fromDate: Date
|
||||
name: string
|
||||
packages: BookingConfirmation["booking"]["packages"]
|
||||
formattedTotalCost: string
|
||||
rateDefinition: BookingConfirmation["booking"]["rateDefinition"]
|
||||
roomFeatures?: PackageSchema[] | null
|
||||
roomPoints: number
|
||||
roomPrice: number
|
||||
roomTypeCode: string | null
|
||||
toDate: Date
|
||||
@@ -39,14 +41,23 @@ export interface InitialState {
|
||||
toDate: Date
|
||||
currencyCode: string
|
||||
vat: number
|
||||
isVatCurrency: boolean
|
||||
formattedTotalCost: string
|
||||
totalBookingPrice: number
|
||||
}
|
||||
|
||||
export interface BookingConfirmationState {
|
||||
bookingCode: string | null
|
||||
isVatCurrency: boolean
|
||||
rooms: (Room | null)[]
|
||||
currencyCode: string
|
||||
vat: number
|
||||
fromDate: Date
|
||||
toDate: Date
|
||||
actions: { setRoom: (room: Room, idx: number) => void }
|
||||
formattedTotalCost: string | null
|
||||
totalBookingPrice: number
|
||||
actions: {
|
||||
setRoom: (room: Room, idx: number) => void
|
||||
setFormattedTotalCost: (updatedFormattedTotalCost: string) => void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ export function getSingleDecimal(n: Number | string) {
|
||||
* @param intl - react-intl object
|
||||
* @param price - number to be formatted
|
||||
* @param currency - currency code
|
||||
* @param additionalPrice - number (obtained in reward nights and Corporate cheque scenarios)
|
||||
* @param additionalPriceCurrency - currency code (obtained in reward nights and Corporate cheque scenarios)
|
||||
* @returns localized and formatted number in string type with currency
|
||||
*/
|
||||
export function formatPrice(
|
||||
@@ -26,21 +28,14 @@ export function formatPrice(
|
||||
const localizedPrice = intl.formatNumber(price, {
|
||||
minimumFractionDigits: 0,
|
||||
})
|
||||
return `${localizedPrice} ${currency} ${additionalPrice ? "+ " + additionalPrice + " " + additionalPriceCurrency : ""}`
|
||||
}
|
||||
|
||||
// This will handle redemption and bonus cheque (corporate cheque) scneario with partial payments
|
||||
export function formatPriceWithAdditionalPrice(
|
||||
intl: IntlShape,
|
||||
points: number,
|
||||
pointsCurrency: string,
|
||||
additionalPrice?: number,
|
||||
additionalPriceCurrency?: string
|
||||
) {
|
||||
const formattedAdditionalPrice =
|
||||
additionalPrice && additionalPriceCurrency
|
||||
? `+ ${formatPrice(intl, additionalPrice, additionalPriceCurrency)}`
|
||||
: ""
|
||||
let formattedAdditionalPrice: string = ""
|
||||
if (additionalPrice && additionalPriceCurrency) {
|
||||
const localizedAdditionalPrice = intl.formatNumber(additionalPrice, {
|
||||
minimumFractionDigits: 0,
|
||||
})
|
||||
formattedAdditionalPrice = ` ${localizedAdditionalPrice} ${additionalPriceCurrency}`
|
||||
}
|
||||
|
||||
return `${formatPrice(intl, points, pointsCurrency)} ${formattedAdditionalPrice}`
|
||||
return `${localizedPrice} ${currency}${formattedAdditionalPrice}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user