diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/PriceDetailsTable/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/PriceDetailsTable/index.tsx index 8c1dbd1aa..085429a63 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/PriceDetailsTable/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/PriceDetailsTable/index.tsx @@ -100,6 +100,10 @@ export default function PriceDetailsTable({ const isMainRoom = idx === 0 const getMemberRate = isMainRoom && isMember + if (!room) { + return null + } + let price if ( getMemberRate && @@ -130,6 +134,17 @@ export default function PriceDetailsTable({ price.localPrice.currency )} /> + {room.packages?.map((pkg) => ( + + ))} - isBookingCodeRate(r.roomRate) + const containsBookingCodeRate = rooms.find( + (r) => r && isBookingCodeRate(r.roomRate) ) const showDiscounted = containsBookingCodeRate || isMember @@ -93,6 +94,10 @@ export default function Summary({ {rooms.map((room, idx) => { + if (!room) { + return null + } + const roomNumber = idx + 1 const adults = room.adults const childrenInRoom = room.childrenInRoom @@ -137,6 +142,8 @@ export default function Summary({ guestsParts.push(childrenMsg) } + const roomPackages = room.packages + return (
) : null} + {roomPackages?.map((pkg) => ( +
+
+ {pkg.description} +
+ + + {formatPrice( + intl, + pkg.localPrice.price, + pkg.localPrice.currency + )} + +
+ ))}
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx index df642f60d..ae9bbf012 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/index.tsx @@ -27,14 +27,21 @@ export default function MobileSummary({ const scrollY = useRef(0) const [isSummaryOpen, setIsSummaryOpen] = useState(false) - const { booking, bookingRooms, roomsAvailability, rateSummary, vat } = - useRatesStore((state) => ({ - booking: state.booking, - bookingRooms: state.booking.rooms, - roomsAvailability: state.roomsAvailability, - rateSummary: state.rateSummary, - vat: state.vat, - })) + const { + booking, + bookingRooms, + roomsAvailability, + rateSummary, + vat, + packages, + } = useRatesStore((state) => ({ + booking: state.booking, + bookingRooms: state.booking.rooms, + roomsAvailability: state.roomsAvailability, + rateSummary: state.rateSummary, + vat: state.vat, + packages: state.packages, + })) function toggleSummaryOpen() { setIsSummaryOpen(!isSummaryOpen) @@ -71,11 +78,11 @@ export default function MobileSummary({ } const rooms = rateSummary.map((room, index) => - mapRate(room, index, bookingRooms) + room ? mapRate(room, index, bookingRooms, packages) : null ) - const containsBookingCodeRate = rateSummary.find((r) => - isBookingCodeRate(r.product) + const containsBookingCodeRate = rateSummary.find( + (r) => r && isBookingCodeRate(r.product) ) const showDiscounted = containsBookingCodeRate || isUserLoggedIn diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mapRate.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mapRate.ts index 5b600a807..6c9b05218 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mapRate.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mapRate.ts @@ -3,8 +3,18 @@ import type { Room, } from "@/types/components/hotelReservation/selectRate/selectRate" import { CurrencyEnum } from "@/types/enums/currency" +import type { Packages } from "@/types/requests/packages" + +export function mapRate( + room: Rate, + index: number, + bookingRooms: Room[], + packages: NonNullable +) { + const roomPackages = room.packages + .map((code) => packages.find((pkg) => pkg.code === code)) + .filter((pkg): pkg is NonNullable => Boolean(pkg)) -export function mapRate(room: Rate, index: number, bookingRooms: Room[]) { const rate = { adults: bookingRooms[index].adults, cancellationText: room.product.rateDefinition?.cancellationText ?? "", @@ -29,6 +39,7 @@ export function mapRate(room: Rate, index: number, bookingRooms: Room[]) { }, roomRate: room.product, roomType: room.roomType, + packages: roomPackages, } if ("corporateCheque" in room.product) { diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx index fea0cda89..64ea46345 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx @@ -16,16 +16,10 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { formatPrice } from "@/utils/numberFormatting" import MobileSummary from "./MobileSummary" -import { - calculateCorporateChequePrice, - calculateRedemptionTotalPrice, - calculateTotalPrice, - calculateVoucherPrice, -} from "./utils" +import { getTotalPrice } from "./utils" import styles from "./rateSummary.module.css" -import type { Price } from "@/types/components/hotelReservation/price" import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RateEnum } from "@/types/enums/rate" @@ -96,9 +90,10 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) { const summaryPriceText = `${totalNights}, ${totalAdults}${totalChildren}, ${totalRooms}` const totalRoomsRequired = bookingRooms.length - const isAllRoomsSelected = rateSummary.length === totalRoomsRequired + const isAllRoomsSelected = + rateSummary.filter((rate) => rate !== null).length === totalRoomsRequired const hasMemberRates = rateSummary.some( - (room) => "member" in room.product && room.product.member + (room) => room && "member" in room.product && room.product.member ) const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn @@ -134,12 +129,15 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) { const isBookingCodeRate = rateSummary.some( (rate) => + rate && "public" in rate.product && rate.product.public?.rateType !== RateTypeEnum.Regular ) - const isVoucherRate = rateSummary.some((rate) => "voucher" in rate.product) + const isVoucherRate = rateSummary.some( + (rate) => rate && "voucher" in rate.product + ) const isCorporateChequeRate = rateSummary.some( - (rate) => "corporateCheque" in rate.product + (rate) => rate && "corporateCheque" in rate.product ) const showDiscounted = isUserLoggedIn || @@ -148,37 +146,29 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) { isCorporateChequeRate const mainRoomProduct = rateSummary[0] - let totalPriceToShow: Price - if ("redemption" in mainRoomProduct.product) { - // In case of reward night (redemption) only single room booking is supported by business rules - totalPriceToShow = calculateRedemptionTotalPrice( - mainRoomProduct.product.redemption - ) - } else if ("voucher" in mainRoomProduct.product) { - totalPriceToShow = calculateVoucherPrice(rateSummary) - } else if ("corporateCheque" in mainRoomProduct.product) { - totalPriceToShow = calculateCorporateChequePrice(rateSummary) - } else { - totalPriceToShow = calculateTotalPrice( - rateSummary, - isUserLoggedIn, - petRoomPackage - ) + const totalPriceToShow = getTotalPrice( + mainRoomProduct, + rateSummary, + isUserLoggedIn, + petRoomPackage + ) + + const rateProduct = rateSummary.find((rate) => rate?.product)?.product + + if (!totalPriceToShow || !rateProduct) { + return null } let mainRoomCurrency = "" - if ( - "member" in mainRoomProduct.product && - mainRoomProduct.product.member?.localPrice - ) { - mainRoomCurrency = mainRoomProduct.product.member.localPrice.currency + if ("member" in rateProduct && rateProduct.member?.localPrice) { + mainRoomCurrency = rateProduct.member.localPrice.currency } if ( !mainRoomCurrency && - "public" in mainRoomProduct.product && - mainRoomProduct.product.public?.localPrice + "public" in rateProduct && + rateProduct.public?.localPrice ) { - mainRoomCurrency = mainRoomProduct.product.public.localPrice.currency + mainRoomCurrency = rateProduct.public.localPrice.currency } return ( @@ -186,38 +176,56 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
- {rateSummary.map((room, index) => ( -
- {rateSummary.length > 1 ? ( - <> - + {rateSummary.map((room, index) => { + if (!room) { + return ( +
+ {intl.formatMessage( { id: "Room {roomIndex}" }, { roomIndex: index + 1 } )} - {room.roomType} - - {getRateDetails(room.rate)} - - - ) : ( - <> - - {room.roomType} - - - {getRateDetails(room.rate)} + + {intl.formatMessage({ id: "Select room" })} - - )} -
- ))} +
+ ) + } + + return ( +
+ {rateSummary.length > 1 ? ( + <> + + {intl.formatMessage( + { id: "Room {roomIndex}" }, + { roomIndex: index + 1 } + )} + + {room.roomType} + + {getRateDetails(room.rate)} + + + ) : ( + <> + + {room.roomType} + + + {getRateDetails(room.rate)} + + + )} +
+ ) + })} {/* Render unselected rooms */} {Array.from({ length: totalRoomsRequired - rateSummary.length, }).map((_, index) => ( -
+
{intl.formatMessage( { id: "Room {roomIndex}" }, @@ -235,47 +243,45 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
{ - const memberExists = - "member" in product && product.member - const publicExists = - "public" in product && product.public - if (!memberExists) { - if (!publicExists) { - return total - } - } + amount: rateSummary.reduce((total, rate) => { + if (!rate) { + return total + } - const price = - product.member?.localPrice.pricePerStay || - product.public?.localPrice.pricePerStay + const { features, packages: roomPackages, product } = rate - if (!price) { + const memberExists = "member" in product && product.member + const publicExists = "public" in product && product.public + if (!memberExists) { + if (!publicExists) { return total } + } - const hasSelectedPetRoom = roomPackages.includes( - RoomPackageCodeEnum.PET_ROOM - ) - if (!hasSelectedPetRoom) { - return total + price - } - const isPetRoom = features.find( - (feature) => - feature.code === RoomPackageCodeEnum.PET_ROOM - ) - const petRoomPrice = - isPetRoom && petRoomPackage - ? Number(petRoomPackage.localPrice.totalPrice) - : 0 - return total + price + petRoomPrice - }, - 0 - ), + const price = + product.member?.localPrice.pricePerStay || + product.public?.localPrice.pricePerStay + + if (!price) { + return total + } + + const hasSelectedPetRoom = roomPackages.includes( + RoomPackageCodeEnum.PET_ROOM + ) + if (!hasSelectedPetRoom) { + return total + price + } + const isPetRoom = features.find( + (feature) => + feature.code === RoomPackageCodeEnum.PET_ROOM + ) + const petRoomPrice = + isPetRoom && petRoomPackage + ? Number(petRoomPackage.localPrice.totalPrice) + : 0 + return total + price + petRoomPrice + }, 0), currency: mainRoomCurrency, }} /> diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts index 3ae2146ba..eb78b198c 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts @@ -5,6 +5,7 @@ import { } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" import { CurrencyEnum } from "@/types/enums/currency" +import type { Packages } from "@/types/requests/packages" import type { RedemptionProduct } from "@/types/trpc/routers/hotel/roomAvailability" export function calculateTotalPrice( @@ -194,3 +195,32 @@ export function calculateCorporateChequePrice(selectedRateSummary: Rate[]) { } ) } + +export function getTotalPrice( + mainRoomProduct: Rate | null, + rateSummary: Array, + isUserLoggedIn: boolean, + petRoomPackage: NonNullable[number] | undefined +): Price | null { + const summaryArray = rateSummary.filter((rate): rate is Rate => rate !== null) + + if (summaryArray.some((rate) => "corporateCheque" in rate.product)) { + return calculateCorporateChequePrice(summaryArray) + } + + if (!mainRoomProduct) { + return calculateTotalPrice(summaryArray, isUserLoggedIn, petRoomPackage) + } + + const { product } = mainRoomProduct + + // In case of reward night (redemption) or voucher only single room booking is supported by business rules + if ("redemption" in product) { + return calculateRedemptionTotalPrice(product.redemption) + } + if ("voucher" in product) { + return calculateVoucherPrice(summaryArray) + } + + return calculateTotalPrice(summaryArray, isUserLoggedIn, petRoomPackage) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx index d2fc810f4..e6286acdc 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx @@ -20,9 +20,10 @@ import { RateEnum } from "@/types/enums/rate" export default function SelectedRoomPanel() { const intl = useIntl() - const { isUserLoggedIn, roomCategories } = useRatesStore((state) => ({ + const { isUserLoggedIn, roomCategories, rooms } = useRatesStore((state) => ({ isUserLoggedIn: state.isUserLoggedIn, roomCategories: state.roomCategories, + rooms: state.rooms, })) const { actions: { modifyRate }, @@ -89,6 +90,10 @@ export default function SelectedRoomPanel() { return null } + const showModifyButton = + isMainRoom || + (!isMainRoom && rooms.slice(0, roomNr).every((room) => room.selectedRate)) + return (
@@ -118,14 +123,16 @@ export default function SelectedRoomPanel() { width={600} /> ) : null} -
- -
+ {showModifyButton && ( +
+ +
+ )}
) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/checkbox.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/checkbox.module.css index 0d19d9c37..9ab174d8e 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/checkbox.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/checkbox.module.css @@ -32,6 +32,15 @@ background-color: var(--UI-Input-Controls-Fill-Selected); } +.checkboxWrapper[data-disabled] .checkbox { + border-color: var(--UI-Input-Controls-Border-Disabled); + background-color: var(--UI-Input-Controls-Surface-Disabled); +} + +.checkboxWrapper[data-disabled] .text { + color: var(--Base-Text-Disabled); +} + @media screen and (max-width: 767px) { .checkboxWrapper:hover { background-color: transparent; diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/index.tsx index 4ea6a91e8..6fc58734f 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/Checkbox/index.tsx @@ -14,6 +14,7 @@ interface CheckboxProps { value: string isSelected: boolean iconName: MaterialSymbolProps["icon"] + isDisabled: boolean onChange: (value: string) => void } @@ -22,12 +23,14 @@ export default function Checkbox({ name, value, iconName, + isDisabled, onChange, }: CheckboxProps) { return ( onChange(value)} > {({ isSelected }) => ( @@ -35,7 +38,10 @@ export default function Checkbox({ {isSelected && } - + {name} {iconName ? ( diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/index.tsx index 8efa5f200..fd536780e 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/index.tsx @@ -1,7 +1,15 @@ "use client" -import { Button, Dialog, DialogTrigger, Popover } from "react-aria-components" +import { useEffect, useState } from "react" +import { + Button as AriaButton, + Dialog, + DialogTrigger, + Popover, +} from "react-aria-components" +import { Controller, useForm } from "react-hook-form" import { useIntl } from "react-intl" +import { Button } from "@scandic-hotels/design-system/Button" import { ChipButton } from "@scandic-hotels/design-system/ChipButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" @@ -16,20 +24,46 @@ import { getIconNameByPackageCode } from "./utils" import styles from "./roomPackageFilter.module.css" +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" + +type FormValues = { + selectedPackages: RoomPackageCodeEnum[] +} + export default function RoomPackageFilter() { + const intl = useIntl() + + const [isOpen, setIsOpen] = useState(false) const packageOptions = useRatesStore((state) => state.packageOptions) const { - actions: { togglePackage }, + actions: { togglePackages }, selectedPackages, } = useRoomContext() - const intl = useIntl() + + const { setValue, handleSubmit, control } = useForm({ + defaultValues: { + selectedPackages: selectedPackages, + }, + }) + + useEffect(() => { + setValue("selectedPackages", selectedPackages) + }, [selectedPackages, setValue]) + + function onSubmit(data: FormValues) { + togglePackages(data.selectedPackages) + setIsOpen(false) + } return (
{selectedPackages.map((pkg) => ( - + ))} - + {intl.formatMessage({ id: "Room preferences" })} -
- {packageOptions.map((option) => ( - togglePackage(option.code)} - /> - ))} -
-
- - -

- {intl.formatMessage( - { - id: "200 SEK/night Important information on pricing and features of pet-friendly rooms.", - }, - { - b: (str) => ( - - - {str} - - - ), - } - )} -

-
-
+
+ ( +
+ {packageOptions.map((option) => { + const isPetRoom = + option.code === RoomPackageCodeEnum.PET_ROOM + + const isAllergyRoom = + option.code === RoomPackageCodeEnum.ALLERGY_ROOM + + const hasPetRoom = field.value.includes( + RoomPackageCodeEnum.PET_ROOM + ) + + const hasAllergyRoom = field.value.includes( + RoomPackageCodeEnum.ALLERGY_ROOM + ) + + const isDisabled = + (isPetRoom && hasAllergyRoom) || + (isAllergyRoom && hasPetRoom) + + return ( + <> + { + const isSelected = field.value.includes( + option.code + ) + const newValue = isSelected + ? field.value.filter( + (pkg) => pkg !== option.code + ) + : [...field.value, option.code] + field.onChange(newValue) + }} + /> + {option.code === RoomPackageCodeEnum.PET_ROOM && ( + +

+ {intl.formatMessage( + { + id: "200 SEK/night Important information on pricing and features of pet-friendly rooms.", + }, + { + b: (str) => ( + + + {str} + + + ), + } + )} +

+
+ )} + + ) + })} +
+ )} + /> +
+ +
+ + + + + + +
+
+
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/roomPackageFilter.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/roomPackageFilter.module.css index c20ffca67..f16475506 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/roomPackageFilter.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomPackageFilter/roomPackageFilter.module.css @@ -23,6 +23,7 @@ .additionalInformation { color: var(--Text-Tertiary); + padding: var(--Space-x1) var(--Space-x15); } .additionalInformationPrice { @@ -40,3 +41,9 @@ border-width: 0; cursor: pointer; } + +.buttonContainer { + display: flex; + justify-content: space-between; + align-items: center; +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx new file mode 100644 index 000000000..c90ac9037 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx @@ -0,0 +1,19 @@ +import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" + +import styles from "./roomsListSkeleton.module.css" + +type Props = { + count?: number +} + +export function RoomsListSkeleton({ count = 4 }: Props) { + return ( +
+
+ {Array.from({ length: count }).map((_, index) => ( + + ))} +
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/index.tsx index 42f875235..696e42f39 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/index.tsx @@ -2,12 +2,18 @@ import { useRoomContext } from "@/contexts/SelectRate/Room" import RoomListItem from "./RoomListItem" +import { RoomsListSkeleton } from "./RoomsListSkeleton" import ScrollToList from "./ScrollToList" import styles from "./rooms.module.css" export default function RoomsList() { - const { rooms } = useRoomContext() + const { rooms, isFetchingRoomFeatures } = useRoomContext() + + if (isFetchingRoomFeatures) { + return + } + return ( <> diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/rooms.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/rooms.module.css index be8911d82..fab1703c8 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/rooms.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/rooms.module.css @@ -3,6 +3,7 @@ display: grid; gap: var(--Spacing-x2); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + overflow: hidden; } .roomList > li { diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css new file mode 100644 index 000000000..23ccc3f2c --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css @@ -0,0 +1,17 @@ +.container { + max-width: var(--max-width-page); +} + +.skeletonContainer { + display: grid; + + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + /* used to hide overflowing rows */ + grid-template-rows: auto; + grid-auto-rows: 0; + overflow: hidden; + + flex-wrap: wrap; + justify-content: space-between; + gap: var(--Spacing-x2); +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx index 2ccecc808..6593c9fd1 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx @@ -1,5 +1,5 @@ "use client" -import { REDEMPTION } from "@/constants/booking" + import { dt } from "@/lib/dt" import useLang from "@/hooks/useLang" @@ -25,9 +25,6 @@ export function RoomsContainer({ const fromDateString = dt(fromDate).format("YYYY-MM-DD") const toDateString = dt(toDate).format("YYYY-MM-DD") - const redemption = booking.searchType - ? booking.searchType === REDEMPTION - : undefined const { data: roomsAvailability, isPending: isLoadingAvailability } = useRoomsAvailability( @@ -37,8 +34,7 @@ export function RoomsContainer({ toDateString, lang, childArray, - booking.bookingCode, - redemption + booking ) const { data: packages, isPending: isLoadingPackages } = useHotelPackages( diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/utils.ts index 54cfee553..9cead0710 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/utils.ts @@ -1,10 +1,6 @@ -import { useSearchParams } from "next/navigation" -import { useEffect, useMemo, useState } from "react" - +import { REDEMPTION } from "@/constants/booking" import { trpc } from "@/lib/trpc/client" -import { convertSearchParamsToObj, searchParamsToRecord } from "@/utils/url" - import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import type { Lang } from "@/constants/languages" @@ -17,79 +13,30 @@ export function useRoomsAvailability( toDateString: string, lang: Lang, childArray: ChildrenInRoom, - bookingCode?: string, - redemption?: boolean + booking: SelectRateSearchParams ) { - const searchParams = useSearchParams() - const searchParamsObj = convertSearchParamsToObj( - searchParamsToRecord(searchParams) + const redemption = booking.searchType + ? booking.searchType === REDEMPTION + : undefined + + const roomFeatureCodesArray = booking.rooms.map( + (room) => room.packages ?? null ) - const hasPackagesParam = searchParamsObj.rooms.some((room) => room.packages) - const [hasRoomFeatures, setHasRoomFeatures] = useState(hasPackagesParam) - - useEffect(() => { - setHasRoomFeatures(hasPackagesParam) - }, [hasPackagesParam, setHasRoomFeatures]) - - const { data: roomFeatures, isPending: isRoomFeaturesPending } = - trpc.hotel.availability.roomFeatures.useQuery( - { - hotelId, - startDate: fromDateString, - endDate: toDateString, - adultsCount, - childArray, - }, - { - enabled: hasRoomFeatures, - } - ) - - const { data: roomsAvailability, isPending: isRoomsAvailabiltyPending } = + const roomsAvailability = trpc.hotel.availability.roomsCombinedAvailability.useQuery({ adultsCount, - bookingCode, childArray, hotelId, lang, redemption, roomStayEndDate: toDateString, roomStayStartDate: fromDateString, + bookingCode: booking.bookingCode, + roomFeatureCodesArray, }) - const combinedData = useMemo(() => { - if (!roomsAvailability) { - return undefined - } - if (!roomFeatures) { - return roomsAvailability - } - - return roomsAvailability.map((room, idx) => { - if ("error" in room) { - return room - } - - return { - ...room, - roomConfigurations: room.roomConfigurations.map((config) => ({ - ...config, - features: - roomFeatures?.[idx]?.find( - (r) => r.roomTypeCode === config.roomTypeCode - )?.features ?? [], - })), - } - }) - }, [roomFeatures, roomsAvailability]) - - return { - data: combinedData, - isPending: hasRoomFeatures - ? isRoomsAvailabiltyPending || isRoomFeaturesPending - : isRoomsAvailabiltyPending, - } + return roomsAvailability } export function useHotelPackages( diff --git a/apps/scandic-web/i18n/dictionaries/da.json b/apps/scandic-web/i18n/dictionaries/da.json index 9ef0d66a1..1ab5ed503 100644 --- a/apps/scandic-web/i18n/dictionaries/da.json +++ b/apps/scandic-web/i18n/dictionaries/da.json @@ -186,6 +186,7 @@ "City pulse": "Byens puls", "City/State": "By/Stat", "Classroom": "Classroom", + "Clear": "Ryd", "Clear all filters": "Ryd alle filtre", "Clear searches": "Ryd søgninger", "Click here to log in": "Klik her for at logge ind", diff --git a/apps/scandic-web/i18n/dictionaries/de.json b/apps/scandic-web/i18n/dictionaries/de.json index af6ed871f..9296bfbc7 100644 --- a/apps/scandic-web/i18n/dictionaries/de.json +++ b/apps/scandic-web/i18n/dictionaries/de.json @@ -187,6 +187,7 @@ "City pulse": "Stadtpuls", "City/State": "Stadt/Zustand", "Classroom": "Classroom", + "Clear": "Löschen", "Clear all filters": "Alle Filter löschen", "Clear searches": "Suche löschen", "Click here to log in": "Klicken Sie hier, um sich einzuloggen", diff --git a/apps/scandic-web/i18n/dictionaries/en.json b/apps/scandic-web/i18n/dictionaries/en.json index acba9b742..33a647a6e 100644 --- a/apps/scandic-web/i18n/dictionaries/en.json +++ b/apps/scandic-web/i18n/dictionaries/en.json @@ -187,6 +187,7 @@ "City pulse": "City pulse", "City/State": "City/State", "Classroom": "Classroom", + "Clear": "Clear", "Clear all filters": "Clear all filters", "Clear searches": "Clear searches", "Click here to log in": "Click here to log in", diff --git a/apps/scandic-web/i18n/dictionaries/fi.json b/apps/scandic-web/i18n/dictionaries/fi.json index 52810a326..ff1ea771a 100644 --- a/apps/scandic-web/i18n/dictionaries/fi.json +++ b/apps/scandic-web/i18n/dictionaries/fi.json @@ -185,6 +185,7 @@ "City pulse": "Kaupungin syke", "City/State": "Kaupunki/Osavaltio", "Classroom": "Classroom", + "Clear": "Tyhjennä", "Clear all filters": "Tyhjennä kaikki suodattimet", "Clear searches": "Tyhjennä haut", "Click here to log in": "Napsauta tästä kirjautuaksesi sisään", diff --git a/apps/scandic-web/i18n/dictionaries/no.json b/apps/scandic-web/i18n/dictionaries/no.json index 4a081941e..b43eb643c 100644 --- a/apps/scandic-web/i18n/dictionaries/no.json +++ b/apps/scandic-web/i18n/dictionaries/no.json @@ -185,6 +185,7 @@ "City pulse": "Byens puls", "City/State": "By/Stat", "Classroom": "Classroom", + "Clear": "Rens", "Clear all filters": "Fjern alle filtre", "Clear searches": "Tømme søk", "Click here to log in": "Klikk her for å logge inn", diff --git a/apps/scandic-web/i18n/dictionaries/sv.json b/apps/scandic-web/i18n/dictionaries/sv.json index 103dd0f8d..2ba73e58a 100644 --- a/apps/scandic-web/i18n/dictionaries/sv.json +++ b/apps/scandic-web/i18n/dictionaries/sv.json @@ -185,6 +185,7 @@ "City pulse": "Stadspuls", "City/State": "Ort", "Classroom": "Klassrum", + "Clear": "Rensa", "Clear all filters": "Rensa alla filter", "Clear searches": "Rensa tidigare sökningar", "Click here to log in": "Klicka här för att logga in", diff --git a/apps/scandic-web/providers/SelectRate/RoomProvider.tsx b/apps/scandic-web/providers/SelectRate/RoomProvider.tsx index 204f750da..02665527e 100644 --- a/apps/scandic-web/providers/SelectRate/RoomProvider.tsx +++ b/apps/scandic-web/providers/SelectRate/RoomProvider.tsx @@ -24,14 +24,16 @@ export default function RoomProvider({ roomAvailability, searchParams, selectedFilter, + selectedPackages, } = useRatesStore((state) => ({ activeRoom: state.activeRoom, booking: state.booking, roomAvailability: state.roomsAvailability?.[idx], searchParams: state.searchParams, selectedFilter: state.rooms[idx].selectedFilter, + selectedPackages: state.rooms[idx].selectedPackages, })) - const { appendRegularRates, ...actions } = room.actions + const { appendRegularRates, addRoomFeatures, ...actions } = room.actions const roomNr = idx + 1 const redemptionSearch = searchParams.has("searchType") @@ -91,6 +93,40 @@ export default function RoomProvider({ } }, [appendRegularRates, data, enabled, isFetched, isFetching]) + const { + data: roomFeaturesData, + isFetched: isRoomFeaturesFetched, + isFetching: isRoomFeaturesFetching, + } = trpc.hotel.availability.roomFeatures.useQuery( + { + adults: room.bookingRoom.adults, + childrenInRoom: room.bookingRoom.childrenInRoom, + hotelId: booking.hotelId, + startDate: booking.fromDate, + endDate: booking.toDate, + roomFeatureCodes: selectedPackages, + roomIndex: idx, // Creates a unique query key for each room + }, + { + enabled: !!selectedPackages.length, + } + ) + + useEffect(() => { + if ( + isRoomFeaturesFetched && + !isRoomFeaturesFetching && + roomFeaturesData?.length + ) { + addRoomFeatures(roomFeaturesData) + } + }, [ + addRoomFeatures, + roomFeaturesData, + isRoomFeaturesFetched, + isRoomFeaturesFetching, + ]) + return ( diff --git a/apps/scandic-web/server/routers/hotels/query.ts b/apps/scandic-web/server/routers/hotels/query.ts index f8cadd5ae..f8e2ac832 100644 --- a/apps/scandic-web/server/routers/hotels/query.ts +++ b/apps/scandic-web/server/routers/hotels/query.ts @@ -37,6 +37,7 @@ import { hotelsAvailabilityInputSchema, nearbyHotelIdsInput, ratesInputSchema, + type RoomFeaturesInput, roomFeaturesInputSchema, roomPackagesInputSchema, roomsCombinedAvailabilityInputSchema, @@ -471,6 +472,75 @@ export const getHotelsAvailabilityByHotelIds = async ( ) } +async function getRoomFeatures( + { + hotelId, + startDate, + endDate, + adults, + childrenInRoom, + roomFeatureCodes, + }: RoomFeaturesInput, + token: string +) { + const params = { + hotelId, + roomStayStartDate: startDate, + roomStayEndDate: endDate, + adults, + ...(childrenInRoom?.length && { + children: generateChildrenString(childrenInRoom), + }), + roomFeatureCode: roomFeatureCodes, + } + + metrics.roomFeatures.counter.add(1, params) + + const apiResponse = await api.get( + api.endpoints.v1.Availability.roomFeatures(hotelId), + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + params + ) + + if (!apiResponse.ok) { + const text = apiResponse.text() + console.error( + "api.availability.roomfeature error", + JSON.stringify({ + query: { hotelId, params }, + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }, + }) + ) + metrics.roomFeatures.fail.add(1, params) + return null + } + + const data = await apiResponse.json() + const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data) + if (!validatedRoomFeaturesData.success) { + console.error( + "api.availability.roomfeature error", + JSON.stringify({ + query: { hotelId, params }, + error: validatedRoomFeaturesData.error, + }) + ) + return null + } + + metrics.roomFeatures.success.add(1, params) + + return validatedRoomFeaturesData.data +} + export const hotelQueryRouter = router({ availability: router({ hotelsByCity: safeProtectedServiceProcedure @@ -576,6 +646,7 @@ export const hotelQueryRouter = router({ redemption, roomStayEndDate, roomStayStartDate, + roomFeatureCodesArray, }, }) => { const apiLang = toApiLang(lang) @@ -643,6 +714,35 @@ export const hotelQueryRouter = router({ } } + const roomFeatureCodes = roomFeatureCodesArray?.[idx] + if (roomFeatureCodes?.length) { + const roomFeaturesResponse = await getRoomFeatures( + { + hotelId, + startDate: roomStayStartDate, + endDate: roomStayEndDate, + adults: adultCount, + childrenInRoom: kids ?? undefined, + roomFeatureCodes, + }, + ctx.serviceToken + ) + + if (roomFeaturesResponse) { + validateAvailabilityData.data.roomConfigurations.forEach( + (room) => { + const features = roomFeaturesResponse.find( + (feat) => feat.roomTypeCode === room.roomTypeCode + )?.features + + if (features) { + room.features = features + } + } + ) + } + } + if (rateCode) { validateAvailabilityData.data.mustBeGuaranteed = validateAvailabilityData.data.rateDefinitions.find( @@ -982,73 +1082,7 @@ export const hotelQueryRouter = router({ roomFeatures: serviceProcedure .input(roomFeaturesInputSchema) .query(async ({ input, ctx }) => { - const { hotelId, startDate, endDate, adultsCount, childArray } = input - - const responses = await Promise.allSettled( - adultsCount.map(async (adultCount, index) => { - const kids = childArray?.[index] - const params = { - hotelId, - roomStayStartDate: startDate, - roomStayEndDate: endDate, - adults: adultCount, - ...(kids?.length && { children: generateChildrenString(kids) }), - } - - metrics.roomFeatures.counter.add(1, params) - - const apiResponse = await api.get( - api.endpoints.v1.Availability.roomFeatures(hotelId), - { - headers: { - Authorization: `Bearer ${ctx.serviceToken}`, - }, - }, - params - ) - - if (!apiResponse.ok) { - const text = apiResponse.text() - console.error( - "api.availability.roomfeature error", - JSON.stringify({ - query: { hotelId, params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - metrics.roomFeatures.fail.add(1, params) - return null - } - - const data = await apiResponse.json() - const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data) - if (!validatedRoomFeaturesData.success) { - console.error( - "api.availability.roomfeature error", - JSON.stringify({ - query: { hotelId, params }, - error: validatedRoomFeaturesData.error, - }) - ) - return null - } - - metrics.roomFeatures.success.add(1, params) - - return validatedRoomFeaturesData.data - }) - ) - - return responses.map((features) => { - if (features.status === "fulfilled") { - return features.value - } - return null - }) + return await getRoomFeatures(input, ctx.serviceToken) }), }), rates: router({ diff --git a/apps/scandic-web/stores/select-rate/helpers.ts b/apps/scandic-web/stores/select-rate/helpers.ts index c416c5af2..1adf5ff19 100644 --- a/apps/scandic-web/stores/select-rate/helpers.ts +++ b/apps/scandic-web/stores/select-rate/helpers.ts @@ -106,3 +106,16 @@ export function isRoomPackageCode( code as RoomPackageCodeEnum ) } + +export function filterRoomsBySelectedPackages( + selectedPackages: RoomPackageCodeEnum[], + rooms: RoomConfiguration[] +) { + if (!selectedPackages.length) { + return rooms + } + + return rooms.filter((r) => + selectedPackages.every((pkg) => r.features.find((f) => f.code === pkg)) + ) +} diff --git a/apps/scandic-web/stores/select-rate/index.ts b/apps/scandic-web/stores/select-rate/index.ts index 7906fc12d..841989d02 100644 --- a/apps/scandic-web/stores/select-rate/index.ts +++ b/apps/scandic-web/stores/select-rate/index.ts @@ -6,9 +6,9 @@ import { create, useStore } from "zustand" import { RatesContext } from "@/contexts/Rates" import { + filterRoomsBySelectedPackages, findProductInRoom, findSelectedRate, - isRoomPackageCode, } from "./helpers" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" @@ -72,11 +72,18 @@ export function createRatesStore({ for (const [idx, room] of booking.rooms.entries()) { if (room.rateCode && room.roomTypeCode) { const roomConfiguration = roomConfigurations?.[idx] + const selectedPackages = room.packages ?? [] + + let rooms: RoomConfiguration[] = filterRoomsBySelectedPackages( + selectedPackages, + roomConfiguration + ) + const selectedRoom = findSelectedRate( room.rateCode, room.counterRateCode, room.roomTypeCode, - roomConfiguration + rooms ) if (!selectedRoom) { @@ -97,6 +104,8 @@ export function createRatesStore({ roomType: selectedRoom.roomType, roomTypeCode: selectedRoom.roomTypeCode, } + } else { + rateSummary[idx] = null } } } @@ -105,9 +114,10 @@ export function createRatesStore({ if (searchParams.has("modifyRateIndex")) { activeRoom = Number(searchParams.get("modifyRateIndex")) } else if (rateSummary.length === booking.rooms.length) { - // Since all rooms has selections, all sections should be - // closed on load - activeRoom = -1 + // Finds the first unselected room and sets that to active + // if no unselected rooms it will return -1 and close all rooms + const unselectedRoomIndex = rateSummary.findIndex((rate) => !rate) + activeRoom = unselectedRoomIndex } return create()((set) => { @@ -126,6 +136,13 @@ export function createRatesStore({ roomConfigurations, rooms: booking.rooms.map((room, idx) => { const roomConfiguration = roomConfigurations[idx] + const selectedPackages = room.packages ?? [] + + let rooms: RoomConfiguration[] = filterRoomsBySelectedPackages( + selectedPackages, + roomConfiguration + ) + const selectedRate = findSelectedRate( room.rateCode, @@ -143,21 +160,6 @@ export function createRatesStore({ ) } - // Since features are fetched async based on query string, we need to read from query string to apply correct filtering - const packagesParam = searchParams.get(`room[${idx}].packages`) - const selectedPackages = packagesParam - ? packagesParam.split(",").filter(isRoomPackageCode) - : [] - - let rooms: RoomConfiguration[] = roomConfiguration - if (selectedPackages.length) { - rooms = roomConfiguration.filter((r) => - selectedPackages.some((pkg) => - r.features.find((f) => f.code === pkg) - ) - ) - } - return { actions: { appendRegularRates(roomConfigurations) { @@ -204,6 +206,48 @@ export function createRatesStore({ }) ) }, + addRoomFeatures(roomFeatures) { + return set( + produce((state: RatesState) => { + const selectedPackages = state.rooms[idx].selectedPackages + const rateSummaryItem = state.rateSummary[idx] + + state.roomConfigurations[idx].forEach((room) => { + const features = roomFeatures.find( + (feat) => feat.roomTypeCode === room.roomTypeCode + )?.features + + if (features) { + room.features = features + + if (rateSummaryItem) { + rateSummaryItem.packages = selectedPackages + rateSummaryItem.features = features + } + } + }) + + state.rateSummary[idx] = rateSummaryItem + + state.rooms[idx].rooms = filterRoomsBySelectedPackages( + selectedPackages, + state.roomConfigurations[idx] + ) + + const selectedRate = findSelectedRate( + room.rateCode, + room.counterRateCode, + room.roomTypeCode, + state.rooms[idx].rooms + ) + + if (!selectedRate) { + state.rooms[idx].selectedRate = null + state.rateSummary[idx] = null + } + }) + ) + }, closeSection() { return set( produce((state: RatesState) => { @@ -229,44 +273,53 @@ export function createRatesStore({ }) ) }, - togglePackage(code) { + togglePackages(selectedPackages) { return set( produce((state: RatesState) => { - const isSelected = - state.rooms[idx].selectedPackages.includes(code) - const selectedPackages = isSelected - ? state.rooms[idx].selectedPackages.filter( - (pkg) => pkg !== code - ) - : [...state.rooms[idx].selectedPackages, code] state.rooms[idx].selectedPackages = selectedPackages + const rateSummaryItem = state.rateSummary[idx] const roomConfiguration = state.roomConfigurations[idx] if (roomConfiguration) { const searchParams = new URLSearchParams(state.searchParams) if (selectedPackages.length) { - state.rooms[idx].rooms = roomConfiguration.filter( - (room) => - selectedPackages.every((pkg) => - room.features.find((feat) => feat.code === pkg) - ) - ) searchParams.set( `room[${idx}].packages`, selectedPackages.join(",") ) - if (state.rateSummary[idx]) { - state.rateSummary[idx].packages = selectedPackages + if (rateSummaryItem) { + rateSummaryItem.packages = selectedPackages } } else { state.rooms[idx].rooms = roomConfiguration - if (state.rateSummary[idx]) { - state.rateSummary[idx].packages = [] + if (rateSummaryItem) { + rateSummaryItem.packages = [] } searchParams.delete(`room[${idx}].packages`) } + // If we already have the features data 'addRoomFeatures' wont run + // so we need to do additional filtering here if thats the case + const filteredRooms = filterRoomsBySelectedPackages( + selectedPackages, + state.roomConfigurations[idx] + ) + + if (filteredRooms.length) { + const selectedRate = findSelectedRate( + room.rateCode, + room.counterRateCode, + room.roomTypeCode, + state.rooms[idx].rooms + ) + + if (!selectedRate) { + state.rooms[idx].selectedRate = null + state.rateSummary[idx] = null + } + } + state.searchParams = new ReadonlyURLSearchParams( searchParams ) diff --git a/apps/scandic-web/types/components/hotelReservation/summary.ts b/apps/scandic-web/types/components/hotelReservation/summary.ts index 8e2a7f6b6..5c293cad2 100644 --- a/apps/scandic-web/types/components/hotelReservation/summary.ts +++ b/apps/scandic-web/types/components/hotelReservation/summary.ts @@ -18,20 +18,21 @@ export interface SummaryProps { isMember: boolean } -export interface SummaryUIProps { +export interface EnterDetailsSummaryProps { booking: SelectRateSearchParams isMember: boolean totalPrice: Price - toggleSummaryOpen: () => void vat: number -} - -export interface EnterDetailsSummaryProps extends SummaryUIProps { rooms: RoomState[] + toggleSummaryOpen: () => void } -export interface SelectRateSummaryProps extends SummaryUIProps { - rooms: { +export interface SelectRateSummaryProps { + booking: SelectRateSearchParams + isMember: boolean + totalPrice: Price + vat: number + rooms: Array<{ adults: number childrenInRoom: Child[] | undefined roomType: string @@ -39,5 +40,7 @@ export interface SelectRateSummaryProps extends SummaryUIProps { roomRate: RoomRate rateDetails: string[] | undefined cancellationText: string - }[] + packages?: Packages + } | null> + toggleSummaryOpen: () => void } diff --git a/apps/scandic-web/types/contexts/select-rate/room.ts b/apps/scandic-web/types/contexts/select-rate/room.ts index a74699f61..9967b5dc3 100644 --- a/apps/scandic-web/types/contexts/select-rate/room.ts +++ b/apps/scandic-web/types/contexts/select-rate/room.ts @@ -1,9 +1,13 @@ import type { RatesState, SelectedRoom } from "@/types/stores/rates" export interface RoomContextValue extends Omit { - actions: Omit + actions: Omit< + SelectedRoom["actions"], + "appendRegularRates" | "addRoomFeatures" + > isActiveRoom: boolean isFetchingAdditionalRate: boolean + isFetchingRoomFeatures: boolean isMainRoom: boolean roomAvailability: | NonNullable[number] diff --git a/apps/scandic-web/types/stores/rates.ts b/apps/scandic-web/types/stores/rates.ts index 3379c4bd9..87448ad31 100644 --- a/apps/scandic-web/types/stores/rates.ts +++ b/apps/scandic-web/types/stores/rates.ts @@ -25,10 +25,16 @@ export interface AvailabilityError { interface Actions { appendRegularRates: (roomConfigurations: RoomConfiguration[]) => void + addRoomFeatures: ( + roomFeatures: { + roomTypeCode: RoomConfiguration["roomTypeCode"] + features: RoomConfiguration["features"] + }[] + ) => void closeSection: () => void modifyRate: () => void selectFilter: (filter: BookingCodeFilterEnum) => void - togglePackage: (code: RoomPackageCodeEnum) => void + togglePackages: (codes: RoomPackageCodeEnum[]) => void selectRate: (rate: SelectedRate) => void } @@ -57,7 +63,7 @@ export interface RatesState { packages: NonNullable pathname: string petRoomPackage: NonNullable[number] | undefined - rateSummary: Rate[] + rateSummary: Array rooms: SelectedRoom[] roomCategories: Room[] roomConfigurations: RoomConfiguration[][]