diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 603bbbf4a..48a0b30b1 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -58,6 +58,8 @@ export default async function SelectRatePage({ return "No hotel data found" // TODO: Add a proper error message } + console.log(selectRoomParamsObject) + const roomCategories = hotelData?.included return ( diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx index 3bc438c41..a58bdd1b2 100644 --- a/components/BookingWidget/MobileToggleButton/index.tsx +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -68,7 +68,14 @@ export default function MobileToggleButton({ {`${selectedFromDate} - ${selectedToDate} (${intl.formatMessage( { id: "booking.nights" }, { totalNights: nights } - )}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${intl.formatMessage({ id: "booking.children" }, { totalChildren })}, ${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`} + )}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${ + totalChildren > 0 + ? intl.formatMessage( + { id: "booking.children" }, + { totalChildren } + ) + ", " + : "" + }${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`}
diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index 836dd5b49..19fd4d8b7 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -9,6 +9,7 @@ import { z } from "zod" import { InfoCircleIcon } from "@/components/Icons" import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" import { Tooltip } from "@/components/TempDesignSystem/Tooltip" import { getIconForFeatureCode } from "../utils" @@ -49,6 +50,12 @@ export default function RoomFilter({ const petFriendly = watch(RoomPackageCodeEnum.PETR) const allergyFriendly = watch(RoomPackageCodeEnum.ALLG) + const selectedFilters = useMemo(() => getValues(), [getValues]) + + const tooltipText = intl.formatMessage({ + id: "Pet-friendly rooms have an additional fee of 20 EUR per stay", + }) + const submitFilter = useCallback(() => { const data = getValues() onFilter(data) @@ -61,9 +68,33 @@ export default function RoomFilter({ return (
- - {intl.formatMessage({ id: "Room types available" }, { numberOfRooms })} - +
+ + {intl.formatMessage( + { id: "Room types available" }, + { numberOfRooms } + )} + +
+
+
+ + {intl.formatMessage({ id: "Filter" })} + + + {Object.entries(selectedFilters) + .filter(([_, value]) => value) + .map(([key]) => intl.formatMessage({ id: key })) + .join(", ")} + +
+ + {intl.formatMessage( + { id: "Room types available" }, + { numberOfRooms } + )} + +
@@ -80,13 +111,7 @@ export default function RoomFilter({ Icon={getIconForFeatureCode(option.code)} /> ))} - +
diff --git a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css index c0eff095a..9cce04e43 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css +++ b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css @@ -17,3 +17,27 @@ stroke: var(--UI-Text-Medium-contrast); fill: transparent; } +.filterInfo { + display: flex; + flex-direction: row; + gap: var(--Spacing-x-half); + align-items: flex-end; +} + +.infoDesktop { + display: none; +} + +.infoMobile { + display: block; +} + +@media (min-width: 768px) { + .infoDesktop { + display: block; + } + + .infoMobile { + display: none; + } +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx index 76f366adb..dc7ca20fc 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx @@ -40,6 +40,9 @@ export default function PriceList({ {publicLocalPrice.currency} + + /{intl.formatMessage({ id: "night" })} +
) : ( @@ -64,6 +67,9 @@ export default function PriceList({ {memberLocalPrice.currency} + + /{intl.formatMessage({ id: "night" })} +
) : ( diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css index 7320cf1be..4f3431525 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css @@ -12,3 +12,8 @@ display: flex; gap: var(--Spacing-x-half); } + +.perNight { + font-weight: 400; + font-size: var(--typography-Caption-Regular-fontSize); +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index 98915efa6..3b9f6dbf6 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -2,6 +2,8 @@ import { useIntl } from "react-intl" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./rateSummary.module.css" @@ -13,12 +15,19 @@ export default function RateSummary({ rateSummary, isUserLoggedIn, packages, + roomsAvailability, }: RateSummaryProps) { const intl = useIntl() + const { + member, + public: publicRate, + features, + roomType, + priceName, + } = rateSummary + const priceToShow = isUserLoggedIn ? member : publicRate - const priceToShow = isUserLoggedIn ? rateSummary.member : rateSummary.public - - const isPetRoomSelect = rateSummary.features.some( + const isPetRoomSelect = features.some( (feature) => feature.code === RoomPackageCodeEnum.PETR ) @@ -26,17 +35,23 @@ export default function RateSummary({ (pkg) => pkg.code === RoomPackageCodeEnum.PETR ) - const petRoomPrice = petRoomPackage ? petRoomPackage.calculatedPrice : null - const petRoomCurrency = petRoomPackage ? petRoomPackage.currency : null + const petRoomPrice = petRoomPackage?.calculatedPrice ?? null + const petRoomCurrency = petRoomPackage?.currency ?? null + + const checkInDate = new Date(roomsAvailability.checkInDate) + const checkOutDate = new Date(roomsAvailability.checkOutDate) + const nights = Math.ceil( + (checkOutDate.getTime() - checkInDate.getTime()) / (1000 * 60 * 60 * 24) + ) return (
- {rateSummary.roomType} - {rateSummary.priceName} + {roomType} + {priceName}
-
+
{priceToShow?.localPrice.pricePerStay}{" "} {priceToShow?.localPrice.currency} @@ -47,6 +62,38 @@ export default function RateSummary({ {priceToShow?.requestedPrice?.currency}
+
+ + {intl.formatMessage({ id: "Total price" })} + + + {priceToShow?.localPrice.pricePerStay}{" "} + {priceToShow?.localPrice.currency} + + + {intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )} + ,{" "} + {intl.formatMessage( + { id: "booking.adults" }, + { totalAdults: roomsAvailability.occupancy?.adults } + )} + {roomsAvailability.occupancy?.children && ( + <> + ,{" "} + {intl.formatMessage( + { id: "booking.children" }, + { totalChildren: roomsAvailability.occupancy.children } + )} + + )} + +
{isPetRoomSelect && (
@@ -57,7 +104,7 @@ export default function RateSummary({
)} -
diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css index 07e9841b4..5cb5a4229 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css @@ -5,7 +5,7 @@ left: 0; right: 0; background-color: var(--Base-Surface-Primary-light-Normal); - padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5); + padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x5); display: flex; justify-content: space-between; align-items: center; @@ -13,10 +13,50 @@ .summaryPrice { display: flex; + width: 100%; gap: var(--Spacing-x4); } .petInfo { border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); padding-left: var(--Spacing-x2); + display: none; +} + +.summaryText { + display: none; +} + +.summaryPriceTextDesktop { + display: none; +} + +.continueButton { + margin-left: auto; + height: fit-content; + width: 100%; +} + +.summaryPriceTextMobile { + white-space: nowrap; +} + +@media (min-width: 768px) { + .summary { + padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5); + } + .petInfo, + .summaryText, + .summaryPriceTextDesktop { + display: block; + } + .summaryPriceTextMobile { + display: none; + } + .summaryPrice { + width: auto; + } + .continueButton { + width: auto; + } } diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 3b161d8c7..d1ab92174 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -12,8 +12,8 @@ import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import ImageGallery from "../../ImageGallery" -import RoomSidePeek from "../RoomSidePeek" import { getIconForFeatureCode } from "../../utils" +import RoomSidePeek from "../RoomSidePeek" import styles from "./roomCard.module.css" @@ -26,17 +26,19 @@ export default function RoomCard({ handleSelectRate, }: RoomCardProps) { const intl = useIntl() - const saveRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "NonCancellable" - ) - const changeRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "Modifiable" - ) - const flexRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "CancellableBefore6PM" + + // TODO: Update string when API has decided + const rateTypes = { + saveRate: "NonCancellable", + changeRate: "Modifiable", + flexRate: "CancellableBefore6PM", + } + + const rates = Object.fromEntries( + Object.entries(rateTypes).map(([key, rule]) => [ + key, + rateDefinitions.find((rate) => rate.cancellationRule === rule), + ]) ) function findProductForRate(rate: RateDefinition | undefined) { @@ -49,9 +51,7 @@ export default function RoomCard({ : undefined } - function getPriceInformationForRate( - rate: typeof saveRate | typeof changeRate | typeof flexRate - ) { + function getPriceInformationForRate(rate: RateDefinition | undefined) { return rateDefinitions.find((def) => def.rateCode === rate?.rateCode) ?.generalTerms } @@ -59,10 +59,7 @@ export default function RoomCard({ const selectedRoom = roomCategories.find( (room) => room.name === roomConfiguration.roomType ) - const roomSize = selectedRoom?.roomSize - const occupancy = selectedRoom?.occupancy.total - const roomDescription = selectedRoom?.descriptions.short - const images = selectedRoom?.images + const { roomSize, occupancy, descriptions, images } = selectedRoom || {} const mainImage = images?.[0] return ( @@ -74,7 +71,7 @@ export default function RoomCard({ { id: "booking.guests", }, - { nrOfGuests: occupancy } + { nrOfGuests: occupancy?.total } )} @@ -93,7 +90,7 @@ export default function RoomCard({ {roomConfiguration.roomType} - {roomDescription} + {descriptions?.short}
{intl.formatMessage({ @@ -101,39 +98,29 @@ export default function RoomCard({ })}
- - - + {Object.entries(rates).map(([key, rate]) => ( + + ))}
diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 351efd5b3..3c0305190 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -1,6 +1,6 @@ "use client" import { useRouter, useSearchParams } from "next/navigation" -import { useState } from "react" +import { useMemo,useState } from "react" import RateSummary from "./RateSummary" import RoomCard from "./RoomCard" @@ -23,27 +23,32 @@ export default function RoomSelection({ const searchParams = useSearchParams() const isUserLoggedIn = !!user - function handleSubmit(e: React.FormEvent) { - e.preventDefault() - const searchParamsObject = getHotelReservationQueryParams(searchParams) + const { roomConfigurations, rateDefinitions } = roomsAvailability - const queryParams = new URLSearchParams(searchParams) + const queryParams = useMemo(() => { + const params = new URLSearchParams(searchParams) + const searchParamsObject = getHotelReservationQueryParams(searchParams) searchParamsObject.room.forEach((item, index) => { if (rateSummary?.roomTypeCode) { - queryParams.set(`room[${index}].roomtype`, rateSummary.roomTypeCode) + params.set(`room[${index}].roomtype`, rateSummary.roomTypeCode) } if (rateSummary?.public?.rateCode) { - queryParams.set(`room[${index}].ratecode`, rateSummary.public.rateCode) + params.set(`room[${index}].ratecode`, rateSummary.public.rateCode) } if (rateSummary?.member?.rateCode) { - queryParams.set( + params.set( `room[${index}].counterratecode`, rateSummary.member.rateCode ) } }) + return params + }, [searchParams, rateSummary]) + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() router.push(`select-bed?${queryParams}`) } @@ -55,10 +60,10 @@ export default function RoomSelection({ onSubmit={handleSubmit} >