Merged in feat/SW-1652-confirmation-page (pull request #1483)

Feat/SW-1652 confirmation page

* feat(SW-1652): handle linkedReservations fetching

* fix: add missing translations

* feat: add linkedReservation retry functionality

* chore: align naming


Approved-by: Simon.Emanuelsson
This commit is contained in:
Arvid Norlin
2025-03-07 12:47:04 +00:00
parent 7fa86a2077
commit ec60e9abdd
34 changed files with 474 additions and 303 deletions

View File

@@ -19,10 +19,6 @@
gap: var(--Spacing-x-half);
}
.coordinates {
margin-top: var(--Spacing-x-half);
}
.list {
padding-left: var(--Spacing-x2);
}

View File

@@ -41,15 +41,6 @@ export default function HotelDetails({
</Link>
</Body>
</div>
<Body color="uiTextPlaceholder" className={styles.coordinates}>
{intl.formatMessage(
{ id: "Long {long} ∙ Lat {lat}" },
{
lat: hotel.location.latitude,
long: hotel.location.longitude,
}
)}
</Body>
</div>
<div className={styles.contact}>
<Link

View File

@@ -1,61 +1,49 @@
import { dt } from "@/lib/dt"
import { serverClient } from "@/lib/trpc/server"
"use client"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { CreditCardAddIcon } from "@/components/Icons"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { formatPrice } from "@/utils/numberFormatting"
import styles from "./paymentDetails.module.css"
import type { BookingConfirmationPaymentDetailsProps } from "@/types/components/hotelReservation/bookingConfirmation/paymentDetails"
export default function PaymentDetails() {
const intl = useIntl()
export default async function PaymentDetails({
booking,
}: BookingConfirmationPaymentDetailsProps) {
const intl = await getIntl()
const lang = getLang()
const linkedReservations = await Promise.all(
// TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI
booking.linkedReservations.map(async (res) => {
const confirmation = await serverClient().booking.confirmation({
confirmationNumber: res.confirmationNumber,
})
return confirmation
})
const rooms = useBookingConfirmationStore((state) => state.rooms)
const currencyCode = useBookingConfirmationStore(
(state) => state.currencyCode
)
const grandTotal = linkedReservations.reduce((acc, res) => {
return res ? acc + res.booking.totalPrice : acc
}, booking.totalPrice)
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">
{intl.formatMessage({ id: "Payment details" })}
</Subtitle>
<div className={styles.payment}>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "{amount} has been paid" },
{
amount: formatPrice(intl, grandTotal, booking.currencyCode),
}
)}
</Body>
<Body color="uiTextHighContrast">
{dt(booking.createDateTime)
.locale(lang)
.format("ddd D MMM YYYY, hh:mm")}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "{card} ending with {cardno}" },
{ card: "N/A", cardno: "N/A" }
)}
</Body>
{hasAllRoomsLoaded ? (
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "{amount} has been paid" },
{
amount: formatPrice(intl, grandTotal, currencyCode),
}
)}
</Body>
) : (
<SkeletonShimmer width={"100%"} />
)}
</div>
<Button
className={styles.btn}

View File

@@ -6,6 +6,7 @@
.details {
gap: var(--Spacing-x-one-and-half);
max-width: fit-content;
}
.payment {

View File

@@ -0,0 +1,17 @@
import SkeletonShimmer from "@/components/SkeletonShimmer"
import styles from "./roomSkeletonLoader.module.css"
export default function RoomSkeletonLoader() {
return (
<div className={styles.room}>
<SkeletonShimmer />
<SkeletonShimmer width={"15%"} />
<SkeletonShimmer width={"30%"} />
<SkeletonShimmer width={"40%"} />
<SkeletonShimmer />
<SkeletonShimmer />
<SkeletonShimmer />
</div>
)
}

View File

@@ -2,6 +2,9 @@
import { useIntl } from "react-intl"
import { CancellationRuleEnum } from "@/constants/booking"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
@@ -10,58 +13,49 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { formatPrice } from "@/utils/numberFormatting"
import RoomSkeletonLoader from "./RoomSkeletonLoader"
import styles from "./room.module.css"
import type { BookingConfirmationReceiptRoomProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default function ReceiptRoom({
booking,
room,
roomNumber,
roomIndex,
}: BookingConfirmationReceiptRoomProps) {
const intl = useIntl()
const breakfastPkgSelected = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
const breakfastPkgIncluded = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
const room = useBookingConfirmationStore((state) => state.rooms[roomIndex])
const currencyCode = useBookingConfirmationStore(
(state) => state.currencyCode
)
if (!room) {
return <RoomSkeletonLoader />
}
return (
<article className={styles.room}>
{roomNumber !== null ? (
<Body color="uiTextHighContrast" textTransform={"bold"}>
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: roomNumber }
)}
</Body>
) : null}
<header className={styles.roomHeader}>
<Body color="uiTextHighContrast">{room.name}</Body>
{booking.rateDefinition.isMemberRate ? (
{room.rateDefinition.isMemberRate ? (
<div className={styles.memberPrice}>
<Body color="red">
{formatPrice(intl, booking.roomPrice, booking.currencyCode)}
{formatPrice(intl, room.roomPrice, currencyCode)}
</Body>
</div>
) : (
<Body color="uiTextHighContrast">
{formatPrice(intl, booking.roomPrice, booking.currencyCode)}
{formatPrice(intl, room.roomPrice, currencyCode)}
</Body>
)}
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
{
totalAdults: booking.adults,
totalAdults: room.adults,
}
)}
</Caption>
<Caption color="uiTextMediumContrast">
{booking.rateDefinition.cancellationText}
{room.rateDefinition.cancellationText}
</Caption>
<Modal
trigger={
@@ -78,15 +72,16 @@ export default function ReceiptRoom({
</Link>
</Button>
}
title={booking.rateDefinition.cancellationText || ""}
title={room.rateDefinition.cancellationText || ""}
subtitle={
booking.rateDefinition.cancellationRule == "CancellableBefore6PM"
room.rateDefinition.cancellationRule ===
CancellationRuleEnum.CancellableBefore6PM
? intl.formatMessage({ id: "Pay later" })
: intl.formatMessage({ id: "Pay now" })
}
>
<div className={styles.terms}>
{booking.rateDefinition.generalTerms?.map((info) => (
{room.rateDefinition.generalTerms?.map((info) => (
<Body
key={info}
color="uiTextHighContrast"
@@ -105,22 +100,22 @@ export default function ReceiptRoom({
</Modal>
</header>
<div className={styles.entry}>
<Body color="uiTextHighContrast">{room.bedType.description}</Body>
<Body color="uiTextHighContrast">{room.bedDescription}</Body>
<Body color="uiTextHighContrast">
{formatPrice(intl, 0, booking.currencyCode)}
{formatPrice(intl, 0, currencyCode)}
</Body>
</div>
<div className={styles.entry}>
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
{(booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded) ? (
{(room.rateDefinition.breakfastIncluded ?? room.breakfastIncluded) ? (
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
) : null}
{breakfastPkgSelected ? (
{room.selectedBreakfast ? (
<Body color="uiTextHighContrast">
{formatPrice(
intl,
breakfastPkgSelected.totalPrice,
breakfastPkgSelected.currency
room.selectedBreakfast.totalPrice,
room.selectedBreakfast.currency
)}
</Body>
) : null}

View File

@@ -0,0 +1,5 @@
.room {
display: flex;
gap: var(--Spacing-x1);
flex-direction: column;
}

View File

@@ -1,88 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import { ChevronRightSmallIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { formatPrice } from "@/utils/numberFormatting"
import Room from "../Room"
import styles from "./rooms.module.css"
import type { BookingConfirmationReceiptRoomsProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt"
export default function ReceiptRooms({
booking,
room,
linkedReservations,
}: BookingConfirmationReceiptRoomsProps) {
const intl = useIntl()
if (linkedReservations.some((reservation) => !reservation)) {
return null
}
const grandTotal = linkedReservations.reduce((acc, reservation) => {
const reservationTotalPrice = reservation?.booking.totalPrice || 0
return acc + reservationTotalPrice
}, booking.totalPrice)
return (
<>
<Room
booking={booking}
room={room}
roomNumber={linkedReservations.length ? 1 : null}
/>
{linkedReservations.map((reservation, idx) => {
if (!reservation?.room) {
return null
}
return (
<Room
key={reservation?.booking.confirmationNumber}
booking={reservation.booking}
room={reservation.room}
roomNumber={idx + 2}
/>
)
})}
<Divider color="primaryLightSubtle" />
<div className={styles.price}>
<div className={styles.entry}>
<Body textTransform="bold">
{intl.formatMessage({ id: "Total price" })}
</Body>
<Body textTransform="bold">
{formatPrice(intl, grandTotal, booking.currencyCode)}
</Body>
</div>
<div className={styles.entry}>
<Button
className={styles.btn}
intent="text"
size="small"
theme="base"
variant="icon"
wrapping
>
{intl.formatMessage({ id: "Price details" })}
<ChevronRightSmallIcon />
</Button>
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{ id: "Approx. {value}" },
{
value: "N/A",
}
)}
</Caption>
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,66 @@
"use client"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { ChevronRightSmallIcon } from "@/components/Icons"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body"
import { formatPrice } from "@/utils/numberFormatting"
import styles from "./totalPrice.module.css"
export default function TotalPrice() {
const intl = useIntl()
const rooms = useBookingConfirmationStore((state) => state.rooms)
const currencyCode = useBookingConfirmationStore(
(state) => state.currencyCode
)
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>
{hasAllRoomsLoaded ? (
<Body textTransform="bold">
{formatPrice(intl, grandTotal, currencyCode)}
</Body>
) : (
<SkeletonShimmer width={"25%"} />
)}
</div>
{hasAllRoomsLoaded ? (
<div className={styles.entry}>
<Button
className={styles.btn}
intent="text"
size="small"
theme="base"
variant="icon"
wrapping
>
{intl.formatMessage({ id: "Price details" })}
<ChevronRightSmallIcon />
</Button>
</div>
) : (
<div className={styles.priceDetailsLoader}>
<SkeletonShimmer width={"100%"} />
</div>
)}
</div>
</>
)
}

View File

@@ -1,46 +1,42 @@
import { notFound } from "next/navigation"
"use client"
import { serverClient } from "@/lib/trpc/server"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import ReceiptRooms from "./Rooms"
import Room from "./Room"
import TotalPrice from "./TotalPrice"
import styles from "./receipt.module.css"
import type { BookingConfirmationReceiptProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt"
export default async function Receipt({
booking,
room,
}: BookingConfirmationReceiptProps) {
if (!room) {
return notFound()
}
const intl = await getIntl()
const linkedReservations = await Promise.all(
// TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI
booking.linkedReservations.map(async (res) => {
const confirmation = await serverClient().booking.confirmation({
confirmationNumber: res.confirmationNumber,
})
return confirmation
})
)
export default function Receipt() {
const intl = useIntl()
const rooms = useBookingConfirmationStore((state) => state.rooms)
return (
<section className={styles.receipt}>
<Subtitle type="two">
{intl.formatMessage({ id: "Booking summary" })}
</Subtitle>
<ReceiptRooms
booking={booking}
room={room}
linkedReservations={linkedReservations}
/>
{rooms.map((room, idx) => (
<div key={room ? room.confirmationNumber : `loader-${idx}`}>
{rooms.length > 1 ? (
<Body color="uiTextHighContrast" textTransform={"bold"}>
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: idx + 1 }
)}
</Body>
) : null}
<Room roomIndex={idx} />
</div>
))}
<TotalPrice />
</section>
)
}

View File

@@ -0,0 +1,23 @@
"use client"
import { useIntl } from "react-intl"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./retry.module.css"
import type { RetryProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
export default function Retry({ handleRefetch }: RetryProps) {
const intl = useIntl()
return (
<div className={styles.retry}>
<Body>{intl.formatMessage({ id: "Something went wrong!" })}</Body>
<Button size={"small"} onPress={handleRefetch}>
{intl.formatMessage({ id: "Try again" })}
</Button>
</div>
)
}

View File

@@ -1,22 +1,50 @@
import { serverClient } from "@/lib/trpc/server"
"use client"
import { useEffect } from "react"
import { trpc } from "@/lib/trpc/client"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import useLang from "@/hooks/useLang"
import { mapRoomState } from "../../utils"
import Room from "../Room"
import { LinkedReservationCardSkeleton } from "./LinkedReservationCardSkeleton"
import Retry from "./Retry"
export async function LinkedReservation({
import type { LinkedReservationProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
export function LinkedReservation({
confirmationNumber,
}: {
confirmationNumber: string
}) {
const confirmation = await serverClient().booking.confirmation({
roomIndex,
}: LinkedReservationProps) {
const lang = useLang()
const { data, refetch, isLoading } = trpc.booking.confirmation.useQuery({
confirmationNumber,
lang,
})
const setRoom = useBookingConfirmationStore((state) => state.actions.setRoom)
const room = confirmation?.room
const booking = confirmation?.booking
useEffect(() => {
if (data?.room) {
const roomData = mapRoomState(data.booking, data.room)
setRoom(roomData, roomIndex)
}
}, [data, roomIndex, setRoom])
if (!booking || !room) {
return <div>Something went wrong, try again</div>
if (isLoading) {
return <LinkedReservationCardSkeleton />
}
return <Room booking={booking} img={room.images[0]} roomName={room.name} />
if (!data?.room) {
return <Retry handleRefetch={refetch} />
}
return (
<Room
img={data.room.images[0]}
booking={data.booking}
roomName={data.room.name}
/>
)
}

View File

@@ -0,0 +1,10 @@
.retry {
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Large);
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3)
var(--Spacing-x2);
align-items: center;
}

View File

@@ -2,6 +2,7 @@
import { useIntl } from "react-intl"
import { CancellationRuleEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
import {
@@ -27,6 +28,12 @@ export default function Room({ booking, img, roomName }: RoomProps) {
const guestName = `${booking.guest.firstName} ${booking.guest.lastName}`
const fromDate = dt(booking.checkInDate).locale(lang)
const toDate = dt(booking.checkOutDate).locale(lang)
const isFlexBooking =
booking.rateDefinition.cancellationRule ===
CancellationRuleEnum.CancellableBefore6PM
const isChangeBooking =
booking.rateDefinition.cancellationRule === CancellationRuleEnum.Changeable
return (
<article className={styles.room}>
<header className={styles.header}>
@@ -98,14 +105,6 @@ export default function Room({ booking, img, roomName }: RoomProps) {
)}
</Body>
</li>
<li className={styles.listItem}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Breakfast" })}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage({ id: "N/A" })}
</Body>
</li>
<li className={styles.listItem}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Cancellation policy" })}
@@ -114,14 +113,19 @@ export default function Room({ booking, img, roomName }: RoomProps) {
{booking.rateDefinition.cancellationText}
</Body>
</li>
<li className={styles.listItem}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Rebooking" })}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage({ id: "N/A" })}
</Body>
</li>
{isFlexBooking || isChangeBooking ? (
<li className={styles.listItem}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Rebooking" })}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Until {time}, {date}" },
{ time: "18:00", date: fromDate.format("dddd D MMM") }
)}
</Body>
</li>
) : null}
</ul>
<div className={styles.guest}>
<Body color="uiTextPlaceholder">

View File

@@ -1,9 +1,6 @@
import { Suspense } from "react"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import { LinkedReservationCardSkeleton } from "./LinkedReservation/LinkedReservationCardSkeleton"
import { LinkedReservation } from "./LinkedReservation"
import Room from "./Room"
@@ -17,6 +14,7 @@ export default async function Rooms({
linkedReservations,
}: BookingConfirmationRoomsProps) {
const intl = await getIntl()
return (
<section className={styles.rooms}>
<div className={styles.room}>
@@ -40,11 +38,10 @@ export default async function Rooms({
{ roomIndex: idx + 2 }
)}
</Subtitle>
<Suspense fallback={<LinkedReservationCardSkeleton />}>
<LinkedReservation
confirmationNumber={reservation.confirmationNumber}
/>
</Suspense>
<LinkedReservation
confirmationNumber={reservation.confirmationNumber}
roomIndex={idx + 1}
/>
</div>
))}
</section>

View File

@@ -1,9 +1,7 @@
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
import { notFound } from "next/navigation"
import { Suspense } from "react"
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server"
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails"
@@ -14,10 +12,12 @@ import SidePanel from "@/components/HotelReservation/SidePanel"
import Divider from "@/components/TempDesignSystem/Divider"
import TrackingSDK from "@/components/TrackingSDK"
import { getLang } from "@/i18n/serverContext"
import BookingConfirmationProvider from "@/providers/BookingConfirmationProvider"
import { invertedBedTypeMap } from "../utils"
import Alerts from "./Alerts"
import Confirmation from "./Confirmation"
import { mapRoomState } from "./utils"
import styles from "./bookingConfirmation.module.css"
@@ -47,16 +47,16 @@ export default async function BookingConfirmation({
const arrivalDate = new Date(booking.checkInDate)
const departureDate = new Date(booking.checkOutDate)
const breakfastPkgSelected = booking.packages.find(
const selectedBreakfast = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
const breakfastAncillary = breakfastPkgSelected && {
const breakfastAncillary = selectedBreakfast && {
hotelid: hotel.operaId,
productName: "BreakfastAdult",
productCategory: "", // TODO: Add category
productId: breakfastPkgSelected.code ?? "",
productPrice: +breakfastPkgSelected.unitPrice,
productId: selectedBreakfast.code ?? "",
productPrice: +selectedBreakfast.unitPrice,
productUnits: booking.adults,
productPoints: 0,
productType: "food",
@@ -112,18 +112,15 @@ export default async function BookingConfirmation({
paymentStatus: "confirmed",
}
const linkedReservations = await Promise.all(
// TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI
booking.linkedReservations.map(async (res) => {
const confirmation = await serverClient().booking.confirmation({
confirmationNumber: res.confirmationNumber,
})
return confirmation
})
)
return (
<>
<BookingConfirmationProvider
currencyCode={booking.currencyCode}
rooms={[
mapRoomState(booking, room),
// null represents "known but not yet fetched rooms" and is used to render placeholders correctly
...Array(booking.linkedReservations.length).fill(null),
]}
>
<Confirmation booking={booking} hotel={hotel} room={room}>
<div className={styles.booking}>
<Alerts booking={booking} />
@@ -132,12 +129,7 @@ export default async function BookingConfirmation({
mainRoom={room}
linkedReservations={booking.linkedReservations}
/>
<Suspense fallback={null}>
<PaymentDetails
booking={booking}
linkedReservations={linkedReservations}
/>
</Suspense>
<PaymentDetails />
<Divider color="primaryLightSubtle" />
<HotelDetails hotel={hotel} />
<Promos
@@ -146,16 +138,12 @@ export default async function BookingConfirmation({
lastName={booking.guest.lastName}
/>
<div className={styles.mobileReceipt}>
<Suspense fallback={null}>
<Receipt booking={booking} hotel={hotel} room={room} />
</Suspense>
<Receipt />
</div>
</div>
<aside className={styles.aside}>
<SidePanel variant="receipt">
<Suspense fallback={null}>
<Receipt booking={booking} hotel={hotel} room={room} />
</Suspense>
<Receipt />
</SidePanel>
</aside>
</Confirmation>
@@ -164,6 +152,6 @@ export default async function BookingConfirmation({
hotelInfo={initialHotelsTrackingData}
paymentInfo={paymentInfo}
/>
</>
</BookingConfirmationProvider>
)
}

View File

@@ -0,0 +1,29 @@
import type { BookingConfirmationRoom } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
export function mapRoomState(
booking: BookingConfirmationSchema,
room: BookingConfirmationRoom
) {
const selectedBreakfast = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
const breakfastIncluded = booking.packages.some(
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
)
return {
adults: booking.adults,
bedDescription: room.bedType.description,
breakfastIncluded,
children: booking.childrenAges.length,
confirmationNumber: booking.confirmationNumber,
fromDate: booking.checkInDate,
name: room.name,
rateDefinition: booking.rateDefinition,
roomPrice: booking.roomPrice,
selectedBreakfast,
toDate: booking.checkOutDate,
totalPrice: booking.totalPrice,
}
}