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 SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import styles from "./paymentDetails.module.css"
|
import styles from "./paymentDetails.module.css"
|
||||||
|
|
||||||
export default function PaymentDetails() {
|
export default function PaymentDetails() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const { rooms, currencyCode } = useBookingConfirmationStore((state) => ({
|
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
|
||||||
|
(state) => ({
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
currencyCode: state.currencyCode,
|
formattedTotalCost: state.formattedTotalCost,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const hasAllRoomsLoaded = rooms.every((room) => room)
|
const hasAllRoomsLoaded = rooms.every((room) => room)
|
||||||
const grandTotal = rooms.reduce((acc, room) => {
|
|
||||||
const reservationTotalPrice = room?.totalPrice || 0
|
|
||||||
return acc + reservationTotalPrice
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.details}>
|
<div className={styles.details}>
|
||||||
<Subtitle color="uiTextHighContrast" type="two">
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
@@ -33,11 +29,12 @@ export default function PaymentDetails() {
|
|||||||
<div className={styles.payment}>
|
<div className={styles.payment}>
|
||||||
{hasAllRoomsLoaded ? (
|
{hasAllRoomsLoaded ? (
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${intl.formatMessage({ id: "Total cost" })}: ${formatPrice(
|
{intl.formatMessage(
|
||||||
intl,
|
{ id: "Total cost: {amount}" },
|
||||||
grandTotal,
|
{
|
||||||
currencyCode
|
amount: formattedTotalCost,
|
||||||
)}`}
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
) : (
|
) : (
|
||||||
<SkeletonShimmer width={"100%"} />
|
<SkeletonShimmer width={"100%"} />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from "react"
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
@@ -61,14 +62,24 @@ function TableSectionHeader({
|
|||||||
export default function PriceDetailsModal() {
|
export default function PriceDetailsModal() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { rooms, currencyCode, vat, fromDate, toDate, bookingCode } =
|
const {
|
||||||
useBookingConfirmationStore((state) => ({
|
rooms,
|
||||||
|
currencyCode,
|
||||||
|
vat,
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
bookingCode,
|
||||||
|
isVatCurrency,
|
||||||
|
formattedTotalCost,
|
||||||
|
} = useBookingConfirmationStore((state) => ({
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
currencyCode: state.currencyCode,
|
currencyCode: state.currencyCode,
|
||||||
vat: state.vat,
|
vat: state.vat,
|
||||||
fromDate: state.fromDate,
|
fromDate: state.fromDate,
|
||||||
toDate: state.toDate,
|
toDate: state.toDate,
|
||||||
bookingCode: state.bookingCode,
|
bookingCode: state.bookingCode,
|
||||||
|
isVatCurrency: state.isVatCurrency,
|
||||||
|
formattedTotalCost: state.formattedTotalCost,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (!rooms[0]) {
|
if (!rooms[0]) {
|
||||||
@@ -149,7 +160,7 @@ export default function PriceDetailsModal() {
|
|||||||
<Row
|
<Row
|
||||||
bold
|
bold
|
||||||
label={intl.formatMessage({ id: "Room charge" })}
|
label={intl.formatMessage({ id: "Room charge" })}
|
||||||
value={formatPrice(intl, room.roomPrice, currencyCode)}
|
value={room.formattedTotalCost}
|
||||||
/>
|
/>
|
||||||
</TableSection>
|
</TableSection>
|
||||||
|
|
||||||
@@ -200,6 +211,8 @@ export default function PriceDetailsModal() {
|
|||||||
})}
|
})}
|
||||||
<TableSection>
|
<TableSection>
|
||||||
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
||||||
|
{isVatCurrency ? (
|
||||||
|
<>
|
||||||
<Row
|
<Row
|
||||||
label={intl.formatMessage({ id: "Price excluding VAT" })}
|
label={intl.formatMessage({ id: "Price excluding VAT" })}
|
||||||
value={formatPrice(intl, bookingTotal.priceExVat, currencyCode)}
|
value={formatPrice(intl, bookingTotal.priceExVat, currencyCode)}
|
||||||
@@ -208,16 +221,18 @@ export default function PriceDetailsModal() {
|
|||||||
label={intl.formatMessage({ id: "VAT {vat}%" }, { vat })}
|
label={intl.formatMessage({ id: "VAT {vat}%" }, { vat })}
|
||||||
value={formatPrice(intl, bookingTotal.vatAmount, currencyCode)}
|
value={formatPrice(intl, bookingTotal.vatAmount, currencyCode)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<tr className={styles.row}>
|
<tr className={styles.row}>
|
||||||
<td>
|
<td>
|
||||||
<Body textTransform="bold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
{intl.formatMessage({ id: "Price including VAT" })}
|
<span>{intl.formatMessage({ id: "Price including VAT" })}</span>
|
||||||
</Body>
|
</Typography>
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.price}>
|
<td className={styles.price}>
|
||||||
<Body textTransform="bold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
{formatPrice(intl, bookingTotal.price, currencyCode)}
|
<span>{formattedTotalCost}</span>
|
||||||
</Body>
|
</Typography>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{bookingCode && (
|
{bookingCode && (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { CancellationRuleEnum, ChildBedTypeEnum } from "@/constants/booking"
|
import { CancellationRuleEnum, ChildBedTypeEnum } from "@/constants/booking"
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
@@ -10,8 +11,6 @@ import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
|||||||
import Modal from "@/components/Modal"
|
import Modal from "@/components/Modal"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
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 { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import RoomSkeletonLoader from "./RoomSkeletonLoader"
|
import RoomSkeletonLoader from "./RoomSkeletonLoader"
|
||||||
@@ -45,29 +44,37 @@ export default function ReceiptRoom({
|
|||||||
return (
|
return (
|
||||||
<article className={styles.room}>
|
<article className={styles.room}>
|
||||||
<header className={styles.roomHeader}>
|
<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 ? (
|
{room.rateDefinition.isMemberRate ? (
|
||||||
<div className={styles.memberPrice}>
|
<div className={styles.memberPrice}>
|
||||||
<Body color="red">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
{formatPrice(intl, room.roomPrice, currencyCode)}
|
<p className={styles.red}>{room.formattedTotalCost}</p>
|
||||||
</Body>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(intl, room.roomPrice, currencyCode)}
|
{formatPrice(intl, room.roomPrice, currencyCode)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Caption color="uiTextMediumContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextMediumContrast}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
{
|
{
|
||||||
totalAdults: room.adults,
|
totalAdults: room.adults,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</p>
|
||||||
<Caption color="uiTextMediumContrast">
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextMediumContrast}>
|
||||||
{room.rateDefinition.cancellationText}
|
{room.rateDefinition.cancellationText}
|
||||||
</Caption>
|
</p>
|
||||||
|
</Typography>
|
||||||
<Modal
|
<Modal
|
||||||
trigger={
|
trigger={
|
||||||
<Button intent="text" className={styles.termsLink}>
|
<Button intent="text" className={styles.termsLink}>
|
||||||
@@ -83,7 +90,11 @@ export default function ReceiptRoom({
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
title={room.rateDefinition.cancellationText || ""}
|
title={
|
||||||
|
(room.roomPoints
|
||||||
|
? room.rateDefinition.title
|
||||||
|
: room.rateDefinition.cancellationText) || ""
|
||||||
|
}
|
||||||
subtitle={
|
subtitle={
|
||||||
room.rateDefinition.cancellationRule ===
|
room.rateDefinition.cancellationRule ===
|
||||||
CancellationRuleEnum.CancellableBefore6PM
|
CancellationRuleEnum.CancellableBefore6PM
|
||||||
@@ -93,11 +104,12 @@ export default function ReceiptRoom({
|
|||||||
>
|
>
|
||||||
<div className={styles.terms}>
|
<div className={styles.terms}>
|
||||||
{room.rateDefinition.generalTerms?.map((info) => (
|
{room.rateDefinition.generalTerms?.map((info) => (
|
||||||
<Body
|
<Typography
|
||||||
key={info}
|
key={info}
|
||||||
color="uiTextHighContrast"
|
|
||||||
className={styles.termsText}
|
className={styles.termsText}
|
||||||
|
variant="Body/Paragraph/mdRegular"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
icon="check"
|
icon="check"
|
||||||
color="Icon/Feedback/Success"
|
color="Icon/Feedback/Success"
|
||||||
@@ -105,7 +117,8 @@ export default function ReceiptRoom({
|
|||||||
className={styles.termsIcon}
|
className={styles.termsIcon}
|
||||||
/>
|
/>
|
||||||
{info}
|
{info}
|
||||||
</Body>
|
</span>
|
||||||
|
</Typography>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -114,70 +127,98 @@ export default function ReceiptRoom({
|
|||||||
? room.roomFeatures.map((feature) => (
|
? room.roomFeatures.map((feature) => (
|
||||||
<div className={styles.entry} key={feature.code}>
|
<div className={styles.entry} key={feature.code}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">{feature.description}</Body>
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(intl, feature.totalPrice, feature.currency)}
|
{formatPrice(intl, feature.totalPrice, feature.currency)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Body color="uiTextHighContrast">{room.bedDescription}</Body>
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<Body color="uiTextHighContrast">
|
<p className={styles.uiTextHighContrast}>{room.bedDescription}</p>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(intl, 0, currencyCode)}
|
{formatPrice(intl, 0, currencyCode)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
{childBedCrib ? (
|
{childBedCrib ? (
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Crib (child) × {count}" },
|
{ id: "Crib (child) × {count}" },
|
||||||
{ count: childBedCrib.quantity }
|
{ count: childBedCrib.quantity }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</p>
|
||||||
<Caption color="uiTextMediumContrast">
|
</Typography>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{intl.formatMessage({ id: "Based on availability" })}
|
{intl.formatMessage({ id: "Based on availability" })}
|
||||||
</Caption>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(intl, 0, currencyCode)}
|
{formatPrice(intl, 0, currencyCode)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{childBedExtraBed ? (
|
{childBedExtraBed ? (
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Extra bed (child) × {count}" },
|
{ id: "Extra bed (child) × {count}" },
|
||||||
{
|
{
|
||||||
count: childBedExtraBed.quantity,
|
count: childBedExtraBed.quantity,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(intl, 0, currencyCode)}
|
{formatPrice(intl, 0, currencyCode)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{room.breakfast || breakfastIncluded ? (
|
{room.breakfast || breakfastIncluded ? (
|
||||||
<div className={styles.entry}>
|
<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 ? (
|
{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}
|
) : null}
|
||||||
{room.breakfast && !breakfastIncluded ? (
|
{room.breakfast && !breakfastIncluded ? (
|
||||||
<Body color="uiTextHighContrast">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p className={styles.uiTextHighContrast}>
|
||||||
{formatPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
room.breakfast.totalPrice,
|
room.breakfast.totalPrice,
|
||||||
room.breakfast.currency
|
room.breakfast.currency
|
||||||
)}
|
)}
|
||||||
</Body>
|
</p>
|
||||||
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -40,3 +40,15 @@
|
|||||||
.terms .termsIcon {
|
.terms .termsIcon {
|
||||||
padding-right: var(--Spacing-x1);
|
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 { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
|
|
||||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import PriceDetailsModal from "../../PriceDetailsModal"
|
import PriceDetailsModal from "../../PriceDetailsModal"
|
||||||
|
|
||||||
@@ -15,29 +15,27 @@ import styles from "./totalPrice.module.css"
|
|||||||
|
|
||||||
export default function TotalPrice() {
|
export default function TotalPrice() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { rooms, currencyCode } = useBookingConfirmationStore((state) => ({
|
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
|
||||||
|
(state) => ({
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
currencyCode: state.currencyCode,
|
formattedTotalCost: state.formattedTotalCost,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const hasAllRoomsLoaded = rooms.every((room) => room)
|
const hasAllRoomsLoaded = rooms.every((room) => room)
|
||||||
const grandTotal = rooms.reduce((acc, room) => {
|
|
||||||
const reservationTotalPrice = room?.totalPrice || 0
|
|
||||||
return acc + reservationTotalPrice
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
<div className={styles.price}>
|
<div className={styles.price}>
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Body textTransform="bold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
{intl.formatMessage({ id: "Total price" })}
|
<p>{intl.formatMessage({ id: "Total price" })}</p>
|
||||||
</Body>
|
</Typography>
|
||||||
{hasAllRoomsLoaded ? (
|
{hasAllRoomsLoaded ? (
|
||||||
<Body textTransform="bold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
{formatPrice(intl, grandTotal, currencyCode)}
|
<p>{formattedTotalCost}</p>
|
||||||
</Body>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<SkeletonShimmer width={"25%"} />
|
<SkeletonShimmer width={"25%"} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import { mapRoomState } from "../../utils"
|
import { mapRoomState } from "../../utils"
|
||||||
import Room from "../Room"
|
import Room from "../Room"
|
||||||
@@ -25,14 +27,36 @@ export function LinkedReservation({
|
|||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
lang,
|
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(() => {
|
useEffect(() => {
|
||||||
if (data?.room) {
|
if (data?.room) {
|
||||||
const roomData = mapRoomState(data.booking, data.room)
|
const roomData = mapRoomState(data.booking, data.room, intl)
|
||||||
setRoom(roomData, roomIndex)
|
setRoom(roomData, roomIndex)
|
||||||
|
|
||||||
|
const formattedTotalCost = formatPrice(
|
||||||
|
intl,
|
||||||
|
totalBookingPrice,
|
||||||
|
currencyCode
|
||||||
|
)
|
||||||
|
setFormattedTotalCost(formattedTotalCost)
|
||||||
}
|
}
|
||||||
}, [data, roomIndex, setRoom])
|
}, [
|
||||||
|
data,
|
||||||
|
roomIndex,
|
||||||
|
setRoom,
|
||||||
|
intl,
|
||||||
|
totalBookingPrice,
|
||||||
|
currencyCode,
|
||||||
|
setFormattedTotalCost,
|
||||||
|
])
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LinkedReservationCardSkeleton />
|
return <LinkedReservationCardSkeleton />
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Receipt from "@/components/HotelReservation/BookingConfirmation/Receipt"
|
|||||||
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
|
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
|
||||||
import SidePanel from "@/components/HotelReservation/SidePanel"
|
import SidePanel from "@/components/HotelReservation/SidePanel"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
import BookingConfirmationProvider from "@/providers/BookingConfirmationProvider"
|
import BookingConfirmationProvider from "@/providers/BookingConfirmationProvider"
|
||||||
|
|
||||||
import Alerts from "./Alerts"
|
import Alerts from "./Alerts"
|
||||||
@@ -38,6 +39,7 @@ export default async function BookingConfirmation({
|
|||||||
`${booking.confirmationNumber},${booking.guest.lastName}`
|
`${booking.confirmationNumber},${booking.guest.lastName}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const intl = await getIntl()
|
||||||
return (
|
return (
|
||||||
<BookingConfirmationProvider
|
<BookingConfirmationProvider
|
||||||
bookingCode={booking.bookingCode}
|
bookingCode={booking.bookingCode}
|
||||||
@@ -45,7 +47,7 @@ export default async function BookingConfirmation({
|
|||||||
fromDate={booking.checkInDate}
|
fromDate={booking.checkInDate}
|
||||||
toDate={booking.checkOutDate}
|
toDate={booking.checkOutDate}
|
||||||
rooms={[
|
rooms={[
|
||||||
mapRoomState(booking, room),
|
mapRoomState(booking, room, intl),
|
||||||
// null represents "known but not yet fetched rooms" and is used to render placeholders correctly
|
// null represents "known but not yet fetched rooms" and is used to render placeholders correctly
|
||||||
...Array(booking.linkedReservations.length).fill(null),
|
...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 type { BookingConfirmationRoom } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
export function mapRoomState(
|
export function mapRoomState(
|
||||||
booking: BookingConfirmationSchema,
|
booking: BookingConfirmationSchema,
|
||||||
room: BookingConfirmationRoom
|
room: BookingConfirmationRoom,
|
||||||
|
intl: IntlShape
|
||||||
) {
|
) {
|
||||||
const breakfast = booking.packages.find(
|
const breakfast = booking.packages.find(
|
||||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||||
@@ -13,6 +19,21 @@ export function mapRoomState(
|
|||||||
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
(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 {
|
return {
|
||||||
adults: booking.adults,
|
adults: booking.adults,
|
||||||
bedDescription: room.bedType.description,
|
bedDescription: room.bedType.description,
|
||||||
@@ -23,11 +44,13 @@ export function mapRoomState(
|
|||||||
childBedPreferences: booking.childBedPreferences,
|
childBedPreferences: booking.childBedPreferences,
|
||||||
confirmationNumber: booking.confirmationNumber,
|
confirmationNumber: booking.confirmationNumber,
|
||||||
currencyCode: booking.currencyCode,
|
currencyCode: booking.currencyCode,
|
||||||
|
formattedTotalCost,
|
||||||
fromDate: booking.checkInDate,
|
fromDate: booking.checkInDate,
|
||||||
name: room.name,
|
name: room.name,
|
||||||
packages: booking.packages,
|
packages: booking.packages,
|
||||||
rateDefinition: booking.rateDefinition,
|
rateDefinition: booking.rateDefinition,
|
||||||
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
||||||
|
roomPoints: booking.roomPoints,
|
||||||
roomPrice: booking.roomPrice,
|
roomPrice: booking.roomPrice,
|
||||||
roomTypeCode: booking.roomTypeCode,
|
roomTypeCode: booking.roomTypeCode,
|
||||||
toDate: booking.checkOutDate,
|
toDate: booking.checkOutDate,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { formId } from "@/components/HotelReservation/EnterDetails/Payment/Payme
|
|||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { formatPriceWithAdditionalPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import styles from "./bottomSheet.module.css"
|
import styles from "./bottomSheet.module.css"
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
|||||||
>
|
>
|
||||||
<Caption>{intl.formatMessage({ id: "Total price" })}</Caption>
|
<Caption>{intl.formatMessage({ id: "Total price" })}</Caption>
|
||||||
<Subtitle>
|
<Subtitle>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPrice.local.price,
|
totalPrice.local.price,
|
||||||
totalPrice.local.currency,
|
totalPrice.local.currency,
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import {
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
formatPrice,
|
|
||||||
formatPriceWithAdditionalPrice,
|
|
||||||
} from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import PriceDetailsTable from "./PriceDetailsTable"
|
import PriceDetailsTable from "./PriceDetailsTable"
|
||||||
|
|
||||||
@@ -202,7 +199,7 @@ export default function SummaryUI({
|
|||||||
memberPrice.amount,
|
memberPrice.amount,
|
||||||
memberPrice.currency
|
memberPrice.currency
|
||||||
)
|
)
|
||||||
: formatPriceWithAdditionalPrice(
|
: formatPrice(
|
||||||
intl,
|
intl,
|
||||||
room.roomPrice.perStay.local.price,
|
room.roomPrice.perStay.local.price,
|
||||||
room.roomPrice.perStay.local.currency,
|
room.roomPrice.perStay.local.currency,
|
||||||
@@ -419,7 +416,7 @@ export default function SummaryUI({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Body textTransform="bold" data-testid="total-price">
|
<Body textTransform="bold" data-testid="total-price">
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPrice.local.price,
|
totalPrice.local.price,
|
||||||
totalPrice.local.currency,
|
totalPrice.local.currency,
|
||||||
@@ -444,9 +441,7 @@ export default function SummaryUI({
|
|||||||
value: formatPrice(
|
value: formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPrice.requested.price,
|
totalPrice.requested.price,
|
||||||
totalPrice.requested.currency,
|
totalPrice.requested.currency
|
||||||
totalPrice.requested.additionalPrice,
|
|
||||||
totalPrice.requested.additionalPriceCurrency
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import {
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
formatPrice,
|
|
||||||
formatPriceWithAdditionalPrice,
|
|
||||||
} from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import { isBookingCodeRate } from "./isBookingCodeRate"
|
import { isBookingCodeRate } from "./isBookingCodeRate"
|
||||||
import PriceDetailsTable from "./PriceDetailsTable"
|
import PriceDetailsTable from "./PriceDetailsTable"
|
||||||
@@ -160,7 +157,7 @@ export default function Summary({
|
|||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
||||||
<Body color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
<Body color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
room.roomPrice.perStay.local.price,
|
room.roomPrice.perStay.local.price,
|
||||||
room.roomPrice.perStay.local.currency,
|
room.roomPrice.perStay.local.currency,
|
||||||
@@ -280,7 +277,7 @@ export default function Summary({
|
|||||||
textTransform="bold"
|
textTransform="bold"
|
||||||
data-testid="total-price"
|
data-testid="total-price"
|
||||||
>
|
>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPrice.local.price,
|
totalPrice.local.price,
|
||||||
totalPrice.local.currency,
|
totalPrice.local.currency,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useRatesStore } from "@/stores/select-rate"
|
|||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { formatPriceWithAdditionalPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import { isBookingCodeRate } from "./isBookingCodeRate"
|
import { isBookingCodeRate } from "./isBookingCodeRate"
|
||||||
import { mapRate } from "./mapRate"
|
import { mapRate } from "./mapRate"
|
||||||
@@ -107,7 +107,7 @@ export default function MobileSummary({
|
|||||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||||
className={styles.wrappedText}
|
className={styles.wrappedText}
|
||||||
>
|
>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPriceToShow.local.price,
|
totalPriceToShow.local.price,
|
||||||
totalPriceToShow.local.currency,
|
totalPriceToShow.local.currency,
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import {
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
formatPrice,
|
|
||||||
formatPriceWithAdditionalPrice,
|
|
||||||
} from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import MobileSummary from "./MobileSummary"
|
import MobileSummary from "./MobileSummary"
|
||||||
import {
|
import {
|
||||||
@@ -299,7 +296,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
|||||||
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
color={showDiscounted ? "red" : "uiTextHighContrast"}
|
||||||
textAlign="right"
|
textAlign="right"
|
||||||
>
|
>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPriceToShow.local.price,
|
totalPriceToShow.local.price,
|
||||||
totalPriceToShow.local.currency,
|
totalPriceToShow.local.currency,
|
||||||
@@ -342,7 +339,7 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
|
|||||||
{intl.formatMessage({ id: "Total price" })}
|
{intl.formatMessage({ id: "Total price" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Subtitle color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
<Subtitle color={showDiscounted ? "red" : "uiTextHighContrast"}>
|
||||||
{formatPriceWithAdditionalPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
totalPriceToShow.local.price,
|
totalPriceToShow.local.price,
|
||||||
totalPriceToShow.local.currency,
|
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.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Total",
|
"Total": "Total",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total paid": "Total betalt",
|
"Total paid": "Total betalt",
|
||||||
"Total points": "Samlet antal point",
|
"Total points": "Samlet antal point",
|
||||||
"Total price": "Samlet pris",
|
"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.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Gesamt",
|
"Total": "Gesamt",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total paid": "Gesamt bezahlt",
|
"Total paid": "Gesamt bezahlt",
|
||||||
"Total points": "Gesamtpunktzahl",
|
"Total points": "Gesamtpunktzahl",
|
||||||
"Total price": "Gesamtpreis",
|
"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.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Total",
|
"Total": "Total",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total paid": "Total paid",
|
"Total paid": "Total paid",
|
||||||
"Total points": "Total points",
|
"Total points": "Total points",
|
||||||
"Total price": "Total price",
|
"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ä.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Kokonais",
|
"Total": "Kokonais",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total paid": "Kokonais maksamasi",
|
"Total paid": "Kokonais maksamasi",
|
||||||
"Total points": "Kokonaispisteet",
|
"Total points": "Kokonaispisteet",
|
||||||
"Total price": "Kokonaishinta",
|
"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.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Total",
|
"Total": "Total",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total incl VAT": "Sum inkl mva",
|
"Total incl VAT": "Sum inkl mva",
|
||||||
"Total paid": "Total betalt",
|
"Total paid": "Total betalt",
|
||||||
"Total points": "Totale poeng",
|
"Total points": "Totale poeng",
|
||||||
@@ -877,7 +877,7 @@
|
|||||||
"When": "Når",
|
"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 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 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 should you go next?": "Hvor ønsker du å reise neste gang?",
|
||||||
"Where to?": "Hvor skal du?",
|
"Where to?": "Hvor skal du?",
|
||||||
"Which room class suits you the best?": "Hvilken romklasse passer deg best?",
|
"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.",
|
"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.",
|
"Too many failed attempts.": "Too many failed attempts.",
|
||||||
"Total": "Totalt",
|
"Total": "Totalt",
|
||||||
"Total cost": "Total cost",
|
"Total cost: {amount}": "Total cost: {amount}",
|
||||||
"Total incl VAT": "Totalt inkl moms",
|
"Total incl VAT": "Totalt inkl moms",
|
||||||
"Total paid": "Total betalt",
|
"Total paid": "Total betalt",
|
||||||
"Total points": "Poäng totalt",
|
"Total points": "Poäng totalt",
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRef } from "react"
|
import { useRef } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { createBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { createBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
|
|
||||||
import { BookingConfirmationContext } from "@/contexts/BookingConfirmation"
|
import { BookingConfirmationContext } from "@/contexts/BookingConfirmation"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import type { BookingConfirmationStore } from "@/types/contexts/booking-confirmation"
|
import type { BookingConfirmationStore } from "@/types/contexts/booking-confirmation"
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import type { BookingConfirmationProviderProps } from "@/types/providers/booking-confirmation"
|
import type { BookingConfirmationProviderProps } from "@/types/providers/booking-confirmation"
|
||||||
|
|
||||||
export default function BookingConfirmationProvider({
|
export default function BookingConfirmationProvider({
|
||||||
@@ -18,9 +21,29 @@ export default function BookingConfirmationProvider({
|
|||||||
rooms,
|
rooms,
|
||||||
vat,
|
vat,
|
||||||
}: BookingConfirmationProviderProps) {
|
}: BookingConfirmationProviderProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const storeRef = useRef<BookingConfirmationStore>()
|
const storeRef = useRef<BookingConfirmationStore>()
|
||||||
|
|
||||||
if (!storeRef.current) {
|
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 = {
|
const initialData = {
|
||||||
bookingCode,
|
bookingCode,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
@@ -28,6 +51,9 @@ export default function BookingConfirmationProvider({
|
|||||||
toDate,
|
toDate,
|
||||||
rooms,
|
rooms,
|
||||||
vat,
|
vat,
|
||||||
|
isVatCurrency,
|
||||||
|
formattedTotalCost,
|
||||||
|
totalBookingPrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
storeRef.current = createBookingConfirmationStore(initialData)
|
storeRef.current = createBookingConfirmationStore(initialData)
|
||||||
|
|||||||
@@ -16,13 +16,24 @@ export function createBookingConfirmationStore(initialState: InitialState) {
|
|||||||
fromDate: initialState.fromDate,
|
fromDate: initialState.fromDate,
|
||||||
toDate: initialState.toDate,
|
toDate: initialState.toDate,
|
||||||
vat: initialState.vat,
|
vat: initialState.vat,
|
||||||
|
formattedTotalCost: initialState.formattedTotalCost,
|
||||||
|
isVatCurrency: initialState.isVatCurrency,
|
||||||
|
totalBookingPrice: initialState.totalBookingPrice,
|
||||||
actions: {
|
actions: {
|
||||||
setRoom: (room, idx) => {
|
setRoom: (room, idx) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const rooms = [...state.rooms]
|
const rooms = [...state.rooms]
|
||||||
rooms[idx] = room
|
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)
|
}, 0)
|
||||||
|
|
||||||
const totalPoints = newRooms.reduce((sum, r) => {
|
const totalPoints = newRooms.reduce((sum, r) => {
|
||||||
return sum + r.roomPoints
|
return sum + (r.roomPoints ?? 0)
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ export interface Room {
|
|||||||
fromDate: Date
|
fromDate: Date
|
||||||
name: string
|
name: string
|
||||||
packages: BookingConfirmation["booking"]["packages"]
|
packages: BookingConfirmation["booking"]["packages"]
|
||||||
|
formattedTotalCost: string
|
||||||
rateDefinition: BookingConfirmation["booking"]["rateDefinition"]
|
rateDefinition: BookingConfirmation["booking"]["rateDefinition"]
|
||||||
roomFeatures?: PackageSchema[] | null
|
roomFeatures?: PackageSchema[] | null
|
||||||
|
roomPoints: number
|
||||||
roomPrice: number
|
roomPrice: number
|
||||||
roomTypeCode: string | null
|
roomTypeCode: string | null
|
||||||
toDate: Date
|
toDate: Date
|
||||||
@@ -39,14 +41,23 @@ export interface InitialState {
|
|||||||
toDate: Date
|
toDate: Date
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
vat: number
|
vat: number
|
||||||
|
isVatCurrency: boolean
|
||||||
|
formattedTotalCost: string
|
||||||
|
totalBookingPrice: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookingConfirmationState {
|
export interface BookingConfirmationState {
|
||||||
bookingCode: string | null
|
bookingCode: string | null
|
||||||
|
isVatCurrency: boolean
|
||||||
rooms: (Room | null)[]
|
rooms: (Room | null)[]
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
vat: number
|
vat: number
|
||||||
fromDate: Date
|
fromDate: Date
|
||||||
toDate: 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 intl - react-intl object
|
||||||
* @param price - number to be formatted
|
* @param price - number to be formatted
|
||||||
* @param currency - currency code
|
* @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
|
* @returns localized and formatted number in string type with currency
|
||||||
*/
|
*/
|
||||||
export function formatPrice(
|
export function formatPrice(
|
||||||
@@ -26,21 +28,14 @@ export function formatPrice(
|
|||||||
const localizedPrice = intl.formatNumber(price, {
|
const localizedPrice = intl.formatNumber(price, {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
})
|
})
|
||||||
return `${localizedPrice} ${currency} ${additionalPrice ? "+ " + additionalPrice + " " + additionalPriceCurrency : ""}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will handle redemption and bonus cheque (corporate cheque) scneario with partial payments
|
let formattedAdditionalPrice: string = ""
|
||||||
export function formatPriceWithAdditionalPrice(
|
if (additionalPrice && additionalPriceCurrency) {
|
||||||
intl: IntlShape,
|
const localizedAdditionalPrice = intl.formatNumber(additionalPrice, {
|
||||||
points: number,
|
minimumFractionDigits: 0,
|
||||||
pointsCurrency: string,
|
})
|
||||||
additionalPrice?: number,
|
formattedAdditionalPrice = ` ${localizedAdditionalPrice} ${additionalPriceCurrency}`
|
||||||
additionalPriceCurrency?: string
|
}
|
||||||
) {
|
|
||||||
const formattedAdditionalPrice =
|
|
||||||
additionalPrice && additionalPriceCurrency
|
|
||||||
? `+ ${formatPrice(intl, additionalPrice, additionalPriceCurrency)}`
|
|
||||||
: ""
|
|
||||||
|
|
||||||
return `${formatPrice(intl, points, pointsCurrency)} ${formattedAdditionalPrice}`
|
return `${localizedPrice} ${currency}${formattedAdditionalPrice}`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user