diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/PriceDetails/mapToPrice.ts b/apps/scandic-web/components/HotelReservation/BookingConfirmation/PriceDetails/mapToPrice.ts index 783890637..a062b24e8 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/PriceDetails/mapToPrice.ts +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/PriceDetails/mapToPrice.ts @@ -4,6 +4,7 @@ import { } from "@/server/routers/hotels/schemas/packages" import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" +import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast" import { BreakfastPackageEnum } from "@/types/enums/breakfast" import { PackageTypeEnum } from "@/types/enums/packages" import type { Package } from "@/types/requests/packages" @@ -47,24 +48,37 @@ export function mapToPrice(rooms: (Room | null)[], nights: number) { } } - const breakfastPackage = breakfastPackageSchema.safeParse({ - code: room.breakfast?.code, - description: room.breakfast?.description, - localPrice: { - currency: room.breakfast?.currency, - price: room.breakfast?.unitPrice, - totalPrice: room.breakfast?.totalPrice, - }, - packageType: - room.breakfast?.code === BreakfastPackageEnum.REGULAR_BREAKFAST - ? PackageTypeEnum.BreakfastAdult - : "", - requestedPrice: { - currency: room.breakfast?.currency, - price: room.breakfast?.unitPrice, - totalPrice: room.breakfast?.totalPrice, - }, - }) + let breakfast: + | false + | undefined + | Omit + if (room.breakfast) { + const breakfastPackage = breakfastPackageSchema.safeParse({ + code: room.breakfast?.code, + description: room.breakfast?.description, + localPrice: { + currency: room.breakfast?.currency, + price: room.breakfast?.unitPrice, + totalPrice: room.breakfast?.totalPrice, + }, + packageType: + room.breakfast?.code === BreakfastPackageEnum.REGULAR_BREAKFAST + ? PackageTypeEnum.BreakfastAdult + : "", + requestedPrice: { + currency: room.breakfast?.currency, + price: room.breakfast?.unitPrice, + totalPrice: room.breakfast?.totalPrice, + }, + }) + if (breakfastPackage.success) { + breakfast = breakfastPackage.data + } + } + + if (!room.breakfastIncluded && !breakfast) { + breakfast = false + } const packages = room.roomFeatures ?.map((featPkg) => { @@ -97,7 +111,7 @@ export function mapToPrice(rooms: (Room | null)[], nights: number) { description: room.bedDescription, roomTypeCode: room.roomTypeCode || "", }, - breakfast: breakfastPackage.success ? breakfastPackage.data : undefined, + breakfast, breakfastIncluded: room.rateDefinition.breakfastIncluded, childrenInRoom: room.childrenAges?.map((age) => ({ age, diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/breakfast.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/breakfast.module.css new file mode 100644 index 000000000..0a5c46a72 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/breakfast.module.css @@ -0,0 +1,12 @@ +.entry { + display: flex; + justify-content: space-between; +} + +.textDefault { + color: var(--Text-Default); +} + +.textSecondary { + color: var(--Text-Secondary); +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/index.tsx new file mode 100644 index 000000000..3f8ebc77d --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/Breakfast/index.tsx @@ -0,0 +1,65 @@ +"use client" +import { useIntl } from "react-intl" + +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { formatPrice } from "@/utils/numberFormatting" + +import styles from "./breakfast.module.css" + +import type { PackageSchema } from "@/types/trpc/routers/booking/confirmation" + +interface BreakfastProps { + breakfast: PackageSchema | false | undefined + breakfastIncluded: boolean + guests: string +} + +export default function Breakfast({ + breakfast, + breakfastIncluded, + guests, +}: BreakfastProps) { + const intl = useIntl() + + const breakfastBuffet = intl.formatMessage({ + defaultMessage: "Breakfast buffet", + }) + + if (breakfastIncluded || breakfast) { + const price = breakfast + ? formatPrice(intl, breakfast.totalPrice, breakfast.currency) + : intl.formatMessage({ defaultMessage: "Included" }) + return ( +
+
+ +

{breakfastBuffet}

+
+ +

{guests}

+
+
+ +

{price}

+
+
+ ) + } + + if (breakfast === false) { + const noBreakfast = intl.formatMessage({ defaultMessage: "No breakfast" }) + return ( +
+ +

{breakfastBuffet}

+
+ +

{noBreakfast}

+
+
+ ) + } + + return null +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx index 6701baf3a..467a3d732 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx @@ -13,6 +13,7 @@ import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import { formatPrice } from "@/utils/numberFormatting" +import Breakfast from "./Breakfast" import RoomSkeletonLoader from "./RoomSkeletonLoader" import styles from "./room.module.css" @@ -34,8 +35,7 @@ export default function ReceiptRoom({ if (!room) { return } - const breakfastIncluded = - room.breakfastIncluded || room.rateDefinition.breakfastIncluded + const childBedCrib = room.childBedPreferences.find( (c) => c.bedType === ChildBedTypeEnum.Crib ) @@ -63,6 +63,8 @@ export default function ReceiptRoom({ guestsParts.push(childrenMsg) } + const guests = guestsParts.join(", ") + return (
@@ -83,9 +85,7 @@ export default function ReceiptRoom({ )} -

- {guestsParts.join(", ")} -

+

{guests}

@@ -226,37 +226,11 @@ export default function ReceiptRoom({ ) : null} - {room.breakfast || breakfastIncluded ? ( -

- -

- {intl.formatMessage({ - defaultMessage: "Breakfast buffet", - })} -

-
- {breakfastIncluded ? ( - -

- {intl.formatMessage({ - defaultMessage: "Included", - })} -

-
- ) : null} - {room.breakfast && !breakfastIncluded ? ( - -

- {formatPrice( - intl, - room.breakfast.totalPrice, - room.breakfast.currency - )} -

-
- ) : null} -
- ) : null} +
) } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts b/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts index 56113c202..fbda0ac43 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts @@ -5,19 +5,30 @@ 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" +import type { + BookingConfirmationSchema, + PackageSchema, +} from "@/types/trpc/routers/booking/confirmation" export function mapRoomState( booking: BookingConfirmationSchema, room: BookingConfirmationRoom, intl: IntlShape ) { - const breakfast = booking.packages.find( + const breakfastPackage = booking.packages.find( (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST ) - const breakfastIncluded = booking.packages.some( - (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST - ) + const breakfastIncluded = + booking.packages.some( + (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST + ) || booking.rateDefinition.breakfastIncluded + + let breakfast: false | undefined | PackageSchema + if (breakfastPackage) { + breakfast = breakfastPackage + } else if (!breakfastIncluded) { + breakfast = false + } let formattedRoomCost = formatPrice( intl, diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/breakfast.module.css b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/breakfast.module.css new file mode 100644 index 000000000..3ed078736 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/breakfast.module.css @@ -0,0 +1,13 @@ +.entry { + display: flex; + gap: var(--Spacing-x-half); + justify-content: space-between; +} + +.textDefault { + color: var(--Text-Default); +} + +.uiTextMediumContrast { + color: var(--UI-Text-Medium-contrast); +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/index.tsx new file mode 100644 index 000000000..68f6f853a --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Breakfast/index.tsx @@ -0,0 +1,73 @@ +"use client" +import { useIntl } from "react-intl" + +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { formatPrice } from "@/utils/numberFormatting" + +import styles from "./breakfast.module.css" + +import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast" + +interface BreakfastProps { + adults: number + breakfast: BreakfastPackage | false | undefined + breakfastIncluded: boolean + guests: string + nights: number +} + +export default function Breakfast({ + adults, + breakfast, + breakfastIncluded, + guests, + nights, +}: BreakfastProps) { + const intl = useIntl() + + const breakfastBuffet = intl.formatMessage({ + defaultMessage: "Breakfast buffet", + }) + + if (breakfastIncluded || breakfast) { + const price = breakfast + ? formatPrice( + intl, + breakfast.localPrice.price * adults * nights, + breakfast.localPrice.currency + ) + : intl.formatMessage({ defaultMessage: "Included" }) + return ( +
+
+ +

{breakfastBuffet}

+
+ +

{guests}

+
+
+ +

{price}

+
+
+ ) + } + + if (breakfast === false) { + const noBreakfast = intl.formatMessage({ defaultMessage: "No breakfast" }) + return ( +
+ +

{breakfastBuffet}

+
+ +

{noBreakfast}

+
+
+ ) + } + + return null +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx index ad1852c38..d855f5680 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -19,6 +19,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" import { formatPrice } from "@/utils/numberFormatting" +import Breakfast from "./Breakfast" import { mapToPrice } from "./mapToPrice" import styles from "./ui.module.css" @@ -185,6 +186,8 @@ export default function SummaryUI({ guestsParts.push(childrenMsg) } + const guests = guestsParts.join(", ") + const hideBedCurrency = notDisplayableCurrencies.includes( room.roomPrice.perStay.local.currency ) @@ -233,9 +236,7 @@ export default function SummaryUI({ )} - - {guestsParts.join(", ")} - + {guests} {room.cancellationText} @@ -371,82 +372,13 @@ export default function SummaryUI({ ) : null} - {room.breakfastIncluded ? ( -
- - {intl.formatMessage({ - defaultMessage: "Breakfast buffet", - })} - - - {intl.formatMessage({ - defaultMessage: "Included", - })} - -
- ) : room.breakfast === false ? ( -
- - {intl.formatMessage({ - defaultMessage: "No breakfast", - })} - - - {formatPrice( - intl, - 0, - room.roomPrice.perStay.local.currency - )} - -
- ) : null} - {room.breakfast ? ( -
- - {intl.formatMessage({ - defaultMessage: "Breakfast buffet", - })} - -
- - {intl.formatMessage( - { - defaultMessage: - "{totalAdults, plural, one {# adult} other {# adults}}", - }, - { totalAdults: adults } - )} - - - {formatPrice( - intl, - room.breakfast.localPrice.price * adults * nights, - room.breakfast.localPrice.currency - )} - -
- {childrenInRoom?.length ? ( -
- - {intl.formatMessage( - { - defaultMessage: - "{totalChildren, plural, one {# child} other {# children}}", - }, - { totalChildren: childrenInRoom.length } - )} - - - {formatPrice( - intl, - 0, - room.breakfast.localPrice.currency - )} - -
- ) : null} -
- ) : null} + diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx index 96338dacf..99d5aaf63 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx @@ -64,7 +64,8 @@ export default function Steps({ closeModal }: ChangeDatesStepsProps) { setDates({ fromDate, toDate }) const pkgsSum = sumPackages(packages) - const extraPrice = pkgsSum.price + (breakfast?.localPrice.totalPrice || 0) + const extraPrice = + pkgsSum.price + ((breakfast && breakfast.localPrice.totalPrice) || 0) if (isLoggedIn && "member" in data.product && data.product.member) { const { currency, pricePerStay } = data.product.member.localPrice setNewPrice(formatPrice(intl, pricePerStay + extraPrice, currency)) diff --git a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts index bfed0cf4c..ba905441f 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts +++ b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts @@ -40,19 +40,21 @@ export function mapRoomDetails({ ) // We don't get `requestedPrice` in packages - const breakfast: Omit | null = - breakfastPackageAdults - ? { - code: breakfastPackageAdults.code, - description: breakfastPackageAdults.description, - localPrice: { - currency: breakfastPackageAdults.currency, - price: breakfastPackageAdults.unitPrice, - totalPrice: breakfastPackageAdults.totalPrice, - }, - packageType: PackageTypeEnum.BreakfastAdult, - } - : null + let breakfast: Omit | undefined | false + if (breakfastPackageAdults) { + breakfast = { + code: breakfastPackageAdults.code, + description: breakfastPackageAdults.description, + localPrice: { + currency: breakfastPackageAdults.currency, + price: breakfastPackageAdults.unitPrice, + totalPrice: breakfastPackageAdults.totalPrice, + }, + packageType: PackageTypeEnum.BreakfastAdult, + } + } else if (!booking.rateDefinition.breakfastIncluded) { + breakfast = false + } const validBreakfastPackagesChildren: string[] = [ BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST, @@ -62,17 +64,17 @@ export function mapRoomDetails({ ) // We don't get `requestedPrice` in packages const breakfastChildren: Omit | null = - breakfastPackageChildren + (breakfastPackageChildren && !booking.rateDefinition.breakfastIncluded) ? { - code: breakfastPackageChildren.code, - description: breakfastPackageChildren.description, - localPrice: { - currency: breakfastPackageChildren.currency, - price: breakfastPackageChildren.unitPrice, - totalPrice: breakfastPackageChildren.totalPrice, - }, - packageType: PackageTypeEnum.BreakfastChildren, - } + code: breakfastPackageChildren.code, + description: breakfastPackageChildren.description, + localPrice: { + currency: breakfastPackageChildren.currency, + price: breakfastPackageChildren.unitPrice, + totalPrice: breakfastPackageChildren.totalPrice, + }, + packageType: PackageTypeEnum.BreakfastChildren, + } : null const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx index af72c0293..736356364 100644 --- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx +++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx @@ -31,8 +31,99 @@ export default function Breakfast({ }: BreakfastProps) { const intl = useIntl() + const breakfastBuffet = intl.formatMessage({ + defaultMessage: "Breakfast buffet", + }) + + const adultsMsg = intl.formatMessage( + { + defaultMessage: + "Breakfast ({totalAdults, plural, one {# adult} other {# adults}}) x {totalBreakfasts}", + }, + { totalAdults: adults, totalBreakfasts: nights } + ) + + let kidsMsg = "" + if (childrenInRoom?.length) { + kidsMsg = intl.formatMessage( + { + defaultMessage: + "Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}", + }, + { + totalChildren: childrenInRoom.length, + totalBreakfasts: nights, + } + ) + } + if (breakfastIncluded) { const included = intl.formatMessage({ defaultMessage: "Included" }) + return ( + + + {childrenInRoom?.length ? ( + + ) : null} + + + ) + } + + if (breakfast) { + const adultPricePerNight = breakfast.localPrice.price * adults + const breakfastAdultsPricePerNight = formatPrice( + intl, + adultPricePerNight, + breakfast.localPrice.currency + ) + + const { payingChildren, freeChildren } = childrenInRoom.reduce( + (total, child) => { + if (child.age >= 4) { + total.payingChildren = total.payingChildren + 1 + } else { + total.freeChildren = total.freeChildren + 1 + } + return total + }, + { payingChildren: 0, freeChildren: 0 } + ) + + const childrenPrice = breakfastChildren?.localPrice.price || 0 + const childrenPricePerNight = childrenPrice * payingChildren + + const childCurrency = + breakfastChildren?.localPrice.currency ?? breakfast.localPrice.currency + + const breakfastChildrenPricePerNight = formatPrice( + intl, + childrenPricePerNight, + childCurrency + ) + + const totalAdultsPrice = adultPricePerNight * nights + const totalChildrenPrice = childrenPricePerNight * nights + const breakfastTotalPrice = formatPrice( + intl, + totalAdultsPrice + totalChildrenPrice, + breakfast.localPrice.currency + ) + + const freeChildrenMsg = intl.formatMessage( + { + defaultMessage: + "Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}", + }, + { + totalChildren: freeChildren, + totalBreakfasts: nights, + } + ) + return ( - {childrenInRoom?.length ? ( + {breakfastChildren ? ( + + ) : null} + {breakfastChildren && freeChildren ? ( + + ) : null} + {childrenInRoom?.length && !breakfastChildren ? ( ) : null} - + ) } - if (!breakfast) { - return null + if (breakfast === false) { + const noBreakfast = intl.formatMessage({ + defaultMessage: "No breakfast", + }) + return ( + + + + ) } - const adultPricePerNight = breakfast.localPrice.price * adults - const breakfastAdultsPricePerNight = formatPrice( - intl, - adultPricePerNight, - breakfast.localPrice.currency - ) - - const { payingChildren, freeChildren } = childrenInRoom.reduce( - (total, child) => { - if (child.age >= 4) { - total.payingChildren = total.payingChildren + 1 - } else { - total.freeChildren = total.freeChildren + 1 - } - return total - }, - { payingChildren: 0, freeChildren: 0 } - ) - - const childrenPrice = breakfastChildren?.localPrice.price || 0 - const childrenPricePerNight = childrenPrice * payingChildren - - const childCurrency = - breakfastChildren?.localPrice.currency ?? breakfast.localPrice.currency - - const breakfastChildrenPricePerNight = formatPrice( - intl, - childrenPricePerNight, - childCurrency - ) - - const totalAdultsPrice = adultPricePerNight * nights - const totalChildrenPrice = childrenPricePerNight * nights - const breakfastTotalPrice = formatPrice( - intl, - totalAdultsPrice + totalChildrenPrice, - breakfast.localPrice.currency - ) - - const freeChildrenMsg = intl.formatMessage( - { - defaultMessage: - "Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}", - }, - { - totalChildren: freeChildren, - totalBreakfasts: nights, - } - ) - - return ( - - - {breakfastChildren ? ( - - ) : null} - {breakfastChildren && freeChildren ? ( - - ) : null} - {childrenInRoom?.length && !breakfastChildren ? ( - - ) : null} - - - ) + return null } diff --git a/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx b/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx index a6dec5ad0..b357d44b7 100644 --- a/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx +++ b/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx @@ -55,7 +55,7 @@ export type Room = Pick< | "vouchers" > & { bedType: BedTypeSchema - breakfast: Omit | null + breakfast: Omit | false | undefined childrenInRoom: Child[] isCancelled: boolean packages: Packages | null diff --git a/apps/scandic-web/types/stores/booking-confirmation.ts b/apps/scandic-web/types/stores/booking-confirmation.ts index b87c71226..7d99f95cc 100644 --- a/apps/scandic-web/types/stores/booking-confirmation.ts +++ b/apps/scandic-web/types/stores/booking-confirmation.ts @@ -15,7 +15,7 @@ export interface Room { adults: number bedDescription: string bookingCode: string | null - breakfast?: PackageSchema + breakfast: PackageSchema | false | undefined breakfastIncluded: boolean cheques: number childBedPreferences: ChildBedPreference[] diff --git a/apps/scandic-web/types/stores/my-stay.ts b/apps/scandic-web/types/stores/my-stay.ts index 742f04cd6..2398256b5 100644 --- a/apps/scandic-web/types/stores/my-stay.ts +++ b/apps/scandic-web/types/stores/my-stay.ts @@ -42,7 +42,7 @@ export type Room = Pick< | "vouchers" > & { bedType: BedTypeSchema - breakfast: Omit | null + breakfast: Omit | undefined | false breakfastChildren: Omit | null childrenAsString: string childrenInRoom: Child[]