diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/loading.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/loading.tsx deleted file mode 100644 index 8bb921599..000000000 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/loading.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { HotelInfoCardSkeleton } from "@/components/HotelReservation/SelectRate/HotelInfoCard" -import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton" - -// Select Rate loading doesn't need a layout and wrapper -// to force loading.tsx to show again since refetch of -// availability happens client-side and only the RoomCards -// display a loading state since we already have the hotel -// data -export default function LoadingSelectRate() { - return ( - <> - - - - ) -} diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/page.tsx deleted file mode 100644 index 45d483e4b..000000000 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate-old/page.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { notFound } from "next/navigation" - -import { parseSelectRateSearchParams } from "@scandic-hotels/booking-flow/utils/url" -import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" - -import { combineRegExps, rateTypeRegex } from "@/constants/booking" - -import SelectRate from "@/components/HotelReservation/SelectRate" - -import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" - -const singleRoomRateTypes = combineRegExps( - [rateTypeRegex.ARB, rateTypeRegex.VOUCHER], - "i" -) - -export default async function SelectRatePage( - props: PageArgs -) { - const params = await props.params - const searchParams = await props.searchParams - const booking = parseSelectRateSearchParams(searchParams) - - if (!booking) return notFound() - - const isMultiRoom = booking.rooms.length > 1 - const isRedemption = booking.searchType === SEARCH_TYPE_REDEMPTION - const isArbOrVoucher = booking.bookingCode - ? singleRoomRateTypes.test(booking.bookingCode) - : false - - if ((isMultiRoom && isRedemption) || (isMultiRoom && isArbOrVoucher)) { - return notFound() - } - - // If someone tries to update the url with - // a bookingCode also, then we need to remove it - if (isRedemption && searchParams.bookingCode) { - delete searchParams.bookingCode - } - - return -} diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/loading.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/loading.tsx index 8bb921599..2c48fbec1 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/loading.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/loading.tsx @@ -1,11 +1,6 @@ import { HotelInfoCardSkeleton } from "@/components/HotelReservation/SelectRate/HotelInfoCard" import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton" -// Select Rate loading doesn't need a layout and wrapper -// to force loading.tsx to show again since refetch of -// availability happens client-side and only the RoomCards -// display a loading state since we already have the hotel -// data export default function LoadingSelectRate() { return ( <> diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index c4be283b8..020a26595 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -7,7 +7,7 @@ import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { combineRegExps, rateTypeRegex } from "@/constants/booking" import { getHotel } from "@/lib/trpc/memoizedRequests" -import SelectRate from "@/components/HotelReservation/SelectRate2" +import SelectRate from "@/components/HotelReservation/SelectRate" import { SelectRateProvider } from "@/contexts/SelectRate/SelectRateContext" import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx index 20e5712b2..c3e871858 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx @@ -14,7 +14,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { useEnterDetailsStore } from "@/stores/enter-details" import { formId } from "@/components/HotelReservation/EnterDetails/Payment/PaymentClient" -import { isBookingCodeRate } from "@/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils" +import { isBookingCodeRate } from "@/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils" import styles from "./bottomSheet.module.css" 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 d4850dcc9..2d376b59a 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -16,7 +16,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import BookingCodeChip from "@/components/BookingCodeChip" import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal" -import { isBookingCodeRate } from "@/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils" +import { isBookingCodeRate } from "@/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import useLang from "@/hooks/useLang" diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/hotelDescription.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/hotelDescription.module.css index a6f1bff19..b8dd42ba5 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/hotelDescription.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/hotelDescription.module.css @@ -1,12 +1,11 @@ .hotelDescription { overflow: hidden; - text-align: left; } .descriptionWrapper { display: flex; flex-direction: column; - gap: var(--Space-x2); + align-items: center; } .collapsed { @@ -25,6 +24,7 @@ display: flex; flex-direction: column; align-items: center; + margin-top: var(--Space-x2); } .description { @@ -34,6 +34,7 @@ .showMoreButton { display: flex; + align-items: flex-end; background-color: transparent; border-width: 0; padding: 0; @@ -49,13 +50,16 @@ display: flex; flex-direction: column; gap: var(--Space-x15); + align-items: center; } .facilityList { display: flex; + align-items: flex-start; justify-content: center; flex-wrap: wrap; gap: var(--Space-x15); + padding-bottom: var(--Space-x2); } .facilitiesItem { diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/index.tsx index 7c5334a7d..30a3fdcb4 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/HotelDescription/index.tsx @@ -53,21 +53,22 @@ export default function HotelDescription({ ))} -
- -

- {description} -

-
- - - {expanded ? textShowLess : textShowMore} - - + +

+ {description} +

+
+ + + {expanded ? textShowLess : textShowMore} + + + + {expanded && (
+ {hotel.specialAlerts.map((alert) => ( + + ))}
-
- - {hotel.specialAlerts.map((alert) => ( - - ))} + )} ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx index ddd7fd0b8..a80d1d3b2 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx @@ -17,13 +17,17 @@ import HotelDescription from "./HotelDescription" import styles from "./hotelInfoCard.module.css" -import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard" +import type { Hotel } from "@scandic-hotels/trpc/types/hotel" + +import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate" import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" -export default async function HotelInfoCard({ - booking, - hotel, -}: HotelInfoCardProps) { +export type HotelInfoCardProps = { + booking: SelectRateBooking + hotel: Hotel +} + +export async function HotelInfoCard({ booking, hotel }: HotelInfoCardProps) { const intl = await getIntl() const sortedFacilities = hotel.detailedFacilities @@ -34,7 +38,6 @@ export default async function HotelInfoCard({ const bookingFromDate = dt(booking.fromDate) const bookingToDate = dt(booking.toDate) - const specialAlerts = getHotelAlertsForBookingDates( hotel.specialAlerts, bookingFromDate, @@ -109,22 +112,26 @@ export default async function HotelInfoCard({ - {specialAlerts.map((alert) => { - return ( -
- -
- ) - })} + {specialAlerts.map((alert) => ( + + ))} ) } +function SpecialAlert({ alert }: { alert: Hotel["specialAlerts"][number] }) { + return ( +
+ +
+ ) +} + export function HotelInfoCardSkeleton() { return (
@@ -157,7 +164,7 @@ export function HotelInfoCardSkeleton() { > - {[1, 2, 3, 4, 5].map((id) => { + {[1, 2, 3, 4, 5]?.map((id) => { return (
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/DesktopSummary.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/DesktopSummary.tsx rename to apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx index 785f84aa8..6ff39693b 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx @@ -2,6 +2,7 @@ import { cx } from "class-variance-authority" import { useIntl } from "react-intl" +import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats" import { dt } from "@scandic-hotels/common/dt" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" @@ -10,59 +11,64 @@ import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRatesStore } from "@/stores/select-rate" - import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" +import useRateTitles from "@/hooks/booking/useRateTitles" import useLang from "@/hooks/useLang" -import { mapToPrice } from "../mapToPrice" +import { isBookingCodeRate } from "../../utils" import Room from "../Room" -import { getMemberPrice, isBookingCodeRate } from "../utils" import styles from "./summaryContent.module.css" -import type { SelectRateSummaryProps } from "@/types/components/hotelReservation/summary" +import type { Price } from "@/contexts/SelectRate/getTotalPrice" + +export type SelectRateSummaryProps = { + isMember: boolean + bookingCode?: string + toggleSummaryOpen: () => void +} export default function SummaryContent({ - booking, - rooms, - totalPrice, isMember, - vat, toggleSummaryOpen, }: SelectRateSummaryProps) { - const { rateSummary, defaultCurrency } = useRatesStore((state) => ({ - rateSummary: state.rateSummary, - defaultCurrency: state.defaultCurrency, - })) + const { selectedRates, input } = useSelectRateContext() + const intl = useIntl() const lang = useLang() + const rateTitles = useRateTitles() - const diff = dt(booking.toDate).diff(booking.fromDate, "days") - - const nights = intl.formatMessage( + const nightsLabel = intl.formatMessage( { defaultMessage: "{totalNights, plural, one {# night} other {# nights}}", }, - { totalNights: diff } + { totalNights: input.nights } ) - const filteredRooms = rooms.filter( - (room): room is NonNullable => !!room - ) const memberPrice = - rooms.length === 1 && rooms[0] ? getMemberPrice(rooms[0].roomRate) : null - const containsBookingCodeRate = rooms.find( - (r) => r && isBookingCodeRate(r.roomRate) + selectedRates.rates.length === 1 && + selectedRates.rates[0] && + "member" in selectedRates.rates[0] + ? selectedRates.rates[0].member + : null + + const containsBookingCodeRate = selectedRates.rates.find( + (r) => r && isBookingCodeRate(r) ) + + if (!selectedRates?.totalPrice) { + return null + } + const showDiscounted = containsBookingCodeRate || isMember - const totalRegularPrice = totalPrice.local?.regularPrice - ? totalPrice.local.regularPrice + const totalRegularPrice = selectedRates?.totalPrice?.local?.regularPrice + ? selectedRates.totalPrice.local.regularPrice : 0 - const showStrikeThroughPrice = totalRegularPrice > totalPrice.local.price - const priceDetailsRooms = mapToPrice(rateSummary, booking.rooms, isMember) + const showStrikeThroughPrice = + totalRegularPrice > selectedRates?.totalPrice?.local?.price return (
@@ -90,26 +96,44 @@ export default function SummaryContent({

- {dt(booking.fromDate).locale(lang).format(longDateFormat[lang])} + {dt(input.data?.booking.fromDate) + .locale(lang) + .format(longDateFormat[lang])} {/* eslint-disable formatjs/no-literal-string-in-jsx */} - {dt(booking.toDate).locale(lang).format(longDateFormat[lang])} ( - {nights}){/* eslint-enable formatjs/no-literal-string-in-jsx */} + {dt(input.data?.booking.toDate) + .locale(lang) + .format(longDateFormat[lang])}{" "} + ({nightsLabel}) + {/* eslint-enable formatjs/no-literal-string-in-jsx */}

- {filteredRooms.map((room, idx) => ( - - ))} + {selectedRates.rates.map((room, idx) => { + if (!room) { + return null + } + + return ( + + ) + })}
@@ -130,7 +154,7 @@ export default function SummaryContent({ )}

- {totalPrice.requested ? ( + {selectedRates.totalPrice.requested ? (

{intl.formatMessage( @@ -140,10 +164,11 @@ export default function SummaryContent({ { value: formatPrice( intl, - totalPrice.requested.price, - totalPrice.requested.currency, - totalPrice.requested.additionalPrice, - totalPrice.requested.additionalPriceCurrency + selectedRates.totalPrice.requested.price, + selectedRates.totalPrice.requested.currency, + selectedRates.totalPrice.requested.additionalPrice, + selectedRates.totalPrice.requested + .additionalPriceCurrency ), } )} @@ -161,22 +186,22 @@ export default function SummaryContent({ > {formatPrice( intl, - totalPrice.local.price, - totalPrice.local.currency, - totalPrice.local.additionalPrice, - totalPrice.local.additionalPriceCurrency + selectedRates.totalPrice.local.price, + selectedRates.totalPrice.local.currency, + selectedRates.totalPrice.local.additionalPrice, + selectedRates.totalPrice.local.additionalPriceCurrency )} {showDiscounted && showStrikeThroughPrice && - totalPrice.local.regularPrice ? ( + selectedRates.totalPrice.local.regularPrice ? ( {formatPrice( intl, - totalPrice.local.regularPrice, - totalPrice.local.currency + selectedRates.totalPrice.local.regularPrice, + selectedRates.totalPrice.local.currency )} @@ -185,18 +210,140 @@ export default function SummaryContent({

{ + if (!room) { + return null + } + + const mapped = mapToRoom({ + isMember, + rate: room, + input, + idx, + getPriceForRoom: selectedRates.getPriceForRoom, + rateTitles, + }) + + function getPrice( + room: NonNullable<(typeof selectedRates.rates)[number]>, + isMember: boolean + ) { + switch (room.type) { + case "regular": + return { + regular: isMember + ? (room.member?.localPrice ?? room.public?.localPrice) + : room.public?.localPrice, + } + case "campaign": + return { + campaign: isMember + ? (room.member ?? room.public) + : room.public, + } + case "redemption": + return { + redemption: room.redemption, + } + case "code": { + if ("corporateCheque" in room) { + return { + corporateCheque: room.corporateCheque, + } + } + + if ("voucher" in room) { + return { + voucher: room.voucher, + } + } + if ("public" in room) { + return { + regular: isMember + ? (room.member?.localPrice ?? room.public?.localPrice) + : room.public?.localPrice, + } + } + } + default: + throw new Error("Unknown price type") + } + } + + const p = getPrice(room!, isMember) + + return { + ...mapped, + idx, + getPriceForRoom: selectedRates.getPriceForRoom, + rateTitles, + price: p, + bedType: undefined, + breakfast: undefined, + breakfastIncluded: + room?.rateDefinition.breakfastIncluded ?? false, + rateDefinition: room.rateDefinition, + } + }) + .filter((x) => !!x)} + fromDate={input.data?.booking.fromDate ?? ""} + toDate={input.data?.booking.toDate ?? ""} + totalPrice={selectedRates.totalPrice} + vat={selectedRates.vat} />
{!isMember && memberPrice ? ( - + ) : null} ) } + +function mapToRoom({ + isMember, + rate, + input, + idx, + getPriceForRoom, + rateTitles, +}: { + isMember: boolean + rate: NonNullable< + ReturnType["selectedRates"]["rates"][number] + > + input: ReturnType["input"] + idx: number + getPriceForRoom: (roomIndex: number) => Price | null + rateTitles: ReturnType +}) { + return { + adults: input.data?.booking.rooms[idx].adults || 0, + childrenInRoom: input.data?.booking.rooms[idx].childrenInRoom, + roomType: rate.roomInfo.roomType, + roomRate: rate, + cancellationText: rateTitles[rate.rate].title, + roomPrice: { + perNight: { local: { price: -1, currency: CurrencyEnum.SEK } }, + perStay: getPriceForRoom(idx) ?? { + local: { price: -1, currency: CurrencyEnum.Unknown }, + }, + }, + rateDetails: isMember + ? (rate.rateDefinitionMember?.generalTerms ?? + rate.rateDefinition.generalTerms) + : rate.rateDefinition.generalTerms, + packages: rate.roomInfo.selectedPackages, + } +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css index a7f7834f6..bbc85e092 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css @@ -49,7 +49,7 @@ } } -.prices .strikeThroughRate { +.strikeThroughRate { text-decoration: line-through; color: var(--Text-Secondary); } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx index df3000bd9..7287f4fc8 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx @@ -8,11 +8,10 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" -import { getRoomPrice } from "@/stores/enter-details/helpers" - import Modal from "@/components/Modal" -import { getMemberPrice, isBookingCodeRate } from "../utils" +import { isBookingCodeRate } from "../../utils" +import { getMemberPrice } from "../utils" import styles from "./room.module.css" @@ -72,7 +71,6 @@ export default function Room({ const memberPrice = getMemberPrice(room.roomRate) const showMemberPrice = !!(isMember && memberPrice && roomNumber === 1) const showDiscounted = isBookingCodeRate(room.roomRate) || showMemberPrice - const regularRate = getRoomPrice(room.roomRate, showMemberPrice) const adultsMsg = intl.formatMessage( { @@ -146,13 +144,12 @@ export default function Room({ room.roomPrice.perStay.local.additionalPriceCurrency )}

- {/* Show the price on which discount applies as Striked when discounted price is available */} - {showDiscounted && regularRate.perStay.local.regularPrice ? ( + {showDiscounted && room.roomPrice.perStay.local.price ? ( {formatPrice( intl, - regularRate.perStay.local.regularPrice, - regularRate.perStay.local.currency + room.roomPrice.perStay.local.price, + room.roomPrice.perStay.local.currency )} ) : null} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css index 28db86422..085bec231 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css @@ -18,6 +18,7 @@ .termsText:nth-child(n) { display: flex; + align-items: center; margin-bottom: var(--Space-x1); } @@ -46,7 +47,7 @@ } } -.prices .strikeThroughRate { +.strikeThroughRate { text-decoration: line-through; color: var(--Text-Secondary); } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Summary.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Summary.tsx deleted file mode 100644 index ed9701d0f..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/Summary.tsx +++ /dev/null @@ -1,370 +0,0 @@ -"use client" -import { Fragment } from "react" -import { Button as ButtonRAC } from "react-aria-components" -import { useIntl } from "react-intl" - -import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats" -import { dt } from "@scandic-hotels/common/dt" -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import Body from "@scandic-hotels/design-system/Body" -import { Button } from "@scandic-hotels/design-system/Button" -import Caption from "@scandic-hotels/design-system/Caption" -import { Divider } from "@scandic-hotels/design-system/Divider" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import Subtitle from "@scandic-hotels/design-system/Subtitle" -import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" - -import { useRatesStore } from "@/stores/select-rate" - -import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal" -import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" -import Modal from "@/components/Modal" -import useLang from "@/hooks/useLang" - -import { mapToPrice } from "./mapToPrice" -import { isBookingCodeRate } from "./utils" - -import styles from "./summary.module.css" - -import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details" -import type { SelectRateSummaryProps } from "@/types/components/hotelReservation/summary" - -export default function Summary({ - booking, - rooms, - totalPrice, - isMember, - vat, - toggleSummaryOpen, -}: SelectRateSummaryProps) { - const { rateSummary, defaultCurrency } = useRatesStore((state) => ({ - rateSummary: state.rateSummary, - defaultCurrency: state.defaultCurrency, - })) - const intl = useIntl() - const lang = useLang() - - const diff = dt(booking.toDate).diff(booking.fromDate, "days") - - const nights = intl.formatMessage( - { - defaultMessage: "{totalNights, plural, one {# night} other {# nights}}", - }, - { totalNights: diff } - ) - - function getMemberPrice(roomRate: RoomRate) { - if ("member" in roomRate && roomRate.member) { - return { - amount: roomRate.member.localPrice.pricePerStay, - currency: roomRate.member.localPrice.currency, - pricePerNight: roomRate.member.localPrice.pricePerNight, - } - } - - return null - } - - const memberPrice = - rooms.length === 1 && rooms[0] ? getMemberPrice(rooms[0].roomRate) : null - - const containsBookingCodeRate = rooms.find( - (r) => r && isBookingCodeRate(r.roomRate) - ) - const showDiscounted = containsBookingCodeRate || isMember - - const priceDetailsRooms = mapToPrice(rateSummary, booking.rooms, isMember) - - return ( -
-
- - - {intl.formatMessage({ - defaultMessage: "Booking summary", - })} - - - {dt(booking.fromDate).locale(lang).format(longDateFormat[lang])} - - {/* eslint-disable formatjs/no-literal-string-in-jsx */} - {dt(booking.toDate).locale(lang).format(longDateFormat[lang])} ( - {nights}){/* eslint-enable formatjs/no-literal-string-in-jsx */} - - - -
- - {rooms.map((room, idx) => { - if (!room) { - return null - } - - const roomNumber = idx + 1 - const adults = room.adults - const childrenInRoom = room.childrenInRoom - - const childrenBeds = childrenInRoom?.reduce( - (acc, value) => { - const bedType = Number(value.bed) - if (bedType === ChildBedMapEnum.IN_ADULTS_BED) { - return acc - } - const count = acc.get(bedType) ?? 0 - acc.set(bedType, count + 1) - return acc - }, - new Map([ - [ChildBedMapEnum.IN_CRIB, 0], - [ChildBedMapEnum.IN_EXTRA_BED, 0], - ]) - ) - - const childBedCrib = childrenBeds?.get(ChildBedMapEnum.IN_CRIB) - const childBedExtraBed = childrenBeds?.get(ChildBedMapEnum.IN_EXTRA_BED) - - const memberPrice = getMemberPrice(room.roomRate) - const showMemberPrice = !!(isMember && memberPrice && roomNumber === 1) - const showDiscounted = - isBookingCodeRate(room.roomRate) || showMemberPrice - - const adultsMsg = intl.formatMessage( - { - defaultMessage: - "{totalAdults, plural, one {# adult} other {# adults}}", - }, - { totalAdults: adults } - ) - - const guestsParts = [adultsMsg] - if (childrenInRoom?.length) { - const childrenMsg = intl.formatMessage( - { - defaultMessage: - "{totalChildren, plural, one {# child} other {# children}}", - }, - { totalChildren: childrenInRoom.length } - ) - guestsParts.push(childrenMsg) - } - - const roomPackages = room.packages - const zeroPrice = formatPrice(intl, 0, defaultCurrency) - - return ( - -
-
- {rooms.length > 1 ? ( - - {intl.formatMessage( - { - defaultMessage: "Room {roomIndex}", - }, - { - roomIndex: roomNumber, - } - )} - - ) : null} -
- {room.roomType} - - {formatPrice( - intl, - room.roomPrice.perStay.local.price, - room.roomPrice.perStay.local.currency, - room.roomPrice.perStay.local.additionalPrice, - room.roomPrice.perStay.local.additionalPriceCurrency - )} - -
- - {guestsParts.join(", ")} - - - {room.cancellationText} - - - {intl.formatMessage({ - defaultMessage: "Rate details", - })} - - - } - title={room.cancellationText} - > -
- {room.rateDetails?.map((info) => ( - - - {info} - - ))} -
-
-
- - {childBedCrib ? ( -
-
- - {intl.formatMessage( - { - defaultMessage: "Crib (child) × {count}", - }, - { count: childBedCrib } - )} - - - {intl.formatMessage({ - defaultMessage: "Subject to availability", - })} - -
- {zeroPrice} -
- ) : null} - {childBedExtraBed ? ( -
-
- - {intl.formatMessage( - { - defaultMessage: "Extra bed (child) × {count}", - }, - { - count: childBedExtraBed, - } - )} - -
- {zeroPrice} -
- ) : null} - {roomPackages?.map((pkg) => ( -
-
- {pkg.description} -
- - - {formatPrice( - intl, - pkg.localPrice.price, - pkg.localPrice.currency - )} - -
- ))} -
- -
- ) - })} -
-
-
- - {intl.formatMessage( - { - defaultMessage: "Total price (incl VAT)", - }, - { b: (str) => {str} } - )} - - -
-
- - {formatPrice( - intl, - totalPrice.local.price, - totalPrice.local.currency, - totalPrice.local.additionalPrice, - totalPrice.local.additionalPriceCurrency - )} - - {booking.bookingCode && totalPrice.local.regularPrice && ( - - {formatPrice( - intl, - totalPrice.local.regularPrice, - totalPrice.local.currency - )} - - )} - {totalPrice.requested && ( - - {intl.formatMessage( - { - defaultMessage: "Approx. {value}", - }, - { - value: formatPrice( - intl, - totalPrice.requested.price, - totalPrice.requested.currency, - totalPrice.requested.additionalPrice, - totalPrice.requested.additionalPriceCurrency - ), - } - )} - - )} -
-
- -
- {!isMember && memberPrice ? ( - - ) : null} -
- ) -} 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 4bfc3b243..d0ab3845c 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 @@ -9,35 +9,21 @@ import { Button } from "@scandic-hotels/design-system/Button" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRatesStore } from "@/stores/select-rate" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" +import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" +import { isBookingCodeRate } from "../utils" import SummaryContent from "./Content" -import { mapRate } from "./mapRate" -import { isBookingCodeRate } from "./utils" import styles from "./mobileSummary.module.css" -import type { RoomsAvailability } from "@scandic-hotels/trpc/types/roomAvailability" - -import type { MobileSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary" - -export default function MobileSummary({ - isAllRoomsSelected, - isUserLoggedIn, - totalPriceToShow, -}: MobileSummaryProps) { +export function MobileSummary() { const intl = useIntl() const scrollY = useRef(0) const [isSummaryOpen, setIsSummaryOpen] = useState(false) + const isUserLoggedIn = useIsUserLoggedIn() - 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 { selectedRates } = useSelectRateContext() function toggleSummaryOpen() { setIsSummaryOpen(!isSummaryOpen) @@ -67,38 +53,28 @@ export default function MobileSummary({ } }, [isSummaryOpen]) - const roomRateDefinitions = roomsAvailability?.find( - (ra): ra is RoomsAvailability => "rateDefinitions" in ra + const containsBookingCodeRate = selectedRates.rates.find( + (r) => r && isBookingCodeRate(r) ) - if (!roomRateDefinitions) { + const showDiscounted = containsBookingCodeRate || isUserLoggedIn + + if (!selectedRates.totalPrice) { return null } - const rooms = rateSummary.map((room, index) => - room ? mapRate(room, index, bookingRooms, room.packages) : null - ) - - const containsBookingCodeRate = rateSummary.find( - (r) => r && isBookingCodeRate(r.product) - ) - const showDiscounted = containsBookingCodeRate || isUserLoggedIn - const totalRegularPrice = totalPriceToShow.local?.regularPrice - ? totalPriceToShow.local.regularPrice + const totalRegularPrice = selectedRates.totalPrice.local?.regularPrice + ? selectedRates.totalPrice.local.regularPrice : 0 const showStrikeThroughPrice = - totalRegularPrice > totalPriceToShow.local.price + totalRegularPrice > selectedRates.totalPrice.local?.price return (
@@ -124,22 +100,22 @@ export default function MobileSummary({ > {formatPrice( intl, - totalPriceToShow.local.price, - totalPriceToShow.local.currency, - totalPriceToShow.local.additionalPrice, - totalPriceToShow.local.additionalPriceCurrency + selectedRates.totalPrice.local.price, + selectedRates.totalPrice.local.currency, + selectedRates.totalPrice.local.additionalPrice, + selectedRates.totalPrice.local.additionalPriceCurrency )} {showDiscounted && showStrikeThroughPrice && - totalPriceToShow.local.regularPrice ? ( + selectedRates.totalPrice.local.regularPrice ? ( {formatPrice( intl, - totalPriceToShow.local.regularPrice, - totalPriceToShow.local.currency + selectedRates.totalPrice.local.regularPrice, + selectedRates.totalPrice.local.currency )} @@ -166,7 +142,7 @@ export default function MobileSummary({ size="Large" type="submit" typography="Body/Paragraph/mdBold" - isDisabled={!isAllRoomsSelected} + isDisabled={selectedRates.state !== "ALL_SELECTED"} > {intl.formatMessage({ defaultMessage: "Continue", diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css index fdd7f901f..0daf39bb1 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css @@ -88,7 +88,7 @@ } } -.priceDetailsButton .strikeThroughRate { +.strikeThroughRate { text-decoration: line-through; color: var(--Text-Secondary); } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/summary.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/summary.module.css index 20d8d5415..0062f8dc8 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/summary.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/summary.module.css @@ -2,8 +2,8 @@ border-radius: var(--Corner-radius-lg); display: flex; flex-direction: column; - gap: var(--Space-x2); - padding: var(--Space-x3); + gap: var(--Spacing-x2); + padding: var(--Spacing-x3); height: 100%; } @@ -31,32 +31,32 @@ .date { align-items: center; display: flex; - gap: var(--Space-x1); + gap: var(--Spacing-x1); justify-content: flex-start; grid-area: date; } .link { - margin-top: var(--Space-x1); + margin-top: var(--Spacing-x1); } .addOns { display: flex; flex-direction: column; - gap: var(--Space-x15); + gap: var(--Spacing-x-one-and-half); overflow-y: auto; } .rateDetailsPopover { display: flex; flex-direction: column; - gap: var(--Space-x05); + gap: var(--Spacing-x-half); max-width: 360px; } .entry { display: flex; - gap: var(--Space-x05); + gap: var(--Spacing-x-half); justify-content: space-between; } @@ -67,7 +67,7 @@ .total { display: flex; flex-direction: column; - gap: var(--Space-x2); + gap: var(--Spacing-x2); } .bottomDivider { @@ -79,15 +79,16 @@ } .terms { - margin-top: var(--Space-x3); - margin-bottom: var(--Space-x3); + margin-top: var(--Spacing-x3); + margin-bottom: var(--Spacing-x3); } .termsText:nth-child(n) { display: flex; - margin-bottom: var(--Space-x1); + align-items: center; + margin-bottom: var(--Spacing-x1); } .terms .termsIcon { - margin-right: var(--Space-x1); + margin-right: var(--Spacing-x1); } @media screen and (min-width: 1367px) { diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts index ee5f420f2..8863fd899 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/MobileSummary/utils.ts @@ -1,7 +1,3 @@ -import { RateTypeEnum } from "@scandic-hotels/trpc/enums/rateType" - -import type { Product } from "@scandic-hotels/trpc/types/roomAvailability" - import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details" export function getMemberPrice(roomRate: RoomRate) { @@ -15,21 +11,3 @@ export function getMemberPrice(roomRate: RoomRate) { return null } - -export function isBookingCodeRate(product: Product) { - if ( - "corporateCheque" in product || - "redemption" in product || - "voucher" in product - ) { - return true - } else { - if (product.public) { - return product.public.rateType !== RateTypeEnum.Regular - } - if (product.member) { - return product.member.rateType !== RateTypeEnum.Regular - } - return false - } -} 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 71416f35c..94854269f 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/index.tsx @@ -1,138 +1,37 @@ "use client" + import { useRouter, useSearchParams } from "next/navigation" -import { useSession } from "next-auth/react" import { useState, useTransition } from "react" -import { useIntl } from "react-intl" -import { dt } from "@scandic-hotels/common/dt" -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import Body from "@scandic-hotels/design-system/Body" -import Caption from "@scandic-hotels/design-system/Caption" -import Footnote from "@scandic-hotels/design-system/Footnote" -import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" -import Subtitle from "@scandic-hotels/design-system/Subtitle" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" -import { RateTypeEnum } from "@scandic-hotels/trpc/enums/rateType" +import { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { useRatesStore } from "@/stores/select-rate" - -import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" -import { isValidClientSession } from "@/utils/clientSession" - -import MobileSummary from "./MobileSummary" -import { getTotalPrice } from "./utils" +import { DesktopSummary } from "./DesktopSummary" +import { MobileSummary } from "./MobileSummary" import styles from "./rateSummary.module.css" -export default function RateSummary() { - const { - bookingCode, - bookingRooms, - dates, - isFetchingPackages, - rateSummary, - roomsAvailability, - } = useRatesStore((state) => ({ - bookingCode: state.booking.bookingCode, - bookingRooms: state.booking.rooms, - dates: { - checkInDate: state.booking.fromDate, - checkOutDate: state.booking.toDate, - }, - isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages), - rateSummary: state.rateSummary, - roomsAvailability: state.roomsAvailability, - })) - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) +export function RateSummary() { + return ( + // eslint-disable-next-line formatjs/no-literal-string-in-jsx + Unable to render summary
}> + + + ) +} + +function InnerRateSummary() { + const { selectedRates, input } = useSelectRateContext() const [isSubmitting, setIsSubmitting] = useState(false) - const intl = useIntl() const router = useRouter() const params = useSearchParams() const [_, startTransition] = useTransition() - if (!roomsAvailability) { + if (selectedRates.state === "NONE_SELECTED") { return null } - const checkInDate = new Date(dates.checkInDate) - const checkOutDate = new Date(dates.checkOutDate) - const nights = dt(checkOutDate).diff(dt(checkInDate), "days") - - const totalNights = intl.formatMessage( - { - defaultMessage: "{totalNights, plural, one {# night} other {# nights}}", - }, - { totalNights: nights } - ) - const totalAdults = intl.formatMessage( - { - defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}", - }, - { totalAdults: bookingRooms.reduce((acc, room) => acc + room.adults, 0) } - ) - const childrenInOneOrMoreRooms = bookingRooms.some( - (room) => room.childrenInRoom?.length - ) - const childrenInroom = intl.formatMessage( - { - defaultMessage: - "{totalChildren, plural, one {# child} other {# children}}", - }, - { - totalChildren: bookingRooms.reduce( - (acc, room) => acc + (room.childrenInRoom?.length ?? 0), - 0 - ), - } - ) - const totalChildren = childrenInOneOrMoreRooms ? `, ${childrenInroom}` : "" - const totalRooms = intl.formatMessage( - { - defaultMessage: "{totalRooms, plural, one {# room} other {# rooms}}", - }, - { totalRooms: bookingRooms.length } - ) - - const summaryPriceText = `${totalNights}, ${totalAdults}${totalChildren}, ${totalRooms}` - - const totalRoomsRequired = bookingRooms.length - const isAllRoomsSelected = - rateSummary.filter((rate) => rate !== null).length === totalRoomsRequired - const hasMemberRates = rateSummary.some( - (room) => room && "member" in room.product && room.product.member - ) - const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn - - const freeCancelation = intl.formatMessage({ - defaultMessage: "Free cancellation", - }) - const nonRefundable = intl.formatMessage({ - defaultMessage: "Non-refundable", - }) - const freeBooking = intl.formatMessage({ - defaultMessage: "Free rebooking", - }) - const payLater = intl.formatMessage({ - defaultMessage: "Pay later", - }) - const payNow = intl.formatMessage({ - defaultMessage: "Pay now", - }) - - function getRateDetails(rate: RateEnum) { - switch (rate) { - case RateEnum.change: - return `${freeBooking}, ${payNow}` - case RateEnum.flex: - return `${freeCancelation}, ${payLater}` - case RateEnum.save: - default: - return `${nonRefundable}, ${payNow}` - } - } - function handleSubmit(e: React.FormEvent) { e.preventDefault() setIsSubmitting(true) @@ -141,62 +40,15 @@ export default function RateSummary() { }) } - if (!rateSummary.length || isFetchingPackages) { - return null - } + const totalPriceToShow = selectedRates.totalPrice - const isBookingCodeRate = rateSummary.some( - (rate) => - rate && - "public" in rate.product && - rate.product.public?.rateType !== RateTypeEnum.Regular - ) - const isVoucherRate = rateSummary.some( - (rate) => rate && "voucher" in rate.product - ) - const isCorporateChequeRate = rateSummary.some( - (rate) => rate && "corporateCheque" in rate.product - ) - const showDiscounted = - isUserLoggedIn || - isBookingCodeRate || - isVoucherRate || - isCorporateChequeRate - - const mainRoomProduct = rateSummary[0] - const totalPriceToShow = getTotalPrice( - mainRoomProduct, - rateSummary, - isUserLoggedIn, - intl - ) - - const rateProduct = rateSummary.find((rate) => rate?.product)?.product - - if (!totalPriceToShow || !rateProduct) { - return null - } - - let mainRoomCurrency = "" - if ("member" in rateProduct && rateProduct.member?.localPrice) { - mainRoomCurrency = rateProduct.member.localPrice.currency - } if ( - !mainRoomCurrency && - "public" in rateProduct && - rateProduct.public?.localPrice + !totalPriceToShow || + !selectedRates.rates.some((room) => room?.isSelected ?? false) ) { - mainRoomCurrency = rateProduct.public.localPrice.currency + return null } - const totalRegularPrice = totalPriceToShow.local?.regularPrice - ? totalPriceToShow.local.regularPrice - : 0 - const isTotalRegularPriceGreaterThanPrice = - totalRegularPrice > totalPriceToShow.local.price - const showStrikedThroughPrice = - (!!bookingCode || isUserLoggedIn) && isTotalRegularPriceGreaterThanPrice - // attribute data-footer-spacing used to add spacing // beneath footer to be able to show entire footer upon // scrolling down to the bottom of the page @@ -209,218 +61,21 @@ export default function RateSummary() { >
-
- {rateSummary.map((room, index) => { - if (!room) { - return ( -
- - {intl.formatMessage( - { - defaultMessage: "Room {roomIndex}", - }, - { roomIndex: index + 1 } - )} - - - {intl.formatMessage({ - defaultMessage: "Select room", - })} - -
- ) - } - - return ( -
- {rateSummary.length > 1 ? ( - <> - - {intl.formatMessage( - { - defaultMessage: "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( - { - defaultMessage: "Room {roomIndex}", - }, - { roomIndex: rateSummary.length + index + 1 } - )} - - - {intl.formatMessage({ - defaultMessage: "Select room", - })} - -
- ))} -
-
- {showMemberDiscountBanner && ( -
- { - if (!rate) { - return total - } - - const { packages: roomPackages, product } = rate - - const memberExists = "member" in product && product.member - const publicExists = "public" in product && product.public - if (!memberExists) { - if (!publicExists) { - return total - } - } - - const price = - product.member?.localPrice.pricePerStay || - product.public?.localPrice.pricePerStay - - if (!price) { - return total - } - - const selectedPackagesPrice = roomPackages.reduce( - (acc, pkg) => acc + pkg.localPrice.totalPrice, - 0 - ) - - return total + price + selectedPackagesPrice - }, 0), - currency: mainRoomCurrency, - }} - /> -
- )} -
- - {intl.formatMessage( - { - defaultMessage: "Total price (incl VAT)", - }, - { b: (str) => {str} } - )} - - {summaryPriceText} -
-
-
- - {formatPrice( - intl, - totalPriceToShow.local.price, - totalPriceToShow.local.currency, - totalPriceToShow.local.additionalPrice, - totalPriceToShow.local.additionalPriceCurrency - )} - - {showStrikedThroughPrice && - totalPriceToShow.local.regularPrice ? ( - - {formatPrice( - intl, - totalPriceToShow.local.regularPrice, - totalPriceToShow.local.currency - )} - - ) : null} - {totalPriceToShow.requested ? ( - - {intl.formatMessage( - { - defaultMessage: "Approx. {value}", - }, - { - value: formatPrice( - intl, - totalPriceToShow.requested.price, - totalPriceToShow.requested.currency, - totalPriceToShow.requested.additionalPrice, - totalPriceToShow.requested.additionalPriceCurrency - ), - } - )} - - ) : null} -
-
- - {intl.formatMessage({ - defaultMessage: "Total price", - })} - - - {formatPrice( - intl, - totalPriceToShow.local.price, - totalPriceToShow.local.currency, - totalPriceToShow.local.additionalPrice, - totalPriceToShow.local.additionalPriceCurrency - )} - - - {summaryPriceText} - -
- -
-
+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + Unable to render desktop summary
}> + +
- + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + Unable to render mobile summary
}> + +
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 6a2073b5d..d24d6a7f3 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts @@ -4,7 +4,10 @@ import { RateTypeEnum } from "@scandic-hotels/trpc/enums/rateType" import { sumPackages } from "@/components/HotelReservation/utils" import type { Packages } from "@scandic-hotels/trpc/types/packages" -import type { RedemptionProduct } from "@scandic-hotels/trpc/types/roomAvailability" +import type { + Product, + RedemptionProduct, +} from "@scandic-hotels/trpc/types/roomAvailability" import type { IntlShape } from "react-intl" import type { Price } from "@/types/components/hotelReservation/price" @@ -23,10 +26,8 @@ export function calculateTotalPrice( const roomNr = idx + 1 const isMainRoom = roomNr === 1 let rate - let publicRate if (isUserLoggedIn && isMainRoom && room.product.member) { rate = room.product.member - publicRate = room.product.public } else if (room.product.public) { rate = room.product.public } @@ -50,16 +51,10 @@ export function calculateTotalPrice( total.local.price = total.local.price + rate.localPrice.pricePerStay + packagesPrice.local - if (rate.rateType === RateTypeEnum.Regular && publicRate) { + if (rate.localPrice.regularPricePerStay) { total.local.regularPrice = (total.local.regularPrice || 0) + - publicRate.localPrice.pricePerStay + - packagesPrice.local - } else { - total.local.regularPrice = - (total.local.regularPrice || 0) + - (rate.localPrice.regularPricePerStay || - rate.localPrice.pricePerStay) + + rate.localPrice.regularPricePerStay + packagesPrice.local } @@ -248,3 +243,23 @@ export function getTotalPrice( return calculateTotalPrice(summaryArray, isUserLoggedIn) } + +export function isBookingCodeRate(product: Product | undefined | null) { + if (!product) return false + + if ( + "corporateCheque" in product || + "redemption" in product || + "voucher" in product + ) { + return true + } else { + if (product.public) { + return product.public.rateType !== RateTypeEnum.Regular + } + if (product.member) { + return product.member.rateType !== RateTypeEnum.Regular + } + return false + } +} 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 cf079b746..3ac188e8e 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 @@ -1,9 +1,7 @@ "use client" -import { useSession } from "next-auth/react" import { useIntl } from "react-intl" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" -import { dt } from "@scandic-hotels/common/dt" import { logger } from "@scandic-hotels/common/logger" import Body from "@scandic-hotels/design-system/Body" import Caption from "@scandic-hotels/design-system/Caption" @@ -13,123 +11,40 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton import Subtitle from "@scandic-hotels/design-system/Subtitle" import { RateEnum } from "@scandic-hotels/trpc/enums/rate" -import { useRatesStore } from "@/stores/select-rate" - import Chip from "@/components/TempDesignSystem/Chip" -import { useRoomContext } from "@/contexts/SelectRate/Room" -import { isValidClientSession } from "@/utils/clientSession" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" +import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" import styles from "./selectedRoomPanel.module.css" -export default function SelectedRoomPanel() { +export function SelectedRoomPanel({ roomIndex }: { roomIndex: number }) { const intl = useIntl() - const { dates, roomCategories, rooms } = useRatesStore((state) => ({ - dates: { - from: state.booking.fromDate, - to: state.booking.toDate, - }, - roomCategories: state.roomCategories, - rooms: state.rooms, - })) - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) + + const isMainRoom = roomIndex === 0 + const roomNr = roomIndex + 1 const { - actions: { modifyRate }, - isMainRoom, - roomNr, - selectedPackages, - selectedRate, - } = useRoomContext() - const nights = dt(dates.to).diff(dt(dates.from), "days") + selectedRates, + actions: { setActiveRoom }, + } = useSelectRateContext() + const selectedRate = selectedRates.forRoom(roomIndex) + const images = selectedRate?.roomInfo?.roomInfo?.images - const images = roomCategories.find((roomCategory) => - roomCategory.roomTypes.some( - (roomType) => roomType.code === selectedRate?.roomTypeCode - ) - )?.images + const rateTitle = useRateTitle(selectedRate?.rate) - const freeCancelation = intl.formatMessage({ - defaultMessage: "Free cancellation", - }) - const nonRefundable = intl.formatMessage({ - defaultMessage: "Non-refundable", - }) - const freeBooking = intl.formatMessage({ - defaultMessage: "Free rebooking", - }) - const payLater = intl.formatMessage({ - defaultMessage: "Pay later", - }) - const payNow = intl.formatMessage({ - defaultMessage: "Pay now", - }) - - function getRateTitle(rate: RateEnum) { - switch (rate) { - case RateEnum.change: - return `${freeBooking}, ${payNow}` - case RateEnum.flex: - return `${freeCancelation}, ${payLater}` - case RateEnum.save: - default: - return `${nonRefundable}, ${payNow}` - } - } + const selectedProductTitle = useSelectedProductTitle({ roomIndex }) if (!selectedRate) { return null } - const selectedPackagesCurrency = selectedPackages.find( - (pkg) => pkg.localPrice.currency - ) - const selectedPackagesPrice = selectedPackages.reduce( - (total, pkg) => total + pkg.localPrice.totalPrice, - 0 - ) - const selectedPackagesPricePerNight = Math.ceil( - selectedPackagesPrice / nights - ) - - const night = intl.formatMessage({ - defaultMessage: "night", - }) - let selectedProduct - if ( - isUserLoggedIn && - isMainRoom && - "member" in selectedRate.product && - selectedRate.product.member - ) { - const { localPrice } = selectedRate.product.member - selectedProduct = `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}` - } else if ("public" in selectedRate.product && selectedRate.product.public) { - const { localPrice } = selectedRate.product.public - selectedProduct = `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}` - } else if ("corporateCheque" in selectedRate.product) { - const { localPrice } = selectedRate.product.corporateCheque - selectedProduct = `${localPrice.numberOfCheques} ${CurrencyEnum.CC}` - if ( - (localPrice.additionalPricePerStay || selectedPackagesPrice) && - localPrice.currency - ) { - selectedProduct = `${selectedProduct} + ${localPrice.additionalPricePerStay + selectedPackagesPrice} ${localPrice.currency}` - } - } else if ("voucher" in selectedRate.product) { - selectedProduct = `${selectedRate.product.voucher.numberOfVouchers} ${CurrencyEnum.Voucher}` - if (selectedPackagesPrice && selectedPackagesCurrency) { - selectedProduct = `${selectedProduct} + ${selectedPackagesPrice} ${selectedPackagesCurrency}` - } - } - - if (!selectedProduct) { + if (!selectedProductTitle) { logger.error("Selected product is unknown") return null } const showModifyButton = isMainRoom || - (!isMainRoom && rooms.slice(0, roomNr).every((room) => room.selectedRate)) + (!isMainRoom && selectedRates.rates.slice(0, roomNr).every((room) => room)) return (
@@ -143,17 +58,19 @@ export default function SelectedRoomPanel() { )} - {selectedRate.roomType} + {selectedRate.roomInfo.roomType} - - {getRateTitle(selectedRate.product.rate)} - - {selectedProduct} + {rateTitle} + {selectedProductTitle}
{images?.[0]?.imageSizes?.tiny ? ( {selectedRate.roomType -
- +
{children}
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx index 318ce9760..5bca5988d 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx @@ -5,31 +5,36 @@ import { alternativeHotels } from "@scandic-hotels/common/constants/routes/hotel import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" import { AlertTypeEnum } from "@scandic-hotels/trpc/types/alertType" -import { useRatesStore } from "@/stores/select-rate" - import Alert from "@/components/TempDesignSystem/Alert" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import useLang from "@/hooks/useLang" import styles from "./alert.module.css" -export default function NoAvailabilityAlert() { +export default function NoAvailabilityAlert({ + roomIndex, +}: { + roomIndex: number +}) { const lang = useLang() const intl = useIntl() - const [bookingCode, selectedRooms, activeRoom] = useRatesStore((state) => [ - state.booking.bookingCode, - state.rooms, - state.activeRoom, - ]) - const { isFetchingPackages, rooms } = useRoomContext() + const { availability, input } = useSelectRateContext() + if (availability.isFetching || !availability.data) { + return null + } - const noAvailableRooms = rooms.every( - (roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable - ) + const indexed = availability.data[roomIndex] + const hasAvailabilityError = "error" in indexed + if (hasAvailabilityError) { + return null + } + + const noAvailableRooms = hasAvailableRoomsForRoom(indexed.roomConfigurations) const alertLink = - activeRoom !== -1 && selectedRooms[activeRoom].selectedPackages.length === 0 + roomIndex !== -1 && + (input.data?.booking.rooms.at(roomIndex)?.packages ?? []).length === 0 ? { title: intl.formatMessage({ defaultMessage: "See alternative hotels", @@ -39,10 +44,6 @@ export default function NoAvailabilityAlert() { } : null - if (isFetchingPackages) { - return null - } - if (noAvailableRooms) { const text = intl.formatMessage({ defaultMessage: "There are no rooms available that match your request.", @@ -61,7 +62,7 @@ export default function NoAvailabilityAlert() { ) } - const isPublicPromotionWithCode = rooms.some((room) => { + const isPublicPromotionWithCode = indexed.roomConfigurations.some((room) => { const filteredCampaigns = room.campaign.filter(Boolean) return filteredCampaigns.length ? filteredCampaigns.every( @@ -72,19 +73,20 @@ export default function NoAvailabilityAlert() { const noAvailableBookingCodeRooms = !isPublicPromotionWithCode && - rooms.every( + indexed.roomConfigurations.every( (room) => room.status === AvailabilityEnum.NotAvailable || !room.code.length ) - if (bookingCode && noAvailableBookingCodeRooms) { + if (input.bookingCode && noAvailableBookingCodeRooms) { const bookingCodeText = intl.formatMessage( { defaultMessage: "We found no available rooms using this booking code ({bookingCode}). See available rates below.", }, - { bookingCode } + { bookingCode: input.bookingCode } ) + return (
["availability"]["data"] + >[number], + { roomConfigurations: unknown } + >["roomConfigurations"] +) { + return roomConfigurations.every( + (roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable + ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/bookingCodeFilter.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/bookingCodeFilter.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/bookingCodeFilter.module.css rename to apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/bookingCodeFilter.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/index.tsx similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/index.tsx rename to apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/BookingCodeFilter/index.tsx diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx index 92cf278de..197f24556 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx @@ -1,14 +1,12 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation" -import { useRatesStore } from "@/stores/select-rate" - import BookingCodeChip from "@/components/BookingCodeChip" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" export function RemoveBookingCodeButton() { - const bookingCode = useRatesStore((state) => state.booking.bookingCode) - const roomNr = useRatesStore((state) => - state.activeRoom !== -1 ? state.activeRoom : 0 - ) + const { + input: { bookingCode }, + } = useSelectRateContext() const router = useRouter() const searchParams = useSearchParams() const pathname = usePathname() @@ -26,9 +24,6 @@ export function RemoveBookingCodeButton() { onClose={() => { const newSearchParams = new URLSearchParams(searchParams) newSearchParams.delete("bookingCode") - newSearchParams.delete(`room[${roomNr}].bookingCode`) - newSearchParams.delete(`room[${roomNr}].ratecode`) - newSearchParams.delete(`room[${roomNr}].roomtype`) const url = `${pathname}?${newSearchParams.toString()}` diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx index f2b777c5d..854051c64 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx @@ -1,19 +1,23 @@ "use client" + import { useIntl } from "react-intl" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRoomContext } from "@/contexts/SelectRate/Room" - import styles from "./petRoom.module.css" -export default function PetRoomMessage() { +export default function PetRoomMessage({ + priceData, +}: { + priceData?: { price: number; currency: string } +}) { const intl = useIntl() - const { petRoomPackage } = useRoomContext() - if (!petRoomPackage) { + + if (!priceData) { return null } + return (

@@ -28,11 +32,7 @@ export default function PetRoomMessage() { {str} ), - price: formatPrice( - intl, - petRoomPackage.localPrice.price, - petRoomPackage.localPrice.currency - ), + price: formatPrice(intl, priceData.price, priceData.currency), } )}

diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx index 56c05d713..98a88e5c5 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx @@ -4,25 +4,29 @@ import { Controller, useFormContext } from "react-hook-form" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" +import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" -import { useRatesStore } from "@/stores/select-rate" - +import { usePackageLabels } from "../../usePackageLabels" import { getIconNameByPackageCode } from "../../utils" -import PetRoomMessage from "./PetRoomMessage" -import { - checkIsAllergyRoom, - checkIsPetRoom, - includesAllergyRoom, - includesPetRoom, -} from "./utils" import styles from "./checkbox.module.css" +import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" +import type { ReactNode } from "react" + import type { FormValues } from "../formValues" -export default function Checkboxes() { - const packageOptions = useRatesStore((state) => state.packageOptions) +export function PackageCheckboxes({ + availablePackages, +}: { + availablePackages: { + code: RoomPackageCodeEnum + message?: ReactNode + }[] +}) { const { control } = useFormContext() + const packageLabels = usePackageLabels() + return ( - {packageOptions.map((option) => { + {availablePackages?.map((option) => { const isAllergyRoom = checkIsAllergyRoom(option.code) const isPetRoom = checkIsPetRoom(option.code) const isDisabled = @@ -59,13 +63,13 @@ export default function Checkboxes() { className={styles.text} variant="Body/Paragraph/mdRegular" > - {option.description} + {packageLabels[option.code]}
{iconName ? ( ) : null} - {isPetRoom ? : null} + {option.message}
) })} @@ -75,3 +79,23 @@ export default function Checkboxes() { /> ) } + +export function includesAllergyRoom(codes: PackageEnum[]) { + return codes.includes(RoomPackageCodeEnum.ALLERGY_ROOM) +} + +export function includesPetRoom(codes: PackageEnum[]) { + return codes.includes(RoomPackageCodeEnum.PET_ROOM) +} + +export function checkIsAllergyRoom( + code: PackageEnum +): code is RoomPackageCodeEnum.ALLERGY_ROOM { + return code === RoomPackageCodeEnum.ALLERGY_ROOM +} + +export function checkIsPetRoom( + code: PackageEnum +): code is RoomPackageCodeEnum.PET_ROOM { + return code === RoomPackageCodeEnum.PET_ROOM +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/utils.ts deleted file mode 100644 index fb9bc282c..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" - -export function includesAllergyRoom(codes: PackageEnum[]) { - return codes.includes(RoomPackageCodeEnum.ALLERGY_ROOM) -} - -export function includesPetRoom(codes: PackageEnum[]) { - return codes.includes(RoomPackageCodeEnum.PET_ROOM) -} - -export function checkIsAllergyRoom(code: PackageEnum) { - return code === RoomPackageCodeEnum.ALLERGY_ROOM -} - -export function checkIsPetRoom(code: PackageEnum) { - return code === RoomPackageCodeEnum.PET_ROOM -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx index 6ea6049de..14261ff95 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx @@ -5,76 +5,53 @@ import { useIntl } from "react-intl" import { Button } from "@scandic-hotels/design-system/Button" import { Divider } from "@scandic-hotels/design-system/Divider" import { Typography } from "@scandic-hotels/design-system/Typography" -import { trpc } from "@scandic-hotels/trpc/client" -import { useRatesStore } from "@/stores/select-rate" - -import { useRoomContext } from "@/contexts/SelectRate/Room" -import useLang from "@/hooks/useLang" - -import Checkboxes from "./Checkboxes" +import { PackageCheckboxes } from "./Checkboxes" import styles from "./form.module.css" +import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" +import type { ReactNode } from "react" import type { FormValues } from "./formValues" -export default function Form({ close }: { close: () => void }) { +export function RoomPackagesForm({ + close, + selectedPackages, + onSelectPackages, + availablePackages, +}: { + close: () => void + availablePackages: { + code: RoomPackageCodeEnum + message: ReactNode + }[] + selectedPackages: PackageEnum[] + onSelectPackages: (packages: PackageEnum[]) => void +}) { const intl = useIntl() - const lang = useLang() - const utils = trpc.useUtils() - - const { - actions: { removeSelectedPackages, selectPackages, updateRooms }, - bookingRoom, - selectedPackages, - } = useRoomContext() - const booking = useRatesStore((state) => state.booking) const methods = useForm({ values: { - selectedPackages: selectedPackages.map((pkg) => pkg.code), + selectedPackages: selectedPackages, }, }) - async function getFilteredRates(packages: PackageEnum[]) { - const bookingCode = bookingRoom.rateCode - ? bookingRoom.bookingCode - : booking.bookingCode - const filterRates = await utils.hotel.availability.selectRate.room.fetch({ - booking: { - fromDate: booking.fromDate, - hotelId: booking.hotelId, - searchType: booking.searchType, - toDate: booking.toDate, - room: { - ...bookingRoom, - bookingCode: bookingCode ?? undefined, - packages, - }, - }, - lang, - }) - updateRooms(filterRates?.roomConfigurations) - } - function clearSelectedPackages() { - removeSelectedPackages() + onSelectPackages([]) close() - getFilteredRates([]) } function onSubmit(data: FormValues) { - selectPackages(data.selectedPackages) + onSelectPackages(data.selectedPackages) close() - getFilteredRates(data.selectedPackages) } return (
- +
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx index 81f8b16a4..1ef0dc166 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react" +import { type ReactNode, useState } from "react" import { Dialog, DialogTrigger, @@ -12,11 +12,25 @@ import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import Form from "./Form" +import { RoomPackagesForm } from "./Form" import styles from "./roomPackageFilter.module.css" -export default function RoomPackageFilterModal() { +import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" +import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" + +export function RoomPackageFilterModal({ + selectedPackages, + onSelectPackages, + availablePackages, +}: { + onSelectPackages: (packages: PackageEnum[]) => void + selectedPackages: PackageEnum[] + availablePackages: { + code: RoomPackageCodeEnum + message: ReactNode + }[] +}) { const intl = useIntl() const [isOpen, setIsOpen] = useState(false) @@ -48,7 +62,12 @@ export default function RoomPackageFilterModal() {
- setIsOpen(false)} /> + setIsOpen(false)} + availablePackages={availablePackages} + selectedPackages={selectedPackages} + onSelectPackages={onSelectPackages} + /> diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx index ba62a44f5..70329557a 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx @@ -1,15 +1,29 @@ -import { useState } from "react" +import { type ReactNode, useState } from "react" import { Dialog, DialogTrigger, Popover } from "react-aria-components" import { useIntl } from "react-intl" import { ChipButton } from "@scandic-hotels/design-system/ChipButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import Form from "./Form" +import { RoomPackagesForm } from "./Form" import styles from "./roomPackageFilter.module.css" -export default function RoomPackageFilterPopover() { +import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" +import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" + +export function RoomPackageFilterPopover({ + selectedPackages, + onSelectPackages, + availablePackages, +}: { + onSelectPackages: (packages: PackageEnum[]) => void + selectedPackages: PackageEnum[] + availablePackages: { + code: RoomPackageCodeEnum + message: ReactNode + }[] +}) { const intl = useIntl() const [isOpen, setIsOpen] = useState(false) @@ -25,8 +39,13 @@ export default function RoomPackageFilterPopover() { - - setIsOpen(false)} /> + + setIsOpen(false)} + availablePackages={availablePackages} + selectedPackages={selectedPackages} + onSelectPackages={onSelectPackages} + /> diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx index 79a1d6abf..e1cbeaec1 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx @@ -1,64 +1,70 @@ "use client" import { Button as ButtonRAC } from "react-aria-components" -import { useMediaQuery } from "usehooks-ts" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import { trpc } from "@scandic-hotels/trpc/client" +import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" -import { useRatesStore } from "@/stores/select-rate" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" +import { useBreakpoint } from "@/hooks/useBreakpoint" -import { useRoomContext } from "@/contexts/SelectRate/Room" -import useLang from "@/hooks/useLang" - -import RoomPackageFilterModal from "./Modal" -import RoomPackageFilterPopover from "./Popover" +import PetRoomMessage from "./Form/Checkboxes/PetRoomMessage" +import { RoomPackageFilterModal } from "./Modal" +import { RoomPackageFilterPopover } from "./Popover" +import { usePackageLabels } from "./usePackageLabels" import { getIconNameByPackageCode } from "./utils" import styles from "./roomPackageFilter.module.css" +import type { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast" import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" +import type { ReactNode } from "react" -export default function RoomPackageFilter() { - const lang = useLang() - const utils = trpc.useUtils() - const displayAsPopover = useMediaQuery("(min-width: 768px)") +export function RoomPackageFilter({ roomIndex }: { roomIndex: number }) { + const displayAsModal = useBreakpoint("mobile") const { - actions: { removeSelectedPackage, updateRooms }, - bookingRoom, - selectedPackages, - } = useRoomContext() - const { booking, packageOptions } = useRatesStore((state) => ({ - booking: state.booking, - packageOptions: state.packageOptions, - })) + getPackagesForRoom, + actions: { selectPackages }, + } = useSelectRateContext() - async function deleteSelectedPackage(code: PackageEnum) { - removeSelectedPackage(code) - const bookingCode = bookingRoom.rateCode - ? bookingRoom.bookingCode - : booking.bookingCode + const { selectedPackages, availablePackages } = getPackagesForRoom(roomIndex) - const filterRates = await utils.hotel.availability.selectRate.room.fetch({ - booking: { - fromDate: booking.fromDate, - hotelId: booking.hotelId, - searchType: booking.searchType, - toDate: booking.toDate, - room: { - ...bookingRoom, - bookingCode: bookingCode ?? undefined, - packages: selectedPackages - .filter((pkg) => pkg.code !== code) - .map((pkg) => pkg.code), - }, - }, - lang, + function deletePackage(code: PackageEnum) { + selectPackages({ + roomIndex, + packages: selectedPackages + .filter((pkg) => pkg.code !== code) + .map((pkg) => pkg.code), }) - updateRooms(filterRates?.roomConfigurations) } + const petRoomPackage = availablePackages.find( + (x) => x.code === RoomPackageCodeEnum.PET_ROOM + ) + const packageLabels = usePackageLabels() + const packageMessages = packageMessageMap({ + petRoomPrice: + petRoomPackage && !("type" in petRoomPackage) + ? petRoomPackage.localPrice + : undefined, + }) + + const packages = availablePackages + .map((x) => { + if (!isRoomPackage(x)) { + return undefined + } + + return { + code: x.code, + message: packageMessages[x.code], + } + }) + .filter((x) => { + return !!x + }) + return (
@@ -73,12 +79,9 @@ export default function RoomPackageFilter() { size={16} color="CurrentColor" /> - { - packageOptions.find((pkgOption) => pkg.code === pkgOption.code) - ?.description - } + {packageLabels[pkg.code] ?? pkg.description} deleteSelectedPackage(pkg.code)} + onPress={() => deletePackage(pkg.code)} className={styles.removeButton} > @@ -87,12 +90,45 @@ export default function RoomPackageFilter() { ))}
- - + {displayAsModal ? ( +
+ pkg.code)} + onSelectPackages={(packages) => { + selectPackages({ roomIndex, packages }) + }} + /> +
+ ) : ( +
+ pkg.code)} + onSelectPackages={(packages) => { + selectPackages({ roomIndex, packages }) + }} + /> +
+ )}
) } + +function isRoomPackage(x: { + code: BreakfastPackageEnum | RoomPackageCodeEnum +}): x is { code: RoomPackageCodeEnum } { + return Object.values(RoomPackageCodeEnum).includes( + x.code as RoomPackageCodeEnum + ) +} + +const packageMessageMap = ({ + petRoomPrice, +}: { + petRoomPrice?: { price: number; currency: string } +}): Record => ({ + [RoomPackageCodeEnum.PET_ROOM]: , + [RoomPackageCodeEnum.ACCESSIBILITY_ROOM]: undefined, + [RoomPackageCodeEnum.ALLERGY_ROOM]: undefined, +}) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/usePackageLabels.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/usePackageLabels.ts similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/usePackageLabels.ts rename to apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/usePackageLabels.ts diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/index.tsx index a7b7fbec9..18de31947 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/index.tsx @@ -1,24 +1,52 @@ "use client" import { useIntl } from "react-intl" +import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" import { Typography } from "@scandic-hotels/design-system/Typography" import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import { RemoveBookingCodeButton } from "./RemoveBookingCodeButton/RemoveBookingCodeButton" -import RoomPackageFilter from "./RoomPackageFilter" +import { RoomPackageFilter } from "./RoomPackageFilter" import styles from "./roomsHeader.module.css" -export default function RoomsHeader() { - const { isFetchingPackages, rooms, totalRooms } = useRoomContext() - const intl = useIntl() +export function RoomsHeader({ roomIndex }: { roomIndex: number }) { + return ( + // eslint-disable-next-line formatjs/no-literal-string-in-jsx + Unable to render rooms header
}> + + + ) +} - const availableRooms = rooms.filter( - (room) => room.status === AvailabilityEnum.Available +function InnerRoomsHeader({ roomIndex }: { roomIndex: number }) { + return ( +
+ +
+ + + {/* */} +
+
+ ) +} + +function AvailableRoomCount({ roomIndex }: { roomIndex: number }) { + const intl = useIntl() + const { isFetching, getAvailabilityForRoom } = useSelectRateContext() + + const roomAvailability = getAvailabilityForRoom(roomIndex) ?? [] + + const availableRooms = roomAvailability.filter( + (x) => x.status === AvailabilityEnum.Available ).length + const totalRooms = roomAvailability.length + const notAllRoomsAvailableText = intl.formatMessage( { defaultMessage: @@ -40,23 +68,17 @@ export default function RoomsHeader() { } ) + if (isFetching) { + return + } + return ( -
- - {isFetchingPackages ? ( -

- ) : ( -

- {availableRooms !== totalRooms - ? notAllRoomsAvailableText - : allRoomsAvailableText} -

- )} -
-
- - -
-
+ +

+ {availableRooms !== totalRooms + ? notAllRoomsAvailableText + : allRoomsAvailableText} +

+
) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx index b96454a77..ecccd4ca8 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx @@ -3,21 +3,20 @@ import { useIntl } from "react-intl" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRatesStore } from "@/stores/select-rate" - import RoomSize from "./RoomSize" import styles from "./details.module.css" -export default function Details({ roomTypeCode }: { roomTypeCode: string }) { +import type { RoomInfo } from "@/contexts/SelectRate/types" + +type Props = { + roomInfo: RoomInfo +} + +export default function Details({ roomInfo }: Props) { const intl = useIntl() - const roomCategories = useRatesStore((state) => state.roomCategories) - const selectedRoom = roomCategories.find((roomCategory) => - roomCategory.roomTypes.find((roomType) => roomType.code === roomTypeCode) - ) - - const { name, occupancy, roomSize } = selectedRoom || {} + const { name, occupancy, roomSize } = roomInfo || {} return ( <> diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx index 4ac7c1f9a..db027d0d8 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx @@ -6,30 +6,32 @@ import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/booki import { Divider } from "@scandic-hotels/design-system/Divider" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRatesStore } from "@/stores/select-rate" - -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import { isValidClientSession } from "@/utils/clientSession" import { getBreakfastMessage } from "./getBreakfastMessage" import styles from "./breakfastMessage.module.css" -export default function BreakfastMessage({ +export function BreakfastMessage({ breakfastIncludedMember, breakfastIncludedStandard, hasRegularRates, + roomIndex, }: { breakfastIncludedMember: boolean breakfastIncludedStandard: boolean hasRegularRates: boolean + roomIndex: number }) { const intl = useIntl() - const { roomNr, selectedFilter } = useRoomContext() + const { hotel } = useSelectRateContext() + const roomNr = roomIndex + 1 + + // TODO: Replace with context value when we have support for dropdown "Show all rates" + const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum + const hotelType = hotel.data?.hotel.hotelType - const { hotelType } = useRatesStore((state) => ({ - hotelType: state.hotelType, - })) const { data: session } = useSession() const isUserLoggedIn = isValidClientSession(session) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx index d416a68c2..3eac9f406 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx @@ -1,5 +1,4 @@ "use client" -import { useSession } from "next-auth/react" import { useIntl } from "react-intl" import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" @@ -10,33 +9,34 @@ import { sumPackages, sumPackagesRequestedPrice, } from "@/components/HotelReservation/utils" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import useRateTitles from "@/hooks/booking/useRateTitles" -import { isValidClientSession } from "@/utils/clientSession" +import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" -import { isSelectedPriceProduct } from "./isSelected" import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" -import type { PriceProduct } from "@scandic-hotels/trpc/types/roomAvailability" +import type { + AvailabilityWithRoomInfo, + Package, +} from "@/contexts/SelectRate/types" -import type { SharedRateCardProps } from "@/types/components/hotelReservation/selectRate/rates" - -interface CampaignProps extends SharedRateCardProps { - campaign: PriceProduct[] +type CampaignProps = { + nights: number + campaign: AvailabilityWithRoomInfo["campaign"] + roomIndex: number + roomTypeCode: string + selectedPackages: Package[] } export default function Campaign({ campaign, - handleSelectRate, + roomIndex, nights, roomTypeCode, + selectedPackages, }: CampaignProps) { - const intl = useIntl() - const { roomNr, selectedFilter, selectedPackages, selectedRate } = - useRoomContext() - const rateTitles = useRateTitles() - const { data: session } = useSession() - const isPrimaryRoomAndLoggedIn = isValidClientSession(session) && roomNr === 1 + // TODO: Replace with context value when we have support for dropdown "Show all rates" + const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum const isCampaignRate = campaign.some( (c) => @@ -47,19 +47,52 @@ export default function Campaign({ return null } + if (selectedFilter === BookingCodeFilterEnum.Discounted) { + campaign = campaign.filter((product) => product.bookingCode) + } + + return campaign.map((product, ix) => { + return ( + + ) + }) +} + +function Inner({ + product, + roomIndex, + roomTypeCode, + selectedPackages, + nights, +}: { + roomIndex: number + nights: number + roomTypeCode: string + product: AvailabilityWithRoomInfo["campaign"][number] + selectedPackages: Package[] +}) { + const roomNr = roomIndex + 1 + const { + isRateSelected, + actions: { selectRate }, + } = useSelectRateContext() + + const rateTitles = useRateTitles() + const isUserLoggedIn = useIsUserLoggedIn() + const intl = useIntl() const night = intl .formatMessage({ defaultMessage: "night", }) .toUpperCase() - if (selectedFilter === BookingCodeFilterEnum.Discounted) { - campaign = campaign.filter((product) => product.bookingCode) - } - - const pkgsSum = sumPackages(selectedPackages) - const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) - const standardPriceMsg = intl.formatMessage({ defaultMessage: "Standard price", }) @@ -68,165 +101,184 @@ export default function Campaign({ defaultMessage: "Member price", }) - return campaign.map((product) => { - if (!product.public) { - return ( - - ) - } - - const rateTermDetails = product.rateDefinitionMember - ? [ - { - title: product.bookingCode - ? product.rateDefinition.title - : standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - { - title: product.bookingCode - ? product.rateDefinitionMember.title - : memberPriceMsg, - terms: product.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: product.bookingCode - ? product.rateDefinition.title - : standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - ] - - const isSelected = isSelectedPriceProduct( - product, - selectedRate, - roomTypeCode - ) - - let bannerText = intl.formatMessage({ - defaultMessage: "Campaign", - }) - if (product.bookingCode) { - bannerText = product.bookingCode - } - - if (product.rateDefinition.breakfastIncluded) { - bannerText = `${bannerText} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast included", - })}` - } else { - bannerText = `${bannerText} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast excluded", - })}` - } - - const pricePerNight = calculatePricePerNightPriceProduct( - product.public.localPrice.pricePerNight, - product.public.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - - const pricePerNightMember = product.member - ? calculatePricePerNightPriceProduct( - product.member.localPrice.pricePerNight, - product.member.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - - let approximateRatePrice = undefined - if (isPrimaryRoomAndLoggedIn && pricePerNightMember) { - approximateRatePrice = pricePerNightMember.totalRequestedPrice - } else if ( - pricePerNight.totalRequestedPrice && - pricePerNightMember?.totalRequestedPrice - ) { - approximateRatePrice = `${pricePerNight.totalRequestedPrice}/${pricePerNightMember.totalRequestedPrice}` - } else if (pricePerNight.totalRequestedPrice) { - approximateRatePrice = pricePerNight.totalRequestedPrice - } - - const approximateRate = - approximateRatePrice && product.public.requestedPrice - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: approximateRatePrice, - unit: product.public.requestedPrice.currency, - } - : undefined - const campaignMemberLabel = - product.rateDefinitionMember?.title || memberPriceMsg + if (!product.public) { return ( - handleSelectRate(product)} - isSelected={isSelected} - isHighlightedRate={ - !!product.rateDefinition?.displayPriceRed || isPrimaryRoomAndLoggedIn - } - memberRate={ - pricePerNightMember && !isPrimaryRoomAndLoggedIn - ? { - label: memberPriceMsg, - price: pricePerNightMember.totalPrice, - unit: `${product.member!.localPrice.currency}/${night}`, - } - : undefined - } - comparisonRate={ - isPrimaryRoomAndLoggedIn - ? { - price: pricePerNight.totalPrice, - unit: product.public.localPrice.currency, - } - : undefined - } - name={`rateCode-${roomNr}-${product.public.rateCode}`} + noPricesAvailableText={rateTitles.noPriceAvailable} paymentTerm={rateTitles[product.rate].paymentTerm} - rate={{ - label: isPrimaryRoomAndLoggedIn - ? campaignMemberLabel - : standardPriceMsg, - price: - isPrimaryRoomAndLoggedIn && pricePerNightMember - ? pricePerNightMember.totalPrice - : pricePerNight.totalPrice, - unit: `${product.public.localPrice.currency}/${night}`, - }} rateTitle={rateTitles[product.rate].title} - omnibusRate={ - product.public.localPrice.omnibusPricePerNight - ? { - label: intl - .formatMessage({ - defaultMessage: "Lowest price (last 30 days)", - }) - .toUpperCase(), - price: - product.public.localPrice.omnibusPricePerNight.toString(), - unit: product.public.localPrice.currency, - } - : undefined - } - rateTermDetails={rateTermDetails} - value={product.public.rateCode} + variant="Campaign" /> ) + } + + const rateTermDetails = product.rateDefinitionMember + ? [ + { + title: product.bookingCode + ? product.rateDefinition.title + : standardPriceMsg, + terms: product.rateDefinition.generalTerms, + }, + { + title: product.bookingCode + ? product.rateDefinitionMember.title + : memberPriceMsg, + terms: product.rateDefinitionMember.generalTerms, + }, + ] + : [ + { + title: product.bookingCode + ? product.rateDefinition.title + : standardPriceMsg, + terms: product.rateDefinition.generalTerms, + }, + ] + const isSelected = isRateSelected({ + roomIndex, + rate: { ...product, type: "campaign" }, + roomTypeCode, }) + + let bannerText = intl.formatMessage({ + defaultMessage: "Campaign", + }) + if (product.bookingCode) { + bannerText = product.bookingCode + } + + if (product.rateDefinition.breakfastIncluded) { + bannerText = `${bannerText} ∙ ${intl.formatMessage({ + defaultMessage: "Breakfast included", + })}` + } else { + bannerText = `${bannerText} ∙ ${intl.formatMessage({ + defaultMessage: "Breakfast excluded", + })}` + } + + const pkgsSum = sumPackages(selectedPackages) + const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) + + const pricePerNight = calculatePricePerNightPriceProduct( + product.public.localPrice.pricePerNight, + product.public.requestedPrice?.pricePerNight, + nights, + pkgsSum.price, + pkgsSumRequested.price + ) + + const pricePerNightMember = product.member + ? calculatePricePerNightPriceProduct( + product.member.localPrice.pricePerNight, + product.member.requestedPrice?.pricePerNight, + nights, + pkgsSum.price, + pkgsSumRequested.price + ) + : undefined + + const isMainRoom = roomIndex === 0 + const isMainRoomAndLoggedIn = isMainRoom && isUserLoggedIn + + let approximateRatePrice = undefined + if (isMainRoomAndLoggedIn && pricePerNightMember) { + approximateRatePrice = pricePerNightMember.totalRequestedPrice + } else if ( + pricePerNight.totalRequestedPrice && + pricePerNightMember?.totalRequestedPrice + ) { + approximateRatePrice = `${pricePerNight.totalRequestedPrice}/${pricePerNightMember.totalRequestedPrice}` + } else if (pricePerNight.totalRequestedPrice) { + approximateRatePrice = pricePerNight.totalRequestedPrice + } + + const approximateRate = + approximateRatePrice && product.public.requestedPrice + ? { + label: intl.formatMessage({ + defaultMessage: "Approx.", + }), + price: approximateRatePrice, + unit: product.public.requestedPrice.currency, + } + : undefined + + const rateCode = isMainRoomAndLoggedIn + ? product.member!.rateCode + : product.public!.rateCode + + const counterRateCode = isMainRoomAndLoggedIn + ? product.public?.rateCode + : product.member?.rateCode + + const campaignMemberLabel = + product.rateDefinitionMember?.title || memberPriceMsg + + return ( + + selectRate({ + roomIndex, + rateCode: rateCode, + counterRateCode: counterRateCode, + roomTypeCode, + bookingCode: product.bookingCode, + }) + } + isSelected={isSelected} + isHighlightedRate={ + !!product.rateDefinition?.displayPriceRed || isMainRoomAndLoggedIn + } + memberRate={ + pricePerNightMember && !isMainRoomAndLoggedIn + ? { + label: memberPriceMsg, + price: pricePerNightMember.totalPrice, + unit: `${product.member!.localPrice.currency}/${night}`, + } + : undefined + } + comparisonRate={ + isMainRoomAndLoggedIn + ? { + price: pricePerNight.totalPrice, + unit: product.public.localPrice.currency, + } + : undefined + } + name={`rateCode-${roomNr}-${product.public.rateCode}`} + paymentTerm={rateTitles[product.rate].paymentTerm} + rate={{ + label: isMainRoomAndLoggedIn ? campaignMemberLabel : standardPriceMsg, + price: + isMainRoomAndLoggedIn && pricePerNightMember + ? pricePerNightMember.totalPrice + : pricePerNight.totalPrice, + + unit: `${product.public.localPrice.currency}/${night}`, + }} + rateTitle={rateTitles[product.rate].title} + omnibusRate={ + product.public.localPrice.omnibusPricePerNight + ? { + label: intl + .formatMessage({ + defaultMessage: "Lowest price (last 30 days)", + }) + .toUpperCase(), + price: product.public.localPrice.omnibusPricePerNight.toString(), + unit: product.public.localPrice.currency, + } + : undefined + } + rateTermDetails={rateTermDetails} + value={product.public.rateCode} + /> + ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx index 8bd9b3f0e..bb3a19b00 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx @@ -4,230 +4,387 @@ import { useIntl } from "react-intl" import CodeRateCard from "@scandic-hotels/design-system/CodeRateCard" -import { useRatesStore } from "@/stores/select-rate" - import { sumPackages, sumPackagesRequestedPrice, } from "@/components/HotelReservation/utils" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import useRateTitles from "@/hooks/booking/useRateTitles" -import { - isSelectedCorporateCheque, - isSelectedPriceProduct, - isSelectedVoucher, -} from "./isSelected" import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" import type { CodeProduct } from "@scandic-hotels/trpc/types/roomAvailability" -import type { SharedRateCardProps } from "@/types/components/hotelReservation/selectRate/rates" +import type { Package } from "@/contexts/SelectRate/types" -interface CodeProps extends SharedRateCardProps { +type CodeProps = { + nights: number + roomTypeCode: string code: CodeProduct[] + roomIndex: number + selectedPackages: Package[] } export default function Code({ code, - handleSelectRate, nights, roomTypeCode, + roomIndex, + selectedPackages, }: CodeProps) { + return code.map((product) => { + return ( + + ) + }) +} + +function InnerCode({ + codeProduct, + roomIndex, + roomTypeCode, + nights, + selectedPackages, +}: { + codeProduct: CodeProduct + roomIndex: number + roomTypeCode: string + nights: number + selectedPackages: Package[] +}) { + const { + input: { bookingCode }, + actions: { selectRate }, + isRateSelected, + } = useSelectRateContext() + + function handleSelectRate(rateCode: string) { + selectRate({ roomIndex, rateCode, roomTypeCode }) + } + + const bannerText = useBannerText({ + bookingCode: bookingCode ?? "", + breakfastIncluded: codeProduct.rateDefinition.breakfastIncluded, + }) + + const pkgsSum = sumPackages(selectedPackages) + const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) + + const isSelected = isRateSelected({ + roomIndex, + roomTypeCode, + rate: { ...codeProduct, type: "code" }, + }) + + if ("corporateCheque" in codeProduct) { + return ( + + ) + } + + if ("voucher" in codeProduct) { + return ( + + ) + } + + if (codeProduct.public) { + return ( + + ) + } + + return null +} + +function useBannerText({ + bookingCode, + breakfastIncluded, +}: { + breakfastIncluded: boolean + bookingCode: string +}) { + const intl = useIntl() + + if (breakfastIncluded) { + return `${bookingCode} ∙ ${intl.formatMessage({ + defaultMessage: "Breakfast included", + })}` + } else { + return `${bookingCode} ∙ ${intl.formatMessage({ + defaultMessage: "Breakfast excluded", + })}` + } +} + +function CorporateChequeCode({ + codeProduct, + roomIndex, + bannerText, + packagesSum, + handleSelectRate, + isSelected, +}: { + codeProduct: Extract + roomIndex: number + roomTypeCode: string + bannerText: string + packagesSum: ReturnType + handleSelectRate: (rateCode: string) => void + isSelected: boolean +}) { + const roomNr = roomIndex + 1 const intl = useIntl() - const { roomNr, selectedPackages, selectedRate } = useRoomContext() - const bookingCode = useRatesStore((state) => state.booking.bookingCode) const rateTitles = useRateTitles() + const { localPrice, rateCode, requestedPrice } = codeProduct.corporateCheque + + const rateTermDetails = getRateTermDetails(codeProduct) + + let price = `${localPrice.numberOfCheques} CC` + + if (localPrice.additionalPricePerStay) { + price = `${price} + ${localPrice.additionalPricePerStay + packagesSum.price}` + } else if (packagesSum.price) { + price = `${price} + ${packagesSum.price}` + } + + const currency = + localPrice.additionalPricePerStay > 0 || packagesSum.price > 0 + ? (localPrice.currency ?? packagesSum.currency ?? "") + : "" + + const approximateRate = + requestedPrice?.additionalPricePerStay && requestedPrice?.currency + ? { + label: intl.formatMessage({ + defaultMessage: "Approx.", + }), + price: + `${requestedPrice.numberOfCheques} CC + ` + + requestedPrice.additionalPricePerStay, + unit: requestedPrice.currency, + } + : undefined + + return ( + + handleSelectRate(codeProduct.corporateCheque.rateCode) + } + isSelected={isSelected} + name={`rateCode-${roomNr}-${rateCode}`} + paymentTerm={rateTitles[codeProduct.rate].paymentTerm} + rate={{ + label: codeProduct.rateDefinition?.title, + price, + unit: currency, + }} + rateTitle={rateTitles[codeProduct.rate].title} + rateTermDetails={rateTermDetails} + value={rateCode} + /> + ) +} + +function PublicCode({ + codeProduct, + roomIndex, + bannerText, + packagesSum, + packagesSumRequested, + nights, + handleSelectRate, + isSelected, +}: { + codeProduct: Extract + roomIndex: number + roomTypeCode: string + bannerText: string + packagesSum: ReturnType + packagesSumRequested: ReturnType + nights: number + handleSelectRate: (rateCode: string) => void + isSelected: boolean +}) { + const roomNr = roomIndex + 1 + const intl = useIntl() + const rateTitles = useRateTitles() + if (!codeProduct.public) { + return null + } + + const rateTermDetails = getRateTermDetails(codeProduct) + const night = intl .formatMessage({ defaultMessage: "night", }) .toUpperCase() - return code.map((product) => { - let bannerText = "" - if (product.rateDefinition.breakfastIncluded) { - bannerText = `${bookingCode} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast included", - })}` - } else { - bannerText = `${bookingCode} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast excluded", - })}` - } - const rateTermDetails = product.rateDefinitionMember - ? [ - { - title: product.rateDefinition.title, - terms: product.rateDefinition.generalTerms, - }, - { - title: product.rateDefinitionMember.title, - terms: product.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: product.rateDefinition.title, - terms: product.rateDefinition.generalTerms, - }, - ] + const { localPrice, rateCode, requestedPrice } = codeProduct.public + const pricePerNight = calculatePricePerNightPriceProduct( + localPrice.pricePerNight, + requestedPrice?.pricePerNight, + nights, + packagesSum.price, + packagesSumRequested.price + ) - const pkgsSum = sumPackages(selectedPackages) - const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) + const approximateRate = + pricePerNight.totalRequestedPrice && requestedPrice?.currency + ? { + label: intl.formatMessage({ + defaultMessage: "Approx.", + }), + price: pricePerNight.totalRequestedPrice, + unit: requestedPrice.currency, + } + : undefined - if ("corporateCheque" in product) { - const { localPrice, rateCode, requestedPrice } = product.corporateCheque - let price = `${localPrice.numberOfCheques} CC` - if (localPrice.additionalPricePerStay) { - price = `${price} + ${localPrice.additionalPricePerStay + pkgsSum.price}` - } else if (pkgsSum.price) { - price = `${price} + ${pkgsSum.price}` - } + const regularPricePerNight = calculatePricePerNightPriceProduct( + localPrice.regularPricePerNight, + requestedPrice?.regularPricePerNight, + nights, + packagesSum.price, + packagesSumRequested.price + ) - const isSelected = isSelectedCorporateCheque( - product, - selectedRate, - roomTypeCode - ) + const comparisonRate = + +regularPricePerNight.totalPrice > +pricePerNight.totalPrice + ? { + price: regularPricePerNight.totalPrice, + unit: localPrice.currency, + } + : undefined - const currency = - localPrice.additionalPricePerStay > 0 || pkgsSum.price > 0 - ? (localPrice.currency ?? pkgsSum.currency ?? "") - : "" - - const approximateRate = - requestedPrice?.additionalPricePerStay && requestedPrice?.currency - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: - `${requestedPrice.numberOfCheques} CC + ` + - requestedPrice.additionalPricePerStay, - unit: requestedPrice.currency, - } - : undefined - - return ( - handleSelectRate(product)} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[product.rate].paymentTerm} - rate={{ - label: product.rateDefinition?.title, - price, - unit: currency, - }} - rateTitle={rateTitles[product.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) - } - - if ("voucher" in product) { - const { numberOfVouchers, rateCode } = product.voucher - const isSelected = isSelectedVoucher(product, selectedRate, roomTypeCode) - - const voucherMsg = intl - .formatMessage({ - defaultMessage: "Voucher", - }) - .toUpperCase() - let price = `${numberOfVouchers} ${voucherMsg}` - if (pkgsSum.price) { - price = `${price} + ${pkgsSum.price}` - } - return ( - handleSelectRate(product)} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[product.rate].paymentTerm} - rate={{ - label: product.rateDefinition?.title, - price, - unit: pkgsSum.currency ?? "", - }} - rateTitle={rateTitles[product.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) - } - - if (product.public) { - const { localPrice, rateCode, requestedPrice } = product.public - const pricePerNight = calculatePricePerNightPriceProduct( - localPrice.pricePerNight, - requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - - const approximateRate = - pricePerNight.totalRequestedPrice && requestedPrice?.currency - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: pricePerNight.totalRequestedPrice, - unit: requestedPrice.currency, - } - : undefined - - const regularPricePerNight = calculatePricePerNightPriceProduct( - localPrice.regularPricePerNight, - requestedPrice?.regularPricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - - const comparisonRate = - +regularPricePerNight.totalPrice > +pricePerNight.totalPrice - ? { - price: regularPricePerNight.totalPrice, - unit: localPrice.currency, - } - : undefined - - const isSelected = isSelectedPriceProduct( - product, - selectedRate, - roomTypeCode - ) - - return ( - handleSelectRate(product)} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[product.rate].paymentTerm} - rate={{ - label: product.rateDefinition?.title, - price: pricePerNight.totalPrice, - unit: `${localPrice.currency}/${night}`, - }} - rateTitle={rateTitles[product.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) - } - - return null - }) + return ( + handleSelectRate(codeProduct.public!.rateCode)} + isSelected={isSelected} + name={`rateCode-${roomNr}-${rateCode}`} + paymentTerm={rateTitles[codeProduct.rate].paymentTerm} + rate={{ + label: codeProduct.rateDefinition?.title, + price: pricePerNight.totalPrice, + unit: `${localPrice.currency}/${night}`, + }} + rateTitle={rateTitles[codeProduct.rate].title} + rateTermDetails={rateTermDetails} + value={rateCode} + /> + ) } + +function VoucherCode({ + codeProduct, + bannerText, + packagesSum, + roomIndex, + handleSelectRate, + isSelected, +}: { + codeProduct: Extract + roomIndex: number + roomTypeCode: string + bannerText: string + packagesSum: ReturnType + handleSelectRate: (rateCode: string) => void + isSelected: boolean +}) { + const roomNr = roomIndex + 1 + const intl = useIntl() + const rateTitles = useRateTitles() + const { numberOfVouchers, rateCode } = codeProduct.voucher + + const rateTermDetails = getRateTermDetails(codeProduct) + + const voucherMsg = intl + .formatMessage({ + defaultMessage: "Voucher", + }) + .toUpperCase() + let price = `${numberOfVouchers} ${voucherMsg}` + if (packagesSum.price) { + price = `${price} + ${packagesSum.price}` + } + return ( + handleSelectRate(codeProduct.voucher.rateCode)} + isSelected={isSelected} + name={`rateCode-${roomNr}-${rateCode}`} + paymentTerm={rateTitles[codeProduct.rate].paymentTerm} + rate={{ + label: codeProduct.rateDefinition?.title, + price, + unit: packagesSum.currency ?? "", + }} + rateTitle={rateTitles[codeProduct.rate].title} + rateTermDetails={rateTermDetails} + value={rateCode} + /> + ) +} + +function getRateTermDetails(codeProduct: CodeProduct): RateTermDetails { + return codeProduct.rateDefinitionMember + ? [ + { + title: codeProduct.rateDefinition.title, + terms: codeProduct.rateDefinition.generalTerms, + }, + { + title: codeProduct.rateDefinitionMember.title, + terms: codeProduct.rateDefinitionMember.generalTerms, + }, + ] + : [ + { + title: codeProduct.rateDefinition.title, + terms: codeProduct.rateDefinition.generalTerms, + }, + ] +} + +type RateTermDetails = { title: string; terms: string[] }[] diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx index 74a2e6359..8937efb99 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx @@ -5,25 +5,37 @@ import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/booki import PointsRateCard from "@scandic-hotels/design-system/PointsRateCard" import { sumPackages } from "@/components/HotelReservation/utils" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import useRateTitles from "@/hooks/booking/useRateTitles" -import type { RedemptionProduct } from "@scandic-hotels/trpc/types/roomAvailability" +import type { + AvailabilityWithRoomInfo, + Package, +} from "@/contexts/SelectRate/types" -import type { SharedRateCardProps } from "@/types/components/hotelReservation/selectRate/rates" - -interface RedemptionsProps extends SharedRateCardProps { - redemptions: RedemptionProduct[] +type RedemptionsProps = { + redemptions: AvailabilityWithRoomInfo["redemptions"] + roomTypeCode: string + selectedPackages: Package[] + roomIndex: number } export default function Redemptions({ - handleSelectRate, redemptions, roomTypeCode, + roomIndex, + selectedPackages, }: RedemptionsProps) { const intl = useIntl() const rateTitles = useRateTitles() - const { selectedFilter, selectedPackages, selectedRate } = useRoomContext() + const { + actions: { selectRate }, + selectedRates, + } = useSelectRateContext() + + // TODO: Replace with context value when we have support for dropdown "Show all rates" + const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum + const selectedRate = selectedRates.forRoom(roomIndex) if ( selectedFilter === BookingCodeFilterEnum.Discounted || @@ -44,22 +56,12 @@ export default function Redemptions({ defaultMessage: "Breakfast excluded", }) - let selectedRateCode = "" - if (selectedRate?.product && "redemption" in selectedRate.product) { - selectedRateCode = - selectedRate.roomTypeCode === roomTypeCode - ? selectedRate.product.redemption.rateCode - : "" - } - - function handleSelect(rateCode: string) { - const selectedRedemption = redemptions.find( - (r) => r.redemption.rateCode === rateCode - ) - if (selectedRedemption) { - handleSelectRate(selectedRedemption) - } - } + const selectedRateCode = + selectedRate && + "redemption" in selectedRate && + selectedRate.roomInfo.roomTypeCode === roomTypeCode + ? selectedRate.redemption.rateCode + : "" const rates = redemptions.map((r) => { let additionalPrice @@ -107,7 +109,13 @@ export default function Redemptions({ { + selectRate({ + roomIndex: roomIndex, + rateCode: rateCode, + roomTypeCode: roomTypeCode, + }) + }} paymentTerm={rateTitles[firstRedemption.rate].paymentTerm} rates={rates} rateTitle={rateTitles[firstRedemption.rate].title} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx index 5e619724d..2bfdeb31e 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx @@ -10,16 +10,15 @@ import { sumPackages, sumPackagesRequestedPrice, } from "@/components/HotelReservation/utils" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import useRateTitles from "@/hooks/booking/useRateTitles" import { isValidClientSession } from "@/utils/clientSession" -import { isSelectedPriceProduct } from "./isSelected" import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" -import type { PriceProduct } from "@scandic-hotels/trpc/types/roomAvailability" +import type { Package } from "@scandic-hotels/trpc/types/packages" -import type { SharedRateCardProps } from "@/types/components/hotelReservation/selectRate/rates" +import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" interface Rate { label: string @@ -31,25 +30,64 @@ interface Rates { memberRate?: Rate rate?: Rate } - -interface RegularProps extends SharedRateCardProps { - regular: PriceProduct[] +type RegularRateProps = { + nights: number + regular: AvailabilityWithRoomInfo["regular"] + roomIndex: number + roomTypeCode: string + selectedPackages: Package[] } -export default function Regular({ - handleSelectRate, +export function RegularRate({ nights, regular, roomTypeCode, -}: RegularProps) { - const intl = useIntl() - const rateTitles = useRateTitles() - const { isMainRoom, roomNr, selectedFilter, selectedPackages, selectedRate } = - useRoomContext() + roomIndex, + selectedPackages, +}: RegularRateProps) { const { data: session } = useSession() const isUserLoggedIn = isValidClientSession(session) - if (selectedFilter === BookingCodeFilterEnum.Discounted) { + return regular.map((product, ix) => ( + + )) +} + +function Inner({ + product, + isUserLoggedIn, + nights, + roomTypeCode, + roomIndex, + selectedPackages, +}: { + product: AvailabilityWithRoomInfo["regular"][number] + isUserLoggedIn: boolean + nights: number + roomTypeCode: string + roomIndex: number + selectedPackages: Package[] +}) { + const intl = useIntl() + + const rateTitles = useRateTitles() + const { + isRateSelected, + bookingCodeFilter, + actions: { selectRate }, + } = useSelectRateContext() + + const isMainRoom = roomIndex === 0 + + if (bookingCodeFilter === BookingCodeFilterEnum.Discounted) { return null } @@ -73,139 +111,147 @@ export default function Regular({ defaultMessage: "Approx.", }) - return regular.map((product) => { - const { member, public: standard } = product - const isMainRoomAndLoggedIn = isMainRoom && isUserLoggedIn - const isMainRoomLoggedInWithoutMember = - isMainRoomAndLoggedIn && !product.member - const noRateAvailable = !product.member && !product.public - const isMemberRateActive = isMainRoomAndLoggedIn && !!member - const isNotLoggedInAndOnlyMemberRate = !isUserLoggedIn && !standard - const rateCode = isMemberRateActive ? member.rateCode : standard?.rateCode - if ( - noRateAvailable || - isMainRoomLoggedInWithoutMember || - !rateCode || - isNotLoggedInAndOnlyMemberRate - ) { - return ( - - ) - } - - const memberPricePerNight = member - ? calculatePricePerNightPriceProduct( - member.localPrice.pricePerNight, - member.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - const standardPricePerNight = standard - ? calculatePricePerNightPriceProduct( - standard.localPrice.pricePerNight, - standard.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - - let approximateMemberRatePrice = null - const rates: Rates = {} - if (memberPricePerNight) { - rates.memberRate = { - label: memberPriceMsg, - price: memberPricePerNight.totalPrice, - unit: `${member!.localPrice.currency}/${night}`, - } - - if (memberPricePerNight.totalRequestedPrice) { - approximateMemberRatePrice = memberPricePerNight.totalRequestedPrice - } - } - - let approximateStandardRatePrice = null - if (standardPricePerNight) { - const standardPriceUnit = isMemberRateActive - ? standard!.localPrice.currency - : `${standard!.localPrice.currency}/${night}` - rates.rate = { - label: standardPriceMsg, - price: standardPricePerNight.totalPrice, - unit: standardPriceUnit, - } - - if (standardPricePerNight.totalRequestedPrice && !isUserLoggedIn) { - approximateStandardRatePrice = standardPricePerNight.totalRequestedPrice - } - } - - let approximatePrice = "" - if (approximateStandardRatePrice && approximateMemberRatePrice) { - approximatePrice = `${approximateStandardRatePrice}/${approximateMemberRatePrice}` - } else if (approximateStandardRatePrice) { - approximatePrice = approximateStandardRatePrice - } else if (approximateMemberRatePrice) { - approximatePrice = approximateMemberRatePrice - } - - const requestedCurrency = - standard?.requestedPrice?.currency || member?.requestedPrice?.currency - const approximateRate = - approximatePrice && requestedCurrency - ? { - label: approxMsg, - price: approximatePrice, - unit: requestedCurrency, - } - : undefined - - const isSelected = isSelectedPriceProduct( - product, - selectedRate, - roomTypeCode - ) - - const rateTermDetails = product.rateDefinitionMember - ? [ - { - title: standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - { - title: memberPriceMsg, - terms: product.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - ] + const { member, public: standard } = product + const isMainRoomAndLoggedIn = isMainRoom && isUserLoggedIn + const isMainRoomLoggedInWithoutMember = + isMainRoomAndLoggedIn && !product.member + const noRateAvailable = !product.member && !product.public + const hideStandardPrice = isMainRoomAndLoggedIn && !!member + const isNotLoggedInAndOnlyMemberRate = !isUserLoggedIn && !standard + const rateCode = hideStandardPrice ? member.rateCode : standard?.rateCode + const counterRateCode = isMainRoomAndLoggedIn + ? standard?.rateCode + : member?.rateCode + if ( + noRateAvailable || + isMainRoomLoggedInWithoutMember || + !rateCode || + isNotLoggedInAndOnlyMemberRate + ) { return ( - handleSelectRate(product)} - isMemberRateActive={isMemberRateActive} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} + noPricesAvailableText={rateTitles.noPriceAvailable} paymentTerm={rateTitles[product.rate].paymentTerm} rateTitle={rateTitles[product.rate].title} - value={rateCode} - rateTermDetails={rateTermDetails} + variant="Regular" /> ) + } + + const memberPricePerNight = member + ? calculatePricePerNightPriceProduct( + member.localPrice.pricePerNight, + member.requestedPrice?.pricePerNight, + nights, + pkgsSum.price, + pkgsSumRequested.price + ) + : undefined + const standardPricePerNight = standard + ? calculatePricePerNightPriceProduct( + standard.localPrice.pricePerNight, + standard.requestedPrice?.pricePerNight, + nights, + pkgsSum.price, + pkgsSumRequested.price + ) + : undefined + + let approximateMemberRatePrice = null + const rates: Rates = {} + if (memberPricePerNight) { + rates.memberRate = { + label: memberPriceMsg, + price: memberPricePerNight.totalPrice, + unit: `${member!.localPrice.currency}/${night}`, + } + + if (memberPricePerNight.totalRequestedPrice) { + approximateMemberRatePrice = memberPricePerNight.totalRequestedPrice + } + } + + let approximateStandardRatePrice = null + if (standardPricePerNight) { + rates.rate = { + label: standardPriceMsg, + price: standardPricePerNight.totalPrice, + unit: `${standard!.localPrice.currency}/${night}`, + } + + if (standardPricePerNight.totalRequestedPrice && !isUserLoggedIn) { + approximateStandardRatePrice = standardPricePerNight.totalRequestedPrice + } + } + + let approximatePrice = "" + if (approximateStandardRatePrice && approximateMemberRatePrice) { + approximatePrice = `${approximateStandardRatePrice}/${approximateMemberRatePrice}` + } else if (approximateStandardRatePrice) { + approximatePrice = approximateStandardRatePrice + } else if (approximateMemberRatePrice) { + approximatePrice = approximateMemberRatePrice + } + + const requestedCurrency = + standard?.requestedPrice?.currency || member?.requestedPrice?.currency + const approximateRate = + approximatePrice && requestedCurrency + ? { + label: approxMsg, + price: approximatePrice, + unit: requestedCurrency, + } + : undefined + + const rateTermDetails = product.rateDefinitionMember + ? [ + { + title: standardPriceMsg, + terms: product.rateDefinition.generalTerms, + }, + { + title: memberPriceMsg, + terms: product.rateDefinitionMember.generalTerms, + }, + ] + : [ + { + title: standardPriceMsg, + terms: product.rateDefinition.generalTerms, + }, + ] + + const isSelected = isRateSelected({ + roomIndex, + rate: { ...product, type: "regular" }, + roomTypeCode, }) + + const isMemberRateActive = isUserLoggedIn && isMainRoom && !!member + + return ( + { + selectRate({ + roomIndex: roomIndex, + rateCode: rateCode, + roomTypeCode: roomTypeCode, + counterRateCode: counterRateCode, + }) + }} + isMemberRateActive={isMemberRateActive} + isSelected={isSelected} + name={`rateCode-${roomIndex + 1}-${rateCode}`} + paymentTerm={rateTitles[product.rate].paymentTerm} + rateTitle={rateTitles[product.rate].title} + value={rateCode} + rateTermDetails={rateTermDetails} + /> + ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx index cfed8dbde..95a2dfd53 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx @@ -1,61 +1,50 @@ "use client" -import { useSession } from "next-auth/react" - import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import { dt } from "@scandic-hotels/common/dt" import { Divider } from "@scandic-hotels/design-system/Divider" -import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" -import { useRatesStore } from "@/stores/select-rate" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { useRoomContext } from "@/contexts/SelectRate/Room" -import { isValidClientSession } from "@/utils/clientSession" - -import BreakfastMessage from "./BreakfastMessage" +import { BreakfastMessage } from "./BreakfastMessage" import Campaign from "./Campaign" import Code from "./Code" import Redemptions from "./Redemptions" -import Regular from "./Regular" +import { RegularRate } from "./Regular" -import type { Product } from "@scandic-hotels/trpc/types/roomAvailability" +import type { Package } from "@scandic-hotels/trpc/types/packages" -import type { RatesProps } from "@/types/components/hotelReservation/selectRate/rates" +import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" -export default function Rates({ +export interface RatesProps { + roomConfiguration: AvailabilityWithRoomInfo + roomIndex: number + selectedPackages: Package[] +} +export function Rates({ roomConfiguration: { breakfastIncludedInAllRates, breakfastIncludedInAllRatesMember, campaign, code, - features, redemptions, regular, - roomType, roomTypeCode, }, + selectedPackages, + roomIndex, }: RatesProps) { const { - actions: { selectRate }, - isFetchingAdditionalRate, - selectedFilter, - } = useRoomContext() - const nights = useRatesStore((state) => - dt(state.booking.toDate).diff(state.booking.fromDate, "days") - ) - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) - function handleSelectRate(product: Product) { - selectRate({ features, product, roomType, roomTypeCode }, isUserLoggedIn) - } + bookingCodeFilter, + input: { nights }, + } = useSelectRateContext() const sharedProps = { - handleSelectRate, nights, roomTypeCode, + roomIndex, + selectedPackages, } - - const showAllRates = selectedFilter === BookingCodeFilterEnum.All + const showAllRates = bookingCodeFilter === BookingCodeFilterEnum.All const hasBookingCodeRates = !!(campaign.length || code.length) const hasRegularRates = !!regular.length const showDivider = showAllRates && hasBookingCodeRates && hasRegularRates @@ -66,18 +55,13 @@ export default function Rates({ {showDivider ? : null} - {isFetchingAdditionalRate ? ( - <> - - - - ) : null} - + ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx index c4e22b1c9..02a4cd4f9 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx @@ -1,68 +1,50 @@ "use client" +import { memo } from "react" import { useIntl } from "react-intl" import ImageGallery from "@scandic-hotels/design-system/ImageGallery" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useRatesStore } from "@/stores/select-rate" - import { IconForFeatureCode } from "@/components/HotelReservation/utils" -import { useRoomContext } from "@/contexts/SelectRate/Room" import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" import ToggleSidePeek from "../Details/ToggleSidePeek" import styles from "./image.module.css" -import type { RoomListItemImageProps } from "@/types/components/hotelReservation/selectRate/roomListItem" +import type { ApiImage } from "@scandic-hotels/trpc/types/hotel" +import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" +import type { RoomConfiguration } from "@scandic-hotels/trpc/types/roomAvailability" -export default function RoomImage({ - roomPackages, +export type RoomListItemImageProps = Pick< + RoomConfiguration, + "roomType" | "roomTypeCode" | "roomsLeft" +> & { + selectedPackages: PackageEnum[] + images: ApiImage[] + hotelId: string +} + +const RoomImage = memo(function RoomImage({ roomsLeft, roomType, roomTypeCode, + selectedPackages, + images, + hotelId, }: RoomListItemImageProps) { - const intl = useIntl() - const { selectedPackages } = useRoomContext() - const { roomCategories, hotelId } = useRatesStore((state) => ({ - roomCategories: state.roomCategories, - hotelId: state.booking.hotelId, - })) - - const showLowInventory = roomsLeft > 0 && roomsLeft < 5 - - const selectedRoom = roomCategories.find((roomCategory) => - roomCategory.roomTypes.find((roomType) => roomType.code === roomTypeCode) - ) - - const galleryImages = mapApiImagesToGalleryImages(selectedRoom?.images || []) + const galleryImages = mapApiImagesToGalleryImages(images || []) return (
- {showLowInventory ? ( - - -

- {intl.formatMessage( - { - defaultMessage: "{amount, number} left", - }, - { amount: roomsLeft } - )} -

-
+ + + {selectedPackages.map((pkg) => ( + + {IconForFeatureCode({ featureCode: pkg, size: 16 })} - ) : null} - {roomPackages - .filter((pkg) => - selectedPackages.find((spkg) => spkg.code === pkg.code) - ) - .map((pkg) => ( - - {IconForFeatureCode({ featureCode: pkg.code, size: 16 })} - - ))} + ))}
) +}) + +export default RoomImage + +function LowInventoryTag({ roomsLeft }: { roomsLeft: number }) { + const intl = useIntl() + const showLowInventory = roomsLeft > 0 && roomsLeft < 5 + + if (!showLowInventory) { + return null + } + + return ( + + +

+ {intl.formatMessage( + { + defaultMessage: "{amount, number} left", + }, + { amount: roomsLeft } + )} +

+
+
+ ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx index 80c639ff1..64726dd25 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx @@ -1,24 +1,37 @@ -"use client" - import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" -import { useRoomContext } from "@/contexts/SelectRate/Room" - import Details from "./Details" import { listItemVariants } from "./listItemVariants" -import Rates from "./Rates" +import { Rates } from "./Rates" import RoomImage from "./RoomImage" import RoomNotAvailable from "./RoomNotAvailable" import styles from "./roomListItem.module.css" -import type { RoomListItemProps } from "@/types/components/hotelReservation/selectRate/roomListItem" +import type { Package } from "@scandic-hotels/trpc/types/packages" + +import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" + +export type RoomListItemProps = { + room: AvailabilityWithRoomInfo + selectedPackages: Package[] + roomIndex: number + hotelId: string +} + +export function RoomListItem({ + room, + selectedPackages, + roomIndex, + hotelId, +}: RoomListItemProps) { + if (!room || !room.roomInfo) { + return null + } -export default function RoomListItem({ roomConfiguration }: RoomListItemProps) { - const { roomPackages } = useRoomContext() const classNames = listItemVariants({ availability: - roomConfiguration.status === AvailabilityEnum.NotAvailable + room.status === AvailabilityEnum.NotAvailable ? "noAvailability" : "default", }) @@ -26,18 +39,24 @@ export default function RoomListItem({ roomConfiguration }: RoomListItemProps) { return (
  • pkg.code)} + images={room.roomInfo.images ?? []} + hotelId={hotelId} /> -
    +
    - {roomConfiguration.status === AvailabilityEnum.NotAvailable ? ( + {room.status === AvailabilityEnum.NotAvailable ? ( ) : ( - + )}
  • diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx index db21b079b..e3b3df8b7 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx @@ -1,43 +1,52 @@ "use client" import { useEffect } from "react" -import { useRatesStore } from "@/stores/select-rate" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import styles from "./rooms.module.css" export default function ScrollToList() { - const { isSingleRoomAndHasSelection } = useRatesStore((state) => ({ - isSingleRoomAndHasSelection: - state.booking.rooms.length === 1 && !!state.rateSummary.length, - })) + const { + input: { isMultiRoom }, + selectedRates, + } = useSelectRateContext() + const selectedRateCode = selectedRates.rates[0] + ? `${selectedRates.rates[0].rateDefinition.rateCode}${selectedRates.rates[0].roomInfo.roomTypeCode}` + : null useEffect(() => { - if (isSingleRoomAndHasSelection) { - // Required to prevent the history.pushState on the first selection - // to scroll user back to top - requestAnimationFrame(() => { - const SCROLL_OFFSET = 173 // summary on mobile is 163px - - const selectedRateCard: HTMLElement | null = document.querySelector( - `.${styles.roomList} label:has(input[type=radio]:checked)` - ) - - if (selectedRateCard) { - const elementPosition = selectedRateCard.getBoundingClientRect().top - const windowHeight = window.innerHeight - const offsetPosition = - elementPosition + - window.scrollY - - (windowHeight - selectedRateCard.offsetHeight - SCROLL_OFFSET) - - window.scrollTo({ - top: offsetPosition, - behavior: "instant", - }) - } - }) + if (isMultiRoom) { + return } - }, [isSingleRoomAndHasSelection]) + + if (!selectedRateCode) { + return + } + + // Required to prevent the history.pushState on the first selection + // to scroll user back to top + requestAnimationFrame(() => { + const SCROLL_OFFSET = 173 // summary on mobile is 163px + + const selectedRateCard: HTMLElement | null = document.querySelector( + `.${styles.roomList} label:has(input[type=radio]:checked)` + ) + + if (selectedRateCard) { + const elementPosition = selectedRateCard.getBoundingClientRect().top + const windowHeight = window.innerHeight + const offsetPosition = + elementPosition + + window.scrollY - + (windowHeight - selectedRateCard.offsetHeight - SCROLL_OFFSET) + + window.scrollTo({ + top: offsetPosition, + behavior: "instant", + }) + } + }) + }, [isMultiRoom, selectedRateCode]) return null } 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 64764c880..174c5b3a2 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 @@ -1,27 +1,40 @@ "use client" -import { useRoomContext } from "@/contexts/SelectRate/Room" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import RoomListItem from "./RoomListItem" +import { RoomListItem } from "./RoomListItem" import { RoomsListSkeleton } from "./RoomsListSkeleton" import ScrollToList from "./ScrollToList" import styles from "./rooms.module.css" -export default function RoomsList() { - const { isFetchingPackages, rooms } = useRoomContext() - if (isFetchingPackages) { +export default function RoomsList({ roomIndex }: { roomIndex: number }) { + const { getAvailabilityForRoom, isFetching, input, getPackagesForRoom } = + useSelectRateContext() + + if (isFetching) { return } + + const hotelId = input?.data?.booking.hotelId + if (!hotelId) { + throw new Error("Hotel ID is required to display room availability") + } + return ( <>
      - {rooms.map((roomConfiguration) => ( - - ))} + {getAvailabilityForRoom(roomIndex)?.map((room, ix) => { + return ( + + ) + })}
    ) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/index.tsx index fdc6b10ce..95aa2049f 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/index.tsx @@ -1,92 +1,39 @@ "use client" -import { useEffect } from "react" -import { useRatesStore } from "@/stores/select-rate" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import RoomProvider from "@/providers/SelectRate/RoomProvider" -import { trackLowestRoomPrice } from "@/utils/tracking" - -import MultiRoomWrapper from "./MultiRoomWrapper" +import { MultiRoomWrapper } from "./MultiRoomWrapper" import NoAvailabilityAlert from "./NoAvailabilityAlert" -import RoomsHeader from "./RoomsHeader" +import { RoomsHeader } from "./RoomsHeader" import RoomsList from "./RoomsList" import styles from "./rooms.module.css" -import type { PriceProduct } from "@scandic-hotels/trpc/types/roomAvailability" - export default function Rooms() { const { - arrivalDate, - bookingRooms, - departureDate, - hotelId, - rooms, - visibleRooms, - } = useRatesStore((state) => ({ - arrivalDate: state.booking.fromDate, - bookingRooms: state.booking.rooms, - departureDate: state.booking.toDate, - hotelId: state.booking.hotelId, - rooms: state.rooms, - visibleRooms: state.roomConfigurations, - })) + availability, + input: { isMultiRoom }, + } = useSelectRateContext() - useEffect(() => { - const pricesWithCurrencies = visibleRooms.flatMap((roomConfiguration) => - roomConfiguration.flatMap((room) => - room.products - .filter( - (product): product is PriceProduct => - !!( - ("public" in product && product.public) || - ("member" in product && product.member) - ) - ) - .map((product) => ({ - currency: (product.public?.localPrice.currency || - product.member?.localPrice.currency)!, - price: (product.public?.localPrice.pricePerNight || - product.member?.localPrice.pricePerNight)!, - })) - ) - ) - - // Specific n/a when no prices available in reward night and voucher scenarios - const lowestPrice = pricesWithCurrencies.length - ? pricesWithCurrencies - .reduce((minPrice, { price }) => Math.min(minPrice, price), Infinity) - .toString() - : "n/a" - - const currency = pricesWithCurrencies.length - ? pricesWithCurrencies[0]?.currency - : "n/a" - - trackLowestRoomPrice({ - hotelId, - arrivalDate, - departureDate, - lowestPrice: lowestPrice, - currency: currency, - }) - }, [arrivalDate, departureDate, hotelId, visibleRooms]) + if (!availability) { + return null + } return (
    - {bookingRooms.map((room, idx) => ( - - 1}> - - - + {availability.data?.map((_room, idx) => { + return ( + + + + - - ))} + ) + })}
    ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx index 08870fe05..4cd54f188 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/index.tsx @@ -1,69 +1,37 @@ "use client" -import { notFound, useSearchParams } from "next/navigation" +import { TRPCClientError } from "@trpc/client" import { useIntl } from "react-intl" -import { - parseSelectRateSearchParams, - searchParamsToRecord, -} from "@scandic-hotels/booking-flow/utils/url" -import { trpc } from "@scandic-hotels/trpc/client" -import { selectRateRoomsAvailabilityInputSchema } from "@scandic-hotels/trpc/routers/hotels/input" import { AlertTypeEnum } from "@scandic-hotels/trpc/types/alertType" import Alert from "@/components/TempDesignSystem/Alert" -import useLang from "@/hooks/useLang" -import RatesProvider from "@/providers/RatesProvider" +import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import RateSummary from "./RateSummary" +import { RateSummary } from "./RateSummary" import Rooms from "./Rooms" import { RoomsContainerSkeleton } from "./RoomsContainerSkeleton" import styles from "./index.module.css" +import type { AppRouter } from "@scandic-hotels/trpc/routers/appRouter" + import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer" -export function RoomsContainer({ - hotelType, - roomCategories, - vat, -}: RoomsContainerProps) { - const lang = useLang() +export function RoomsContainer({}: RoomsContainerProps) { const intl = useIntl() - const searchParams = useSearchParams() - const booking = parseSelectRateSearchParams( - searchParamsToRecord(searchParams) - ) - - if (!booking) return notFound() - - const bookingInput = selectRateRoomsAvailabilityInputSchema.safeParse({ - booking, - lang, - }) - - const { data, isFetching, isError, error } = - trpc.hotel.availability.selectRate.rooms.useQuery(bookingInput.data!, { - retry(failureCount, error) { - if (error.data?.code === "BAD_REQUEST") { - return false - } - - return failureCount <= 3 - }, - enabled: bookingInput.success, - }) + const { + availability: { error, isFetching, isError }, + input: { hasError: hasInputError }, + } = useSelectRateContext() if (isFetching) { return } - if (isError || !bookingInput.success) { - const errorMessage = getErrorMessage( - error?.data?.zodError?.formErrors, - intl - ) + if (isError || hasInputError) { + const errorMessage = getErrorMessage(error, intl) return (
    @@ -73,24 +41,21 @@ export function RoomsContainer({ } return ( - + <> - + ) } -function getErrorMessage( - formErrors: string[] | undefined, - intl: ReturnType -) { - const firstError = formErrors?.at(0) +function getErrorMessage(error: unknown, intl: ReturnType) { + if (!isTRPCClientError(error)) { + return intl.formatMessage({ + defaultMessage: "Something went wrong", + }) + } + + const firstError = error.data?.zodError?.formErrors?.at(0) switch (firstError) { case "FROMDATE_INVALID": @@ -107,3 +72,9 @@ function getErrorMessage( }) } } + +function isTRPCClientError( + cause: unknown +): cause is TRPCClientError { + return cause instanceof TRPCClientError +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx index fa792b372..00cfec116 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx @@ -1,55 +1,35 @@ import { cookies } from "next/headers" -import { notFound } from "next/navigation" import { FamilyAndFriendsCodes } from "@/constants/booking" -import { getHotel } from "@/lib/trpc/memoizedRequests" -import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" +import { HotelInfoCard } from "@/components/HotelReservation/SelectRate/HotelInfoCard" import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer" -import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert" import AvailabilityError from "./AvailabilityError" import Tracking from "./Tracking" -import type { Lang } from "@scandic-hotels/common/constants/language" +import type { RouterOutput } from "@scandic-hotels/trpc/client" import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate" export default async function SelectRatePage({ - lang, booking, + hotelData, }: { - lang: Lang + hotelData: NonNullable booking: SelectRateBooking }) { - const searchDetails = await getHotelSearchDetails(booking) - if (!searchDetails?.hotel) { - return notFound() - } - - const hotelData = await getHotel({ - hotelId: searchDetails.hotel.id, - isCardOnlyPayment: false, - language: lang, - }) - - if (!hotelData) { - return notFound() - } + const bookingCode = booking.bookingCode let isInValidFNF = false - if ( - booking.bookingCode && - FamilyAndFriendsCodes.includes(booking.bookingCode) - ) { + if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) { const cookieStore = await cookies() isInValidFNF = cookieStore.get("sc")?.value !== "1" } - return ( <> - + {isInValidFNF ? ( @@ -62,8 +42,8 @@ export default async function SelectRatePage({ )} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/AvailabilityError.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/AvailabilityError.tsx deleted file mode 100644 index a58096fc9..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/AvailabilityError.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client" - -import { usePathname, useSearchParams } from "next/navigation" -import { useEffect } from "react" -import { useIntl } from "react-intl" - -import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode" - -import { toast } from "@/components/TempDesignSystem/Toasts" - -export default function AvailabilityError() { - const intl = useIntl() - const pathname = usePathname() - const searchParams = useSearchParams() - - const errorCode = searchParams.get("errorCode") - const hasAvailabilityError = - errorCode === BookingErrorCodeEnum.AvailabilityError - - const errorMessage = intl.formatMessage({ - defaultMessage: - "Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.", - }) - - useEffect(() => { - if (hasAvailabilityError) { - toast.error(errorMessage) - - const newParams = new URLSearchParams(searchParams.toString()) - newParams.delete("errorCode") - window.history.replaceState({}, "", `${pathname}?${newParams.toString()}`) - } - }, [errorMessage, hasAvailabilityError, pathname, searchParams]) - - return null -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/hotelDescription.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/hotelDescription.module.css deleted file mode 100644 index b8dd42ba5..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/hotelDescription.module.css +++ /dev/null @@ -1,75 +0,0 @@ -.hotelDescription { - overflow: hidden; -} - -.descriptionWrapper { - display: flex; - flex-direction: column; - align-items: center; -} - -.collapsed { - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; -} - -.expanded { - display: block; - max-height: none; -} - -.expandedContent { - display: flex; - flex-direction: column; - align-items: center; - margin-top: var(--Space-x2); -} - -.description { - display: flex; - gap: var(--Space-x025); -} - -.showMoreButton { - display: flex; - align-items: flex-end; - background-color: transparent; - border-width: 0; - padding: 0; - color: var(--Text-Interactive-Secondary); - cursor: pointer; - - &:hover { - color: var(--Text-Interactive-Secondary-Hover); - } -} - -.facilities { - display: flex; - flex-direction: column; - gap: var(--Space-x15); - align-items: center; -} - -.facilityList { - display: flex; - align-items: flex-start; - justify-content: center; - flex-wrap: wrap; - gap: var(--Space-x15); - padding-bottom: var(--Space-x2); -} - -.facilitiesItem { - display: flex; - align-items: center; - gap: var(--Space-x1); -} - -@media screen and (min-width: 1367px) { - .descriptionWrapper { - display: none; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/index.tsx deleted file mode 100644 index 30a3fdcb4..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/HotelDescription/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -"use client" - -import { useState } from "react" -import { Button as ButtonRAC } from "react-aria-components" -import { useIntl } from "react-intl" - -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" -import ReadMore from "@/components/HotelReservation/ReadMore" -import Alert from "@/components/TempDesignSystem/Alert" - -import styles from "./hotelDescription.module.css" - -import type { Hotel } from "@scandic-hotels/trpc/types/hotel" - -import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" - -export default function HotelDescription({ - description, - hotel, - sortedFacilities, -}: { - description?: string - hotel: Hotel - sortedFacilities: Hotel["detailedFacilities"] -}) { - const intl = useIntl() - - const [expanded, setExpanded] = useState(false) - - const handleToggle = () => { - setExpanded((prev) => !prev) - } - - const textShowMore = intl.formatMessage({ - defaultMessage: "Show more", - }) - - const textShowLess = intl.formatMessage({ - defaultMessage: "Show less", - }) - - return ( -
    -
    - {sortedFacilities?.map((facility) => ( -
    - - -

    {facility.name}

    -
    -
    - ))} -
    - -

    - {description} -

    -
    - - - {expanded ? textShowLess : textShowMore} - - - - {expanded && ( -
    - - {hotel.specialAlerts.map((alert) => ( - - ))} -
    - )} -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/hotelInfoCard.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/hotelInfoCard.module.css deleted file mode 100644 index af8af740a..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/hotelInfoCard.module.css +++ /dev/null @@ -1,150 +0,0 @@ -.container { - background-color: var(--Base-Surface-Subtle-Normal); - padding: var(--Space-x3) 0; -} - -.hotelName { - color: var(--Text-Heading); -} - -.hotelAddress { - color: var(--Text-Tertiary); -} -.wrapper { - display: flex; - margin: 0 auto; - max-width: var(--max-width-page); - position: relative; - flex-direction: column; - gap: var(--Space-x2); -} -.hotelDescription { - display: none; -} -.imageWrapper { - position: relative; - height: 200px; - width: 100%; - border-radius: var(--Corner-radius-md); -} - -.hotelContent { - display: flex; - flex-direction: column; - align-items: center; -} - -.hotelInformation { - display: flex; - flex-direction: column; - gap: var(--Space-x1); - align-items: center; - text-align: center; -} - -.hotelAddressDescription { - display: flex; - flex-direction: column; - gap: var(--Space-x2); - align-items: center; - text-align: center; -} - -.hotelAlert { - display: none; -} - -.facilities { - display: none; -} - -@media screen and (min-width: 768px) { - .container { - padding: var(--Space-x4) 0; - } -} - -@media screen and (min-width: 1367px) { - .container { - padding: var(--Space-x4) var(--Space-x5); - } - .hotelDescription { - display: block; - } - - .hotelAlert { - display: block; - max-width: var(--max-width-page); - margin: 0 auto; - padding-top: var(--Space-x15); - } - - .facilities { - display: flex; - flex-direction: column; - padding: var(--Space-x3) 0 var(--Space-x025); - gap: var(--Space-x15); - align-items: center; - } - - .facilityList { - display: flex; - align-items: flex-start; - justify-content: center; - flex-wrap: wrap; - gap: var(--Space-x15); - padding-bottom: var(--Space-x1); - } - - .facilitiesItem { - display: flex; - align-items: center; - gap: var(--Space-x1); - } - - .imageWrapper { - max-width: 360px; - } - - .hotelContent { - gap: var(--Space-x6); - align-items: normal; - } - - .hotelInformation { - padding-right: var(--Space-x3); - width: min(607px, 100%); - align-items: normal; - text-align: left; - } - - .hotelAddressDescription { - align-items: normal; - text-align: left; - } - - .wrapper { - gap: var(--Space-x3); - flex-direction: row; - } - - .facilities { - padding: var(--Space-x3) var(--Space-x3) var(--Space-x05); - } - - .facilityList { - gap: var(--Space-x1); - padding-bottom: var(--Space-x05); - flex-direction: column; - } - .facilityTitle { - display: none; - } - .hotelContent { - flex-direction: row; - align-items: center; - } - .imageWrapper { - align-self: center; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/index.tsx deleted file mode 100644 index a80d1d3b2..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/HotelInfoCard/index.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import TripAdvisorChip from "@scandic-hotels/booking-flow/components/TripAdvisorChip" -import { dt } from "@scandic-hotels/common/dt" -import { getSingleDecimal } from "@scandic-hotels/common/utils/numberFormatting" -import { Divider } from "@scandic-hotels/design-system/Divider" -import ImageGallery from "@scandic-hotels/design-system/ImageGallery" -import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" -import Alert from "@/components/TempDesignSystem/Alert" -import { getIntl } from "@/i18n" -import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" - -import ReadMore from "../../ReadMore" -import { getHotelAlertsForBookingDates } from "../../utils" -import HotelDescription from "./HotelDescription" - -import styles from "./hotelInfoCard.module.css" - -import type { Hotel } from "@scandic-hotels/trpc/types/hotel" - -import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate" -import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" - -export type HotelInfoCardProps = { - booking: SelectRateBooking - hotel: Hotel -} - -export async function HotelInfoCard({ booking, hotel }: HotelInfoCardProps) { - const intl = await getIntl() - - const sortedFacilities = hotel.detailedFacilities - .sort((a, b) => b.sortOrder - a.sortOrder) - .slice(0, 5) - - const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || []) - - const bookingFromDate = dt(booking.fromDate) - const bookingToDate = dt(booking.toDate) - const specialAlerts = getHotelAlertsForBookingDates( - hotel.specialAlerts, - bookingFromDate, - bookingToDate - ) - - return ( -
    -
    -
    - - {hotel.ratings?.tripAdvisor && ( - - )} -
    -
    -
    - -

    {hotel.name}

    -
    -
    - -

    - {intl.formatMessage( - { - defaultMessage: - "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center", - }, - { - address: hotel.address.streetAddress, - city: hotel.address.city, - distanceToCityCenterInKm: getSingleDecimal( - hotel.location.distanceToCentre / 1000 - ), - } - )} -

    -
    - -

    - {hotel.hotelContent.texts.descriptions?.medium} -

    -
    - -
    -
    - -
    -
    - {sortedFacilities?.map((facility) => ( -
    - - -

    {facility.name}

    -
    -
    - ))} -
    - -
    -
    -
    - {specialAlerts.map((alert) => ( - - ))} -
    - ) -} - -function SpecialAlert({ alert }: { alert: Hotel["specialAlerts"][number] }) { - return ( -
    - -
    - ) -} - -export function HotelInfoCardSkeleton() { - return ( -
    -
    -
    - -
    -
    -
    - -
    - - - - -

    - - - -

    -
    -
    -
    - -
    -
    - - - - {[1, 2, 3, 4, 5]?.map((id) => { - return ( -
    - -
    - ) - })} -
    -
    - -
    -
    -
    -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx deleted file mode 100644 index 6ff39693b..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/index.tsx +++ /dev/null @@ -1,349 +0,0 @@ -"use client" -import { cx } from "class-variance-authority" -import { useIntl } from "react-intl" - -import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" -import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats" -import { dt } from "@scandic-hotels/common/dt" -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import { Divider } from "@scandic-hotels/design-system/Divider" -import { IconButton } from "@scandic-hotels/design-system/IconButton" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal" -import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useRateTitles from "@/hooks/booking/useRateTitles" -import useLang from "@/hooks/useLang" - -import { isBookingCodeRate } from "../../utils" -import Room from "../Room" - -import styles from "./summaryContent.module.css" - -import type { Price } from "@/contexts/SelectRate/getTotalPrice" - -export type SelectRateSummaryProps = { - isMember: boolean - bookingCode?: string - toggleSummaryOpen: () => void -} - -export default function SummaryContent({ - isMember, - toggleSummaryOpen, -}: SelectRateSummaryProps) { - const { selectedRates, input } = useSelectRateContext() - - const intl = useIntl() - const lang = useLang() - const rateTitles = useRateTitles() - - const nightsLabel = intl.formatMessage( - { - defaultMessage: "{totalNights, plural, one {# night} other {# nights}}", - }, - { totalNights: input.nights } - ) - - const memberPrice = - selectedRates.rates.length === 1 && - selectedRates.rates[0] && - "member" in selectedRates.rates[0] - ? selectedRates.rates[0].member - : null - - const containsBookingCodeRate = selectedRates.rates.find( - (r) => r && isBookingCodeRate(r) - ) - - if (!selectedRates?.totalPrice) { - return null - } - - const showDiscounted = containsBookingCodeRate || isMember - const totalRegularPrice = selectedRates?.totalPrice?.local?.regularPrice - ? selectedRates.totalPrice.local.regularPrice - : 0 - - const showStrikeThroughPrice = - totalRegularPrice > selectedRates?.totalPrice?.local?.price - - return ( -
    -
    -
    - -

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

    -
    - - - -
    - -

    - {dt(input.data?.booking.fromDate) - .locale(lang) - .format(longDateFormat[lang])} - - {/* eslint-disable formatjs/no-literal-string-in-jsx */} - {dt(input.data?.booking.toDate) - .locale(lang) - .format(longDateFormat[lang])}{" "} - ({nightsLabel}) - {/* eslint-enable formatjs/no-literal-string-in-jsx */} -

    -
    -
    - - - - {selectedRates.rates.map((room, idx) => { - if (!room) { - return null - } - - return ( - - ) - })} - -
    -
    -
    - -

    - {intl.formatMessage( - { - defaultMessage: "Total price (incl VAT)", - }, - { - b: (str) => ( - - {str} - - ), - } - )} -

    -
    - {selectedRates.totalPrice.requested ? ( - -

    - {intl.formatMessage( - { - defaultMessage: "Approx. {value}", - }, - { - value: formatPrice( - intl, - selectedRates.totalPrice.requested.price, - selectedRates.totalPrice.requested.currency, - selectedRates.totalPrice.requested.additionalPrice, - selectedRates.totalPrice.requested - .additionalPriceCurrency - ), - } - )} -

    -
    - ) : null} -
    -
    - - - {formatPrice( - intl, - selectedRates.totalPrice.local.price, - selectedRates.totalPrice.local.currency, - selectedRates.totalPrice.local.additionalPrice, - selectedRates.totalPrice.local.additionalPriceCurrency - )} - - - {showDiscounted && - showStrikeThroughPrice && - selectedRates.totalPrice.local.regularPrice ? ( - - - {formatPrice( - intl, - selectedRates.totalPrice.local.regularPrice, - selectedRates.totalPrice.local.currency - )} - - - ) : null} -
    -
    - - { - if (!room) { - return null - } - - const mapped = mapToRoom({ - isMember, - rate: room, - input, - idx, - getPriceForRoom: selectedRates.getPriceForRoom, - rateTitles, - }) - - function getPrice( - room: NonNullable<(typeof selectedRates.rates)[number]>, - isMember: boolean - ) { - switch (room.type) { - case "regular": - return { - regular: isMember - ? (room.member?.localPrice ?? room.public?.localPrice) - : room.public?.localPrice, - } - case "campaign": - return { - campaign: isMember - ? (room.member ?? room.public) - : room.public, - } - case "redemption": - return { - redemption: room.redemption, - } - case "code": { - if ("corporateCheque" in room) { - return { - corporateCheque: room.corporateCheque, - } - } - - if ("voucher" in room) { - return { - voucher: room.voucher, - } - } - if ("public" in room) { - return { - regular: isMember - ? (room.member?.localPrice ?? room.public?.localPrice) - : room.public?.localPrice, - } - } - } - default: - throw new Error("Unknown price type") - } - } - - const p = getPrice(room!, isMember) - - return { - ...mapped, - idx, - getPriceForRoom: selectedRates.getPriceForRoom, - rateTitles, - price: p, - bedType: undefined, - breakfast: undefined, - breakfastIncluded: - room?.rateDefinition.breakfastIncluded ?? false, - rateDefinition: room.rateDefinition, - } - }) - .filter((x) => !!x)} - fromDate={input.data?.booking.fromDate ?? ""} - toDate={input.data?.booking.toDate ?? ""} - totalPrice={selectedRates.totalPrice} - vat={selectedRates.vat} - /> -
    - {!isMember && memberPrice ? ( - - ) : null} -
    - ) -} - -function mapToRoom({ - isMember, - rate, - input, - idx, - getPriceForRoom, - rateTitles, -}: { - isMember: boolean - rate: NonNullable< - ReturnType["selectedRates"]["rates"][number] - > - input: ReturnType["input"] - idx: number - getPriceForRoom: (roomIndex: number) => Price | null - rateTitles: ReturnType -}) { - return { - adults: input.data?.booking.rooms[idx].adults || 0, - childrenInRoom: input.data?.booking.rooms[idx].childrenInRoom, - roomType: rate.roomInfo.roomType, - roomRate: rate, - cancellationText: rateTitles[rate.rate].title, - roomPrice: { - perNight: { local: { price: -1, currency: CurrencyEnum.SEK } }, - perStay: getPriceForRoom(idx) ?? { - local: { price: -1, currency: CurrencyEnum.Unknown }, - }, - }, - rateDetails: isMember - ? (rate.rateDefinitionMember?.generalTerms ?? - rate.rateDefinition.generalTerms) - : rate.rateDefinition.generalTerms, - packages: rate.roomInfo.selectedPackages, - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css deleted file mode 100644 index bbc85e092..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Content/summaryContent.module.css +++ /dev/null @@ -1,59 +0,0 @@ -.summary { - border-radius: var(--Corner-radius-lg); - display: grid; - gap: var(--Space-x2); - padding: var(--Space-x3); -} - -.headingWrapper { - display: flex; - justify-content: space-between; - align-items: flex-start; -} - -.heading { - color: var(--Text-Default); -} - -.closeButton { - margin-top: -10px; /* Compensate for padding of the button */ - margin-right: -10px; /* Compensate for padding of the button */ -} - -.dates { - display: flex; - align-items: center; - gap: var(--Space-x1); - justify-content: flex-start; - color: var(--Text-Accent-Secondary); -} - -.entry { - display: flex; - gap: var(--Space-x05); - justify-content: space-between; - margin-bottom: var(--Space-x15); -} - -.prices { - justify-items: flex-end; - flex-shrink: 0; - display: grid; -} - -.price { - color: var(--Text-Default); - - &.discounted { - color: var(--Text-Accent-Primary); - } -} - -.strikeThroughRate { - text-decoration: line-through; - color: var(--Text-Secondary); -} - -.approxPrice { - color: var(--Text-Secondary); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx deleted file mode 100644 index 7287f4fc8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/index.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import { cx } from "class-variance-authority" -import { useIntl } from "react-intl" - -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import { Button } from "@scandic-hotels/design-system/Button" -import { Divider } from "@scandic-hotels/design-system/Divider" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" -import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" - -import Modal from "@/components/Modal" - -import { isBookingCodeRate } from "../../utils" -import { getMemberPrice } from "../utils" - -import styles from "./room.module.css" - -import type { Child } from "@scandic-hotels/trpc/types/child" -import type { Packages } from "@scandic-hotels/trpc/types/packages" - -import type { - RoomPrice, - RoomRate, -} from "@/types/components/hotelReservation/enterDetails/details" - -interface RoomProps { - room: { - adults: number - childrenInRoom: Child[] | undefined - roomType: string - roomPrice: RoomPrice - roomRate: RoomRate - rateDetails: string[] | undefined - cancellationText: string - packages?: Packages - } - roomNumber: number - roomCount: number - isMember: boolean -} - -export default function Room({ - room, - roomNumber, - roomCount, - isMember, -}: RoomProps) { - const intl = useIntl() - const adults = room.adults - const childrenInRoom = room.childrenInRoom - - const childrenBeds = childrenInRoom?.reduce( - (acc, value) => { - const bedType = Number(value.bed) - if (bedType === ChildBedMapEnum.IN_ADULTS_BED) { - return acc - } - const count = acc.get(bedType) ?? 0 - acc.set(bedType, count + 1) - return acc - }, - new Map([ - [ChildBedMapEnum.IN_CRIB, 0], - [ChildBedMapEnum.IN_EXTRA_BED, 0], - ]) - ) - - const childBedCrib = childrenBeds?.get(ChildBedMapEnum.IN_CRIB) - const childBedExtraBed = childrenBeds?.get(ChildBedMapEnum.IN_EXTRA_BED) - - const memberPrice = getMemberPrice(room.roomRate) - const showMemberPrice = !!(isMember && memberPrice && roomNumber === 1) - const showDiscounted = isBookingCodeRate(room.roomRate) || showMemberPrice - - const adultsMsg = intl.formatMessage( - { - defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}", - }, - { totalAdults: adults } - ) - - const guestsParts = [adultsMsg] - if (childrenInRoom?.length) { - const childrenMsg = intl.formatMessage( - { - defaultMessage: - "{totalChildren, plural, one {# child} other {# children}}", - }, - { totalChildren: childrenInRoom.length } - ) - guestsParts.push(childrenMsg) - } - - const roomPackages = room.packages - - return ( - <> -
    -
    - {roomCount > 1 ? ( - -

    - {intl.formatMessage( - { - defaultMessage: "Room {roomIndex}", - }, - { - roomIndex: roomNumber, - } - )} -

    -
    - ) : null} -
    -
    - -

    {room.roomType}

    -
    - -
    -

    {guestsParts.join(", ")}

    -

    {room.cancellationText}

    -
    -
    -
    - -
    -

    - {showMemberPrice - ? formatPrice( - intl, - memberPrice.amount, - memberPrice.currency - ) - : formatPrice( - intl, - room.roomPrice.perStay.local.price, - room.roomPrice.perStay.local.currency, - room.roomPrice.perStay.local.additionalPrice, - room.roomPrice.perStay.local.additionalPriceCurrency - )} -

    - {showDiscounted && room.roomPrice.perStay.local.price ? ( - - {formatPrice( - intl, - room.roomPrice.perStay.local.price, - room.roomPrice.perStay.local.currency - )} - - ) : null} -
    -
    -
    - {room.rateDetails?.length ? ( -
    - - {intl.formatMessage({ - defaultMessage: "Rate details", - })} - - - } - title={room.cancellationText} - > -
    - {room.rateDetails.map((info) => ( - -

    - - {info} -

    -
    - ))} -
    -
    -
    - ) : null} -
    - - {childBedCrib ? ( - -
    -
    -

    - {intl.formatMessage( - { - defaultMessage: "Crib (child) × {count}", - }, - { count: childBedCrib } - )} -

    - -

    - {intl.formatMessage({ - defaultMessage: "Subject to availability", - })} -

    -
    -
    -
    - - {formatPrice(intl, 0, room.roomPrice.perStay.local.currency)} - -
    -
    -
    - ) : null} - {childBedExtraBed ? ( - -
    -
    -

    - {intl.formatMessage( - { - defaultMessage: "Extra bed (child) × {count}", - }, - { - count: childBedExtraBed, - } - )} -

    - -

    - {intl.formatMessage({ - defaultMessage: "Subject to availability", - })} -

    -
    -
    -
    - - {formatPrice(intl, 0, room.roomPrice.perStay.local.currency)} - -
    -
    -
    - ) : null} - {roomPackages?.map((pkg) => ( - -
    -

    {pkg.description}

    -
    - - {formatPrice( - intl, - pkg.localPrice.price, - pkg.localPrice.currency - )} - -
    -
    -
    - ))} -
    - - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css deleted file mode 100644 index 085bec231..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/Room/room.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.room { - display: flex; - flex-direction: column; - gap: var(--Space-x15); - overflow-y: auto; - color: var(--Text-Default); -} - -.roomTitle, -.additionalInformation { - color: var(--Text-Secondary); -} - -.terms { - margin-top: var(--Space-x3); - margin-bottom: var(--Space-x3); -} - -.termsText:nth-child(n) { - display: flex; - align-items: center; - margin-bottom: var(--Space-x1); -} - -.terms .termsIcon { - margin-right: var(--Space-x1); -} - -.entry { - display: flex; - gap: var(--Space-x05); - justify-content: space-between; -} - -.prices { - justify-items: flex-end; - flex-shrink: 0; - display: grid; - align-content: start; -} - -.price { - color: var(--Text-Default); - - &.discounted { - color: var(--Text-Accent-Primary); - } -} - -.strikeThroughRate { - text-decoration: line-through; - color: var(--Text-Secondary); -} - -.ctaWrapper { - margin-top: var(--Space-x15); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/index.tsx deleted file mode 100644 index d0ab3845c..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/index.tsx +++ /dev/null @@ -1,154 +0,0 @@ -"use client" -import { cx } from "class-variance-authority" -import { useEffect, useRef, useState } from "react" -import { Button as ButtonRAC } from "react-aria-components" -import { useIntl } from "react-intl" - -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import { Button } from "@scandic-hotels/design-system/Button" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" - -import { isBookingCodeRate } from "../utils" -import SummaryContent from "./Content" - -import styles from "./mobileSummary.module.css" - -export function MobileSummary() { - const intl = useIntl() - const scrollY = useRef(0) - const [isSummaryOpen, setIsSummaryOpen] = useState(false) - const isUserLoggedIn = useIsUserLoggedIn() - - const { selectedRates } = useSelectRateContext() - - function toggleSummaryOpen() { - setIsSummaryOpen(!isSummaryOpen) - } - - useEffect(() => { - if (isSummaryOpen) { - scrollY.current = window.scrollY - document.body.style.position = "fixed" - document.body.style.top = `-${scrollY.current}px` - document.body.style.width = "100%" - } else { - document.body.style.position = "" - document.body.style.top = "" - document.body.style.width = "" - window.scrollTo({ - top: scrollY.current, - left: 0, - behavior: "instant", - }) - } - - return () => { - document.body.style.position = "" - document.body.style.top = "" - document.body.style.width = "" - } - }, [isSummaryOpen]) - - const containsBookingCodeRate = selectedRates.rates.find( - (r) => r && isBookingCodeRate(r) - ) - const showDiscounted = containsBookingCodeRate || isUserLoggedIn - - if (!selectedRates.totalPrice) { - return null - } - - const totalRegularPrice = selectedRates.totalPrice.local?.regularPrice - ? selectedRates.totalPrice.local.regularPrice - : 0 - - const showStrikeThroughPrice = - totalRegularPrice > selectedRates.totalPrice.local?.price - - return ( -
    -
    -
    - -
    -
    -
    - - - - {intl.formatMessage({ - defaultMessage: "Total price", - })} - - - - - {formatPrice( - intl, - selectedRates.totalPrice.local.price, - selectedRates.totalPrice.local.currency, - selectedRates.totalPrice.local.additionalPrice, - selectedRates.totalPrice.local.additionalPriceCurrency - )} - - - {showDiscounted && - showStrikeThroughPrice && - selectedRates.totalPrice.local.regularPrice ? ( - - - {formatPrice( - intl, - selectedRates.totalPrice.local.regularPrice, - selectedRates.totalPrice.local.currency - )} - - - ) : null} - - - - - {intl.formatMessage({ - defaultMessage: "See details", - })} - - - - - - -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapRate.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapRate.ts deleted file mode 100644 index b6db0177b..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapRate.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" - -import type { Packages } from "@scandic-hotels/trpc/types/packages" - -import type { Price } from "@/types/components/hotelReservation/price" -import type { - Rate, - Room, -} from "@/types/components/hotelReservation/selectRate/selectRate" - -export function mapRate( - room: Rate, - index: number, - bookingRooms: Room[], - packages: NonNullable -) { - const rate = { - adults: bookingRooms[index].adults, - cancellationText: room.product.rateDefinition?.cancellationText ?? "", - childrenInRoom: bookingRooms[index].childrenInRoom ?? undefined, - rateDetails: room.product.rateDefinition?.generalTerms, - roomPrice: { - currency: CurrencyEnum.Unknown, - perNight: { - local: { - currency: CurrencyEnum.Unknown, - price: 0, - }, - requested: undefined, - }, - perStay: { - local: { - currency: CurrencyEnum.Unknown, - price: 0, - }, - requested: undefined, - }, - }, - roomRate: room.product, - roomType: room.roomType, - packages, - } - - if ("corporateCheque" in room.product) { - rate.roomPrice.currency = CurrencyEnum.CC - rate.roomPrice.perNight.local = { - currency: CurrencyEnum.CC, - price: room.product.corporateCheque.localPrice.numberOfCheques, - additionalPrice: - room.product.corporateCheque.localPrice.additionalPricePerStay, - additionalPriceCurrency: - room.product.corporateCheque.localPrice.currency ?? - CurrencyEnum.Unknown, - } - rate.roomPrice.perStay.local = { - currency: CurrencyEnum.CC, - price: room.product.corporateCheque.localPrice.numberOfCheques, - additionalPrice: - room.product.corporateCheque.localPrice.additionalPricePerStay, - additionalPriceCurrency: - room.product.corporateCheque.localPrice.currency ?? - CurrencyEnum.Unknown, - } - } else if ("redemption" in room.product) { - rate.roomPrice.currency = CurrencyEnum.POINTS - rate.roomPrice.perNight.local = { - currency: CurrencyEnum.POINTS, - price: room.product.redemption.localPrice.pointsPerNight, - additionalPrice: - room.product.redemption.localPrice.additionalPricePerStay, - additionalPriceCurrency: - room.product.redemption.localPrice.currency ?? CurrencyEnum.Unknown, - } - rate.roomPrice.perStay.local = { - currency: CurrencyEnum.POINTS, - price: room.product.redemption.localPrice.pointsPerStay, - additionalPrice: - room.product.redemption.localPrice.additionalPricePerStay, - additionalPriceCurrency: - room.product.redemption.localPrice.currency ?? CurrencyEnum.Unknown, - } - } else if ("voucher" in room.product) { - rate.roomPrice.currency = CurrencyEnum.Voucher - rate.roomPrice.perNight.local = { - currency: CurrencyEnum.Voucher, - price: room.product.voucher.numberOfVouchers, - } - rate.roomPrice.perStay.local = { - currency: CurrencyEnum.Voucher, - price: room.product.voucher.numberOfVouchers, - } - } else { - const currency = - room.product.public?.localPrice.currency || - room.product.member?.localPrice.currency || - CurrencyEnum.Unknown - rate.roomPrice.currency = currency - rate.roomPrice.perNight.local = { - currency, - price: - room.product.public?.localPrice.pricePerNight || - room.product.member?.localPrice.pricePerNight || - 0, - } - rate.roomPrice.perStay.local = { - currency, - price: - room.product.public?.localPrice.pricePerStay || - room.product.member?.localPrice.pricePerStay || - 0, - } - } - - return rate -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapToPrice.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapToPrice.ts deleted file mode 100644 index 18591f0c4..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mapToPrice.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { - Rate, - Room as SelectRateRoom, -} from "@/types/components/hotelReservation/selectRate/selectRate" -import type { Room } from "@/components/HotelReservation/PriceDetailsModal/PriceDetailsTable" - -export function mapToPrice( - rooms: (Rate | null)[], - bookingRooms: SelectRateRoom[], - isUserLoggedIn: boolean -) { - return rooms - .map((room, idx) => { - if (!room) { - return null - } - - let price = null - if ("corporateCheque" in room.product) { - price = { - corporateCheque: room.product.corporateCheque.localPrice, - } - } else if ("redemption" in room.product) { - price = { - redemption: room.product.redemption.localPrice, - } - } else if ("voucher" in room.product) { - price = { - voucher: room.product.voucher, - } - } else { - const isMainRoom = idx === 0 - const memberRate = room.product.member - const onlyMemberRate = !room.product.public && memberRate - if ((isUserLoggedIn && isMainRoom && memberRate) || onlyMemberRate) { - price = { - regular: { - ...memberRate.localPrice, - regularPricePerStay: - room.product.public?.localPrice.pricePerStay || - memberRate.localPrice.pricePerStay, - }, - } - } else if (room.product.public) { - price = { - regular: room.product.public.localPrice, - } - } - } - - const bookingRoom = bookingRooms[idx] - return { - adults: bookingRoom.adults, - bedType: undefined, - breakfast: undefined, - breakfastIncluded: room.product.rateDefinition.breakfastIncluded, - childrenInRoom: bookingRoom.childrenInRoom, - packages: room.packages, - price, - roomType: room.roomType, - rateDefinition: room.product.rateDefinition, - } - }) - .filter((r) => !!(r && r.price)) as Room[] -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css deleted file mode 100644 index 0daf39bb1..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/mobileSummary.module.css +++ /dev/null @@ -1,108 +0,0 @@ -.wrapper { - position: relative; - display: grid; - grid-template-rows: 0fr auto; - transition: all 0.5s ease-in-out; - border-top: 1px solid var(--Base-Border-Subtle); - background: var(--Base-Surface-Primary-light-Normal); - align-content: end; - z-index: var(--default-modal-z-index); - - &[data-open="true"] { - grid-template-rows: 1fr auto; - - .bottomSheet { - grid-template-columns: 0fr auto; - } - - .priceDetailsButton { - opacity: 0; - height: 0; - } - } - - &[data-open="false"] .priceDetailsButton { - opacity: 1; - height: auto; - } -} - -.signupPromoWrapper { - position: relative; - z-index: var(--default-modal-z-index); -} - -.overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: var(--Overlay-40); - z-index: var(--default-modal-overlay-z-index); -} - -.bottomSheet { - display: grid; - grid-template-columns: 1fr 1fr; - padding: var(--Space-x2) var(--Space-x3) var(--Space-x5); - align-items: flex-start; - transition: all 0.5s ease-in-out; - width: 100vw; -} - -.priceDetailsButton { - border-width: 0; - background-color: transparent; - text-align: start; - cursor: pointer; - padding: 0; - display: grid; - overflow: hidden; - transition: all 0.3s ease-in-out; -} - -.content { - max-height: 50dvh; - overflow-y: auto; -} - -.summaryAccordion { - background-color: var(--Main-Grey-White); - border-color: var(--Primary-Light-On-Surface-Divider-subtle); - border-style: solid; - border-width: 1px; - border-bottom: none; - z-index: 10; -} - -.priceLabel { - color: var(--Text-Default); -} - -.price { - color: var(--Text-Default); - - &.discounted { - color: var(--Text-Accent-Primary); - } -} - -.strikeThroughRate { - text-decoration: line-through; - color: var(--Text-Secondary); -} - -.seeDetails { - margin-top: var(--Space-x15); - display: flex; - gap: var(--Space-x1); - align-items: center; - color: var(--Component-Button-Brand-Secondary-On-fill-Default); -} - -@media screen and (min-width: 768px) { - .bottomSheet { - padding: var(--Space-x2) 0 var(--Space-x7); - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/summary.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/summary.module.css deleted file mode 100644 index 0062f8dc8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/summary.module.css +++ /dev/null @@ -1,106 +0,0 @@ -.summary { - border-radius: var(--Corner-radius-lg); - display: flex; - flex-direction: column; - gap: var(--Spacing-x2); - padding: var(--Spacing-x3); - height: 100%; -} - -.header button { - display: grid; - grid-template-areas: "title button" "date date"; - grid-template-columns: 1fr auto; - align-items: center; - width: 100%; - - background-color: transparent; - border: none; - padding: 0; - margin: 0; -} - -.title { - grid-area: title; -} - -.chevronIcon { - grid-area: button; -} - -.date { - align-items: center; - display: flex; - gap: var(--Spacing-x1); - justify-content: flex-start; - grid-area: date; -} - -.link { - margin-top: var(--Spacing-x1); -} - -.addOns { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-one-and-half); - overflow-y: auto; -} - -.rateDetailsPopover { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-half); - max-width: 360px; -} - -.entry { - display: flex; - gap: var(--Spacing-x-half); - justify-content: space-between; -} - -.entry > :last-child { - justify-items: flex-end; -} - -.total { - display: flex; - flex-direction: column; - gap: var(--Spacing-x2); -} - -.bottomDivider { - display: none; -} - -.modalContent { - width: 560px; -} - -.terms { - margin-top: var(--Spacing-x3); - margin-bottom: var(--Spacing-x3); -} -.termsText:nth-child(n) { - display: flex; - align-items: center; - margin-bottom: var(--Spacing-x1); -} -.terms .termsIcon { - margin-right: var(--Spacing-x1); -} - -@media screen and (min-width: 1367px) { - .bottomDivider { - display: block; - } - - .header { - display: block; - } - - .summary .header .chevronButton { - display: none; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/utils.ts deleted file mode 100644 index 8863fd899..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/MobileSummary/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details" - -export function getMemberPrice(roomRate: RoomRate) { - if ("member" in roomRate && roomRate.member) { - return { - amount: roomRate.member.localPrice.pricePerStay, - currency: roomRate.member.localPrice.currency, - pricePerNight: roomRate.member.localPrice.pricePerNight, - } - } - - return null -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/index.tsx deleted file mode 100644 index 94854269f..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -"use client" - -import { useRouter, useSearchParams } from "next/navigation" -import { useState, useTransition } from "react" - -import { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { DesktopSummary } from "./DesktopSummary" -import { MobileSummary } from "./MobileSummary" - -import styles from "./rateSummary.module.css" - -export function RateSummary() { - return ( - // eslint-disable-next-line formatjs/no-literal-string-in-jsx - Unable to render summary
    }> - - - ) -} - -function InnerRateSummary() { - const { selectedRates, input } = useSelectRateContext() - - const [isSubmitting, setIsSubmitting] = useState(false) - const router = useRouter() - const params = useSearchParams() - const [_, startTransition] = useTransition() - - if (selectedRates.state === "NONE_SELECTED") { - return null - } - - function handleSubmit(e: React.FormEvent) { - e.preventDefault() - setIsSubmitting(true) - startTransition(() => { - router.push(`details?${params}`) - }) - } - - const totalPriceToShow = selectedRates.totalPrice - - if ( - !totalPriceToShow || - !selectedRates.rates.some((room) => room?.isSelected ?? false) - ) { - return null - } - - // attribute data-footer-spacing used to add spacing - // beneath footer to be able to show entire footer upon - // scrolling down to the bottom of the page - return ( - -
    -
    - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - Unable to render desktop summary
    }> - - -
    -
    - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - Unable to render mobile summary
    }> - - - - - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/rateSummary.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/rateSummary.module.css deleted file mode 100644 index 01f617030..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/rateSummary.module.css +++ /dev/null @@ -1,122 +0,0 @@ -@keyframes slideUp { - 0% { - bottom: -100%; - } - - 100% { - bottom: 0%; - } -} - -.summary { - align-items: center; - animation: slideUp 300ms ease forwards; - background-color: var(--Base-Surface-Primary-light-Normal); - border-top: 1px solid var(--Base-Border-Subtle); - bottom: -100%; - left: 0; - position: fixed; - right: 0; - z-index: 99; -} - -.content { - display: none; -} - -.summaryPriceContainer { - display: flex; - flex-direction: row; - gap: var(--Spacing-x4); - padding-top: var(--Spacing-x2); - width: 100%; -} - -.promoContainer { - display: none; - max-width: 264px; -} - -.summaryPrice { - align-self: center; - 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 { - align-self: center; - display: none; -} - -.continueButton { - margin-left: auto; - height: fit-content; - width: 100%; - min-width: 140px; -} - -.summaryPriceTextMobile { - white-space: nowrap; -} - -.mobileSummary { - display: block; -} - -@media (min-width: 1367px) { - .summary { - border-top: 1px solid var(--Base-Border-Subtle); - padding: var(--Spacing-x3) 0 var(--Spacing-x5); - } - - .content { - align-items: center; - display: flex; - flex-direction: row; - justify-content: space-between; - margin: 0 auto; - max-width: var(--max-width-page); - width: 100%; - } - - .petInfo, - .promoContainer, - .summaryPriceTextDesktop { - display: block; - } - - .summaryText { - display: flex; - gap: var(--Spacing-x2); - } - - .summaryPriceTextMobile { - display: none; - } - - .summaryPrice, - .continueButton { - width: auto; - } - - .summaryPriceContainer { - width: auto; - padding: 0; - align-items: center; - } - - .mobileSummary { - display: none; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/utils.ts deleted file mode 100644 index d24d6a7f3..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RateSummary/utils.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" -import { RateTypeEnum } from "@scandic-hotels/trpc/enums/rateType" - -import { sumPackages } from "@/components/HotelReservation/utils" - -import type { Packages } from "@scandic-hotels/trpc/types/packages" -import type { - Product, - RedemptionProduct, -} from "@scandic-hotels/trpc/types/roomAvailability" -import type { IntlShape } from "react-intl" - -import type { Price } from "@/types/components/hotelReservation/price" -import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" - -export function calculateTotalPrice( - selectedRateSummary: Rate[], - isUserLoggedIn: boolean -) { - return selectedRateSummary.reduce( - (total, room, idx) => { - if (!("member" in room.product) || !("public" in room.product)) { - return total - } - - const roomNr = idx + 1 - const isMainRoom = roomNr === 1 - let rate - if (isUserLoggedIn && isMainRoom && room.product.member) { - rate = room.product.member - } else if (room.product.public) { - rate = room.product.public - } - - if (!rate) { - return total - } - - const packagesPrice = room.packages.reduce( - (total, pkg) => { - total.local = total.local + pkg.localPrice.totalPrice - if (pkg.requestedPrice.totalPrice) { - total.requested = total.requested + pkg.requestedPrice.totalPrice - } - return total - }, - { local: 0, requested: 0 } - ) - - total.local.currency = rate.localPrice.currency - total.local.price = - total.local.price + rate.localPrice.pricePerStay + packagesPrice.local - - if (rate.localPrice.regularPricePerStay) { - total.local.regularPrice = - (total.local.regularPrice || 0) + - rate.localPrice.regularPricePerStay + - packagesPrice.local - } - - if (rate.requestedPrice) { - if (!total.requested) { - total.requested = { - currency: rate.requestedPrice.currency, - price: 0, - } - } - - if (!total.requested.currency) { - total.requested.currency = rate.requestedPrice.currency - } - - total.requested.price = - total.requested.price + - rate.requestedPrice.pricePerStay + - packagesPrice.requested - - if (rate.requestedPrice.regularPricePerStay) { - total.requested.regularPrice = - (total.requested.regularPrice || 0) + - rate.requestedPrice.regularPricePerStay + - packagesPrice.requested - } - } - - return total - }, - { - local: { - currency: CurrencyEnum.Unknown, - price: 0, - regularPrice: undefined, - }, - requested: undefined, - } - ) -} - -export function calculateRedemptionTotalPrice( - redemption: RedemptionProduct["redemption"], - packages: Packages | null -) { - const pkgsSum = sumPackages(packages) - let additionalPrice - if (redemption.localPrice.additionalPricePerStay) { - additionalPrice = - redemption.localPrice.additionalPricePerStay + pkgsSum.price - } else if (pkgsSum.price) { - additionalPrice = pkgsSum.price - } - - let additionalPriceCurrency - if (redemption.localPrice.currency) { - additionalPriceCurrency = redemption.localPrice.currency - } else if (pkgsSum.currency) { - additionalPriceCurrency = pkgsSum.currency - } - return { - local: { - additionalPrice, - additionalPriceCurrency, - currency: CurrencyEnum.POINTS, - price: redemption.localPrice.pointsPerStay, - }, - } -} - -export function calculateVoucherPrice(selectedRateSummary: Rate[]) { - return selectedRateSummary.reduce( - (total, room) => { - if (!("voucher" in room.product)) { - return total - } - const rate = room.product.voucher - - total.local.price = total.local.price + rate.numberOfVouchers - - const pkgsSum = sumPackages(room.packages) - if (pkgsSum.price && pkgsSum.currency) { - total.local.additionalPrice = - (total.local.additionalPrice || 0) + pkgsSum.price - total.local.additionalPriceCurrency = pkgsSum.currency - } - - return total - }, - { - local: { - currency: CurrencyEnum.Voucher, - price: 0, - }, - requested: undefined, - } - ) -} - -export function calculateCorporateChequePrice(selectedRateSummary: Rate[]) { - return selectedRateSummary.reduce( - (total, room) => { - if (!("corporateCheque" in room.product)) { - return total - } - const rate = room.product.corporateCheque - const pkgsSum = sumPackages(room.packages) - - total.local.price = total.local.price + rate.localPrice.numberOfCheques - if (rate.localPrice.additionalPricePerStay) { - total.local.additionalPrice = - (total.local.additionalPrice || 0) + - rate.localPrice.additionalPricePerStay + - pkgsSum.price - } else if (pkgsSum.price) { - total.local.additionalPrice = - (total.local.additionalPrice || 0) + pkgsSum.price - } - if (rate.localPrice.currency) { - total.local.additionalPriceCurrency = rate.localPrice.currency - } - - if (rate.requestedPrice) { - if (!total.requested) { - total.requested = { - currency: CurrencyEnum.CC, - price: 0, - } - } - - total.requested.price = - total.requested.price + rate.requestedPrice.numberOfCheques - - if (rate.requestedPrice.additionalPricePerStay) { - total.requested.additionalPrice = - (total.requested.additionalPrice || 0) + - rate.requestedPrice.additionalPricePerStay - } - - if (rate.requestedPrice.currency) { - total.requested.additionalPriceCurrency = rate.requestedPrice.currency - } - } - - return total - }, - { - local: { - currency: CurrencyEnum.CC, - price: 0, - }, - requested: undefined, - } - ) -} - -export function getTotalPrice( - mainRoomProduct: Rate | null, - rateSummary: Array, - isUserLoggedIn: boolean, - intl: IntlShape -): 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) - } - - const { packages, 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, packages) - } - if ("voucher" in product) { - const voucherPrice = calculateVoucherPrice(summaryArray) - voucherPrice.local.currency = intl.formatMessage({ - defaultMessage: "Voucher", - }) as CurrencyEnum - return voucherPrice - } - - return calculateTotalPrice(summaryArray, isUserLoggedIn) -} - -export function isBookingCodeRate(product: Product | undefined | null) { - if (!product) return false - - if ( - "corporateCheque" in product || - "redemption" in product || - "voucher" in product - ) { - return true - } else { - if (product.public) { - return product.public.rateType !== RateTypeEnum.Regular - } - if (product.member) { - return product.member.rateType !== RateTypeEnum.Regular - } - return false - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx deleted file mode 100644 index 3ac188e8e..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx +++ /dev/null @@ -1,195 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" -import { logger } from "@scandic-hotels/common/logger" -import Body from "@scandic-hotels/design-system/Body" -import Caption from "@scandic-hotels/design-system/Caption" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import Image from "@scandic-hotels/design-system/Image" -import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" -import Subtitle from "@scandic-hotels/design-system/Subtitle" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" - -import Chip from "@/components/TempDesignSystem/Chip" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" - -import styles from "./selectedRoomPanel.module.css" - -export function SelectedRoomPanel({ roomIndex }: { roomIndex: number }) { - const intl = useIntl() - - const isMainRoom = roomIndex === 0 - const roomNr = roomIndex + 1 - const { - selectedRates, - actions: { setActiveRoom }, - } = useSelectRateContext() - const selectedRate = selectedRates.forRoom(roomIndex) - const images = selectedRate?.roomInfo?.roomInfo?.images - - const rateTitle = useRateTitle(selectedRate?.rate) - - const selectedProductTitle = useSelectedProductTitle({ roomIndex }) - - if (!selectedRate) { - return null - } - - if (!selectedProductTitle) { - logger.error("Selected product is unknown") - return null - } - - const showModifyButton = - isMainRoom || - (!isMainRoom && selectedRates.rates.slice(0, roomNr).every((room) => room)) - - return ( -
    -
    - - {intl.formatMessage( - { - defaultMessage: "Room {roomIndex}", - }, - { roomIndex: roomNr } - )} - - - {selectedRate.roomInfo.roomType} - - {rateTitle} - {selectedProductTitle} -
    -
    - {images?.[0]?.imageSizes?.tiny ? ( - { - ) : null} - {showModifyButton && ( -
    - -
    - )} -
    -
    - ) -} - -function useSelectedProductTitle({ roomIndex }: { roomIndex: number }) { - const intl = useIntl() - const isUserLoggedIn = useIsUserLoggedIn() - const { - selectedRates, - input: { nights }, - } = useSelectRateContext() - - const selectedRate = selectedRates.forRoom(roomIndex) - - const night = intl.formatMessage({ - defaultMessage: "night", - }) - - const isMainRoom = roomIndex === 0 - - if (!selectedRate) { - return null - } - - const selectedPackagesCurrency = selectedRate.roomInfo.selectedPackages.find( - (pkg) => pkg.localPrice.currency - ) - const selectedPackagesPrice = selectedRate.roomInfo.selectedPackages.reduce( - (total, pkg) => total + pkg.localPrice.totalPrice, - 0 - ) - const selectedPackagesPricePerNight = Math.ceil( - selectedPackagesPrice / nights - ) - - if ( - isUserLoggedIn && - isMainRoom && - "member" in selectedRate && - selectedRate.member - ) { - const { localPrice } = selectedRate.member - return `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}` - } - - if ("public" in selectedRate && selectedRate.public) { - const { localPrice } = selectedRate.public - return `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}` - } - - if ("corporateCheque" in selectedRate) { - const { localPrice } = selectedRate.corporateCheque - const mainProductTitle = `${localPrice.numberOfCheques} ${CurrencyEnum.CC}` - if ( - (localPrice.additionalPricePerStay || selectedPackagesPrice) && - localPrice.currency - ) { - const packagesText = `${localPrice.additionalPricePerStay + selectedPackagesPrice} ${localPrice.currency}` - return `${mainProductTitle} + ${packagesText}` - } - } - - if ("voucher" in selectedRate) { - const mainProductText = `${selectedRate.voucher.numberOfVouchers} ${CurrencyEnum.Voucher}` - if (selectedPackagesPrice && selectedPackagesCurrency) { - const packagesText = `${selectedPackagesPrice} ${selectedPackagesCurrency}` - return `${mainProductText} + ${packagesText}` - } - } -} - -function useRateTitle(rate: RateEnum | undefined) { - const intl = useIntl() - const freeCancelation = intl.formatMessage({ - defaultMessage: "Free cancellation", - }) - const nonRefundable = intl.formatMessage({ - defaultMessage: "Non-refundable", - }) - const freeBooking = intl.formatMessage({ - defaultMessage: "Free rebooking", - }) - const payLater = intl.formatMessage({ - defaultMessage: "Pay later", - }) - const payNow = intl.formatMessage({ - defaultMessage: "Pay now", - }) - - switch (rate) { - case RateEnum.change: - return `${freeBooking}, ${payNow}` - case RateEnum.flex: - return `${freeCancelation}, ${payLater}` - case RateEnum.save: - default: - return `${nonRefundable}, ${payNow}` - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/selectedRoomPanel.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/selectedRoomPanel.module.css deleted file mode 100644 index 2ff7fc9b8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/selectedRoomPanel.module.css +++ /dev/null @@ -1,53 +0,0 @@ -.selectedRoomPanel { - display: grid; - grid-template-areas: "content image"; - grid-template-columns: 1fr 190px; - position: relative; -} - -.content { - grid-area: content; -} - -.imageContainer { - border-radius: var(--Corner-radius-sm); - display: flex; - grid-area: image; -} - -.img { - border-radius: var(--Corner-radius-sm); - height: auto; - max-height: 105px; - object-fit: fill; - width: 100%; -} - -.modifyButtonContainer { - bottom: var(--Spacing-x1); - position: absolute; - right: var(--Spacing-x1); -} - -div.selectedRoomPanel p.subtitle { - padding-bottom: var(--Spacing-x1); -} - -@media screen and (max-width: 767px) { - .selectedRoomPanel { - gap: var(--Spacing-x1); - grid-template-areas: "image" "content"; - grid-template-columns: 1fr; - grid-template-rows: auto auto; - } - - .img { - max-height: 300px; - } -} - -@media screen and (max-width: 500px) { - .img { - max-height: 190px; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/index.tsx deleted file mode 100644 index 3a8dcc446..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { useEffect } from "react" -import { useIntl } from "react-intl" - -import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" -import Subtitle from "@scandic-hotels/design-system/Subtitle" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { SelectedRoomPanel } from "./SelectedRoomPanel" -import { roomSelectionPanelVariants } from "./variants" - -import styles from "./multiRoomWrapper.module.css" - -type Props = { - children: React.ReactNode - isMultiRoom: boolean - roomIndex: number -} -export function MultiRoomWrapper({ children, isMultiRoom, roomIndex }: Props) { - const intl = useIntl() - - const { getTopOffset } = useStickyPosition() - const { - activeRoomIndex, - selectedRates, - actions: { setActiveRoom }, - input: { data }, - } = useSelectRateContext() - const roomNr = roomIndex + 1 - const adultCount = data?.booking.rooms[roomIndex]?.adults || 0 - const childCount = data?.booking.rooms[roomIndex]?.childrenInRoom?.length || 0 - const isActiveRoom = activeRoomIndex === roomIndex - - const roomMsg = intl.formatMessage( - { - defaultMessage: "Room {roomIndex}", - }, - { roomIndex: roomNr } - ) - - const adultsMsg = intl.formatMessage( - { - defaultMessage: "{adults, plural, one {# adult} other {# adults}}", - }, - { adults: adultCount } - ) - - const childrenMsg = intl.formatMessage( - { - defaultMessage: "{children, plural, one {# child} other {# children}}", - }, - { - children: childCount, - } - ) - - const onlyAdultsMsg = adultsMsg - const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ") - const guestsMsg = childCount ? adultsAndChildrenMsg : onlyAdultsMsg - - const title = [roomMsg, guestsMsg].join(", ") - - useEffect(() => { - requestAnimationFrame(() => { - const SCROLL_OFFSET = 12 + getTopOffset() - const roomElements = document.querySelectorAll(`.${styles.roomContainer}`) - - // If no room is active we will show all rooms collapsed, hence we want - // to scroll to the first room. - const selectedRoom = - activeRoomIndex === -1 ? roomElements[0] : roomElements[activeRoomIndex] - - if (selectedRoom) { - const elementPosition = selectedRoom.getBoundingClientRect().top - const offsetPosition = elementPosition + window.scrollY - SCROLL_OFFSET - - // Setting a tiny delay for the scrolling. Without it the browser sometimes doesn't scroll up - // after modifying the first room. - setTimeout(() => { - window.scrollTo({ - top: offsetPosition, - behavior: "smooth", - }) - }, 5) - } - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeRoomIndex]) - - const selectedRate = selectedRates.rateSelectedForRoom(roomIndex) - - if (isMultiRoom) { - const classNames = roomSelectionPanelVariants({ - active: isActiveRoom, - selected: !!selectedRate && !isActiveRoom, - }) - return ( -
    -
    - {selectedRate && !isActiveRoom ? null : ( - {title} - )} - {selectedRate && isActiveRoom ? ( - - ) : null} -
    -
    -
    - -
    -
    {children}
    -
    -
    - ) - } - - return children -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/multiRoomWrapper.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/multiRoomWrapper.module.css deleted file mode 100644 index f8bffab8f..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/multiRoomWrapper.module.css +++ /dev/null @@ -1,55 +0,0 @@ -.roomContainer { - background: var(--Base-Surface-Primary-light-Normal); - border: 1px solid var(--Base-Border-Subtle); - border-radius: var(--Corner-radius-lg); - display: flex; - flex-direction: column; - padding: var(--Spacing-x3); -} - -.header { - align-items: center; - display: flex; - justify-content: space-between; -} - -.roomPanel, -.roomSelectionPanel { - display: grid; - grid-template-rows: 0fr; - opacity: 0; - height: 0; - transition: - opacity 0.3s ease, - grid-template-rows 0.3s ease; - transform-origin: bottom; -} - -.roomPanel > * { - overflow: hidden; -} - -.roomSelectionPanel { - gap: var(--Spacing-x2); -} - -.roomSelectionPanelContainer.active .roomSelectionPanel, -.roomSelectionPanelContainer.selected .roomPanel { - grid-template-rows: 1fr; - height: auto; - opacity: 1; -} - -.roomSelectionPanelContainer.active .roomPanel { - padding-top: var(--Spacing-x1); -} - -.roomSelectionPanelContainer.selected .roomSelectionPanel { - display: none; -} - -@media (max-width: 767px) { - .roomContainer { - padding: var(--Spacing-x2); - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/variants.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/variants.ts deleted file mode 100644 index cc1e6e713..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/MultiRoomWrapper/variants.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { cva } from "class-variance-authority" - -import styles from "./multiRoomWrapper.module.css" - -export const roomSelectionPanelVariants = cva( - styles.roomSelectionPanelContainer, - { - variants: { - active: { - true: styles.active, - }, - selected: { - true: styles.selected, - }, - }, - defaultVariants: { - active: false, - selected: false, - }, - } -) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/alert.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/alert.module.css deleted file mode 100644 index 67ae964a1..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/alert.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.hotelAlert { - margin: 0 auto; - padding: var(--Spacing-x-one-and-half); - width: 100%; -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx deleted file mode 100644 index 5bca5988d..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/NoAvailabilityAlert/index.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { alternativeHotels } from "@scandic-hotels/common/constants/routes/hotelReservation" -import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" -import { AlertTypeEnum } from "@scandic-hotels/trpc/types/alertType" - -import Alert from "@/components/TempDesignSystem/Alert" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useLang from "@/hooks/useLang" - -import styles from "./alert.module.css" - -export default function NoAvailabilityAlert({ - roomIndex, -}: { - roomIndex: number -}) { - const lang = useLang() - const intl = useIntl() - - const { availability, input } = useSelectRateContext() - if (availability.isFetching || !availability.data) { - return null - } - - const indexed = availability.data[roomIndex] - const hasAvailabilityError = "error" in indexed - if (hasAvailabilityError) { - return null - } - - const noAvailableRooms = hasAvailableRoomsForRoom(indexed.roomConfigurations) - - const alertLink = - roomIndex !== -1 && - (input.data?.booking.rooms.at(roomIndex)?.packages ?? []).length === 0 - ? { - title: intl.formatMessage({ - defaultMessage: "See alternative hotels", - }), - url: `${alternativeHotels(lang)}`, - keepSearchParams: true, - } - : null - - if (noAvailableRooms) { - const text = intl.formatMessage({ - defaultMessage: "There are no rooms available that match your request.", - }) - return ( -
    - -
    - ) - } - - const isPublicPromotionWithCode = indexed.roomConfigurations.some((room) => { - const filteredCampaigns = room.campaign.filter(Boolean) - return filteredCampaigns.length - ? filteredCampaigns.every( - (product) => !!product.rateDefinition?.isCampaignRate - ) - : false - }) - - const noAvailableBookingCodeRooms = - !isPublicPromotionWithCode && - indexed.roomConfigurations.every( - (room) => - room.status === AvailabilityEnum.NotAvailable || !room.code.length - ) - - if (input.bookingCode && noAvailableBookingCodeRooms) { - const bookingCodeText = intl.formatMessage( - { - defaultMessage: - "We found no available rooms using this booking code ({bookingCode}). See available rates below.", - }, - { bookingCode: input.bookingCode } - ) - - return ( -
    - -
    - ) - } - - return null -} - -function hasAvailableRoomsForRoom( - roomConfigurations: Extract< - NonNullable< - ReturnType["availability"]["data"] - >[number], - { roomConfigurations: unknown } - >["roomConfigurations"] -) { - return roomConfigurations.every( - (roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx deleted file mode 100644 index 197f24556..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RemoveBookingCodeButton/RemoveBookingCodeButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation" - -import BookingCodeChip from "@/components/BookingCodeChip" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -export function RemoveBookingCodeButton() { - const { - input: { bookingCode }, - } = useSelectRateContext() - const router = useRouter() - const searchParams = useSearchParams() - const pathname = usePathname() - - if (!bookingCode) { - return null - } - - return ( - { - const newSearchParams = new URLSearchParams(searchParams) - newSearchParams.delete("bookingCode") - - const url = `${pathname}?${newSearchParams.toString()}` - - router.replace(url) - }} - /> - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx deleted file mode 100644 index 854051c64..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client" - -import { useIntl } from "react-intl" - -import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import styles from "./petRoom.module.css" - -export default function PetRoomMessage({ - priceData, -}: { - priceData?: { price: number; currency: string } -}) { - const intl = useIntl() - - if (!priceData) { - return null - } - - return ( - -

    - {intl.formatMessage( - { - defaultMessage: - "Pet-friendly rooms include a charge of approx. {price}/stay", - }, - { - b: (str) => ( - - {str} - - ), - price: formatPrice(intl, priceData.price, priceData.currency), - } - )} -

    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/petRoom.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/petRoom.module.css deleted file mode 100644 index e54847db8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/PetRoomMessage/petRoom.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.additionalInformation { - color: var(--Text-Tertiary); - padding: var(--Space-x1) var(--Space-x15); -} - -.additionalInformationPrice { - color: var(--Text-Default); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/checkbox.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/checkbox.module.css deleted file mode 100644 index 311194ab6..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/checkbox.module.css +++ /dev/null @@ -1,76 +0,0 @@ -.checkboxGroup { - display: grid; - gap: var(--Space-x15); -} - -.checkboxWrapper { - display: grid; - gap: var(--Space-x05); -} - -.checkboxField { - display: grid; - grid-template-columns: auto 1fr auto; - align-items: center; - gap: var(--Space-x15); - padding: var(--Space-x1) var(--Space-x15); - cursor: pointer; - border-radius: var(--Corner-radius-md); - transition: background-color 0.3s; - color: var(--Text-Default); - - &[data-disabled] { - cursor: unset; - - .checkbox { - border-color: var(--Border-Interactive-Disabled); - background-color: var(--Surface-UI-Fill-Disabled); - } - - .text { - color: var(--Base-Text-Disabled); - } - } - - &:hover:not([data-disabled]) { - background-color: var(--UI-Input-Controls-Surface-Hover); - } - - &[data-focus-visible] .checkbox { - /* Used this value as it makes sense from a token name perspective and has a good contrast, but we need to decide for a default ui state */ - outline: 2px solid var(--Border-Interactive-Focus); - outline-offset: 2px; - } - - &[data-selected] .checkbox { - border-color: var(--Surface-UI-Fill-Active); - background-color: var(--Surface-UI-Fill-Active); - } -} - -.checkbox { - width: 24px; - height: 24px; - min-width: 24px; - border: 1px solid var(--Border-Interactive-Default); - border-radius: var(--Corner-radius-sm); - transition: all 0.3s; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--Surface-UI-Fill-Default); -} - -.text { - color: var(--Text-Default); -} - -@media screen and (max-width: 767px) { - .checkboxField:hover:not([data-disabled]) { - background-color: transparent; - } - - .checkboxField[data-selected] { - background-color: transparent; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx deleted file mode 100644 index 98a88e5c5..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/Checkboxes/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client" -import { Checkbox, CheckboxGroup } from "react-aria-components" -import { Controller, useFormContext } from "react-hook-form" - -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import { usePackageLabels } from "../../usePackageLabels" -import { getIconNameByPackageCode } from "../../utils" - -import styles from "./checkbox.module.css" - -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" -import type { ReactNode } from "react" - -import type { FormValues } from "../formValues" - -export function PackageCheckboxes({ - availablePackages, -}: { - availablePackages: { - code: RoomPackageCodeEnum - message?: ReactNode - }[] -}) { - const { control } = useFormContext() - const packageLabels = usePackageLabels() - - return ( - { - const allergyRoomSelected = includesAllergyRoom(field.value) - const petRoomSelected = includesPetRoom(field.value) - return ( - - {availablePackages?.map((option) => { - const isAllergyRoom = checkIsAllergyRoom(option.code) - const isPetRoom = checkIsPetRoom(option.code) - const isDisabled = - (isPetRoom && allergyRoomSelected) || - (isAllergyRoom && petRoomSelected) - - const isSelected = field.value.includes(option.code) - const iconName = getIconNameByPackageCode(option.code) - - return ( -
    - - - {isSelected ? ( - - ) : null} - - - {packageLabels[option.code]} - - {iconName ? ( - - ) : null} - - {option.message} -
    - ) - })} -
    - ) - }} - /> - ) -} - -export function includesAllergyRoom(codes: PackageEnum[]) { - return codes.includes(RoomPackageCodeEnum.ALLERGY_ROOM) -} - -export function includesPetRoom(codes: PackageEnum[]) { - return codes.includes(RoomPackageCodeEnum.PET_ROOM) -} - -export function checkIsAllergyRoom( - code: PackageEnum -): code is RoomPackageCodeEnum.ALLERGY_ROOM { - return code === RoomPackageCodeEnum.ALLERGY_ROOM -} - -export function checkIsPetRoom( - code: PackageEnum -): code is RoomPackageCodeEnum.PET_ROOM { - return code === RoomPackageCodeEnum.PET_ROOM -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/form.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/form.module.css deleted file mode 100644 index 65cb9a4ca..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/form.module.css +++ /dev/null @@ -1,30 +0,0 @@ -.footer { - padding: 0 var(--Space-x15); -} - -.buttonContainer { - display: flex; - flex-direction: column; - gap: var(--Space-x1); -} - -.divider { - margin: var(--Space-x15) 0; -} - -@media screen and (max-width: 767px) { - .divider { - display: none; - } - .footer { - margin-top: var(--Space-x5); - } -} - -@media screen and (min-width: 768px) { - .buttonContainer { - flex-direction: row-reverse; - justify-content: space-between; - align-items: center; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/formValues.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/formValues.ts deleted file mode 100644 index 6661b636f..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/formValues.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" - -export type FormValues = { - selectedPackages: PackageEnum[] -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx deleted file mode 100644 index 14261ff95..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client" -import { FormProvider, useForm } from "react-hook-form" -import { useIntl } from "react-intl" - -import { Button } from "@scandic-hotels/design-system/Button" -import { Divider } from "@scandic-hotels/design-system/Divider" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { PackageCheckboxes } from "./Checkboxes" - -import styles from "./form.module.css" - -import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" -import type { ReactNode } from "react" - -import type { FormValues } from "./formValues" - -export function RoomPackagesForm({ - close, - selectedPackages, - onSelectPackages, - availablePackages, -}: { - close: () => void - availablePackages: { - code: RoomPackageCodeEnum - message: ReactNode - }[] - selectedPackages: PackageEnum[] - onSelectPackages: (packages: PackageEnum[]) => void -}) { - const intl = useIntl() - - const methods = useForm({ - values: { - selectedPackages: selectedPackages, - }, - }) - - function clearSelectedPackages() { - onSelectPackages([]) - close() - } - - function onSubmit(data: FormValues) { - onSelectPackages(data.selectedPackages) - close() - } - - return ( - -
    - -
    - -
    - - - - - - -
    -
    - -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx deleted file mode 100644 index 1ef0dc166..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Modal.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { type ReactNode, useState } from "react" -import { - Dialog, - DialogTrigger, - Modal, - ModalOverlay, -} from "react-aria-components" -import { useIntl } from "react-intl" - -import { ChipButton } from "@scandic-hotels/design-system/ChipButton" -import { IconButton } from "@scandic-hotels/design-system/IconButton" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { RoomPackagesForm } from "./Form" - -import styles from "./roomPackageFilter.module.css" - -import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" - -export function RoomPackageFilterModal({ - selectedPackages, - onSelectPackages, - availablePackages, -}: { - onSelectPackages: (packages: PackageEnum[]) => void - selectedPackages: PackageEnum[] - availablePackages: { - code: RoomPackageCodeEnum - message: ReactNode - }[] -}) { - const intl = useIntl() - const [isOpen, setIsOpen] = useState(false) - - return ( - - - {intl.formatMessage({ defaultMessage: "Special needs" })} - - - - - - -
    - -

    - {intl.formatMessage({ defaultMessage: "Special needs" })} -

    -
    - setIsOpen(false)} - > - - -
    - setIsOpen(false)} - availablePackages={availablePackages} - selectedPackages={selectedPackages} - onSelectPackages={onSelectPackages} - /> -
    -
    -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx deleted file mode 100644 index 70329557a..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Popover.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { type ReactNode, useState } from "react" -import { Dialog, DialogTrigger, Popover } from "react-aria-components" -import { useIntl } from "react-intl" - -import { ChipButton } from "@scandic-hotels/design-system/ChipButton" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" - -import { RoomPackagesForm } from "./Form" - -import styles from "./roomPackageFilter.module.css" - -import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" - -export function RoomPackageFilterPopover({ - selectedPackages, - onSelectPackages, - availablePackages, -}: { - onSelectPackages: (packages: PackageEnum[]) => void - selectedPackages: PackageEnum[] - availablePackages: { - code: RoomPackageCodeEnum - message: ReactNode - }[] -}) { - const intl = useIntl() - const [isOpen, setIsOpen] = useState(false) - - return ( - - - {intl.formatMessage({ defaultMessage: "Special needs" })} - - - - - - setIsOpen(false)} - availablePackages={availablePackages} - selectedPackages={selectedPackages} - onSelectPackages={onSelectPackages} - /> - - - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx deleted file mode 100644 index e1cbeaec1..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client" -import { Button as ButtonRAC } from "react-aria-components" - -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { useBreakpoint } from "@/hooks/useBreakpoint" - -import PetRoomMessage from "./Form/Checkboxes/PetRoomMessage" -import { RoomPackageFilterModal } from "./Modal" -import { RoomPackageFilterPopover } from "./Popover" -import { usePackageLabels } from "./usePackageLabels" -import { getIconNameByPackageCode } from "./utils" - -import styles from "./roomPackageFilter.module.css" - -import type { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" -import type { ReactNode } from "react" - -export function RoomPackageFilter({ roomIndex }: { roomIndex: number }) { - const displayAsModal = useBreakpoint("mobile") - - const { - getPackagesForRoom, - actions: { selectPackages }, - } = useSelectRateContext() - - const { selectedPackages, availablePackages } = getPackagesForRoom(roomIndex) - - function deletePackage(code: PackageEnum) { - selectPackages({ - roomIndex, - packages: selectedPackages - .filter((pkg) => pkg.code !== code) - .map((pkg) => pkg.code), - }) - } - - const petRoomPackage = availablePackages.find( - (x) => x.code === RoomPackageCodeEnum.PET_ROOM - ) - const packageLabels = usePackageLabels() - const packageMessages = packageMessageMap({ - petRoomPrice: - petRoomPackage && !("type" in petRoomPackage) - ? petRoomPackage.localPrice - : undefined, - }) - - const packages = availablePackages - .map((x) => { - if (!isRoomPackage(x)) { - return undefined - } - - return { - code: x.code, - message: packageMessages[x.code], - } - }) - .filter((x) => { - return !!x - }) - - return ( -
    -
    - {selectedPackages.map((pkg) => ( - - - - {packageLabels[pkg.code] ?? pkg.description} - deletePackage(pkg.code)} - className={styles.removeButton} - > - - - - - ))} -
    - {displayAsModal ? ( -
    - pkg.code)} - onSelectPackages={(packages) => { - selectPackages({ roomIndex, packages }) - }} - /> -
    - ) : ( -
    - pkg.code)} - onSelectPackages={(packages) => { - selectPackages({ roomIndex, packages }) - }} - /> -
    - )} -
    - ) -} - -function isRoomPackage(x: { - code: BreakfastPackageEnum | RoomPackageCodeEnum -}): x is { code: RoomPackageCodeEnum } { - return Object.values(RoomPackageCodeEnum).includes( - x.code as RoomPackageCodeEnum - ) -} - -const packageMessageMap = ({ - petRoomPrice, -}: { - petRoomPrice?: { price: number; currency: string } -}): Record => ({ - [RoomPackageCodeEnum.PET_ROOM]: , - [RoomPackageCodeEnum.ACCESSIBILITY_ROOM]: undefined, - [RoomPackageCodeEnum.ALLERGY_ROOM]: undefined, -}) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/roomPackageFilter.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/roomPackageFilter.module.css deleted file mode 100644 index ccd4c597c..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/roomPackageFilter.module.css +++ /dev/null @@ -1,142 +0,0 @@ -.roomPackageFilter { - display: flex; - gap: var(--Space-x1); - flex-direction: column-reverse; - align-items: flex-start; -} - -.selectedPackages { - display: flex; - gap: var(--Space-x1); - flex-wrap: wrap; -} - -.modalOverlay { - position: fixed; - inset: 0; - background-color: var(--Overlay-40); - - &[data-entering] { - animation: overlay-fade 200ms; - } - - &[data-exiting] { - animation: overlay-fade 150ms reverse ease-in; - } -} - -.modal { - position: fixed; - bottom: 0; - left: 0; - right: 0; - padding: var(--Space-x2) var(--Space-x05); - border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0; - background-color: var(--Surface-Primary-Default); - box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); - - &[data-entering] { - animation: modal-anim 200ms; - } - - &[data-exiting] { - animation: modal-anim 150ms reverse ease-in; - } -} - -.modalDialog { - display: grid; - gap: var(--Space-x2); -} - -.dialog { - display: grid; - gap: var(--Space-x2); - max-width: 340px; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 var(--Space-x15); -} - -.footer { - display: grid; - gap: var(--Space-x1); - padding: 0 var(--Space-x15); -} - -.selectedPackage { - display: flex; - justify-content: center; - align-items: center; - padding: var(--Space-x1); - gap: var(--Space-x05); - border-radius: var(--Corner-radius-sm); - background-color: var(--Surface-Secondary-Default-dark); - color: var(--Text-Interactive-Default); -} - -.removeButton { - background-color: transparent; - border-width: 0; - cursor: pointer; - padding: var(--Space-x05); - margin: calc(-1 * var(--Space-x05)); -} - -@media screen and (max-width: 767px) { - .popover { - display: none; - } -} - -@media screen and (min-width: 768px) { - .roomPackageFilter { - flex-direction: row; - align-items: stretch; - } - - .modalOverlay { - display: none; - } - - .popover { - padding: var(--Space-x2); - border-radius: var(--Corner-radius-md); - background-color: var(--Surface-Primary-Default); - box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); - max-width: 340px; - overflow-y: auto; - } - - .checkboxContainer { - padding: 0 var(--Space-x1); - } - - .header { - display: none; - } -} - -@keyframes overlay-fade { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes modal-anim { - from { - transform: translateY(100%); - } - - to { - transform: translateY(0); - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/utils.ts deleted file mode 100644 index b4d8c64fd..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import type { MaterialSymbolProps } from "@scandic-hotels/design-system/Icons/MaterialIcon/MaterialSymbol" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" - -export function getIconNameByPackageCode( - packageCode: PackageEnum -): MaterialSymbolProps["icon"] { - switch (packageCode) { - case RoomPackageCodeEnum.PET_ROOM: - return "pets" - case RoomPackageCodeEnum.ACCESSIBILITY_ROOM: - return "accessible" - case RoomPackageCodeEnum.ALLERGY_ROOM: - return "mode_fan" - default: - return "star" - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/index.tsx deleted file mode 100644 index 18de31947..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" -import { Typography } from "@scandic-hotels/design-system/Typography" -import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" - -import { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { RemoveBookingCodeButton } from "./RemoveBookingCodeButton/RemoveBookingCodeButton" -import { RoomPackageFilter } from "./RoomPackageFilter" - -import styles from "./roomsHeader.module.css" - -export function RoomsHeader({ roomIndex }: { roomIndex: number }) { - return ( - // eslint-disable-next-line formatjs/no-literal-string-in-jsx - Unable to render rooms header}> - - - ) -} - -function InnerRoomsHeader({ roomIndex }: { roomIndex: number }) { - return ( -
    - -
    - - - {/* */} -
    -
    - ) -} - -function AvailableRoomCount({ roomIndex }: { roomIndex: number }) { - const intl = useIntl() - const { isFetching, getAvailabilityForRoom } = useSelectRateContext() - - const roomAvailability = getAvailabilityForRoom(roomIndex) ?? [] - - const availableRooms = roomAvailability.filter( - (x) => x.status === AvailabilityEnum.Available - ).length - - const totalRooms = roomAvailability.length - - const notAllRoomsAvailableText = intl.formatMessage( - { - defaultMessage: - "{availableRooms}/{numberOfRooms, plural, one {# room type} other {# room types}} available", - }, - { - availableRooms, - numberOfRooms: totalRooms, - } - ) - - const allRoomsAvailableText = intl.formatMessage( - { - defaultMessage: - "{numberOfRooms, plural, one {# room type} other {# room types}} available", - }, - { - numberOfRooms: totalRooms, - } - ) - - if (isFetching) { - return - } - - return ( - -

    - {availableRooms !== totalRooms - ? notAllRoomsAvailableText - : allRoomsAvailableText} -

    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css deleted file mode 100644 index 07b5efc61..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css +++ /dev/null @@ -1,21 +0,0 @@ -.container { - display: grid; - gap: var(--Space-x3); - align-items: center; -} - -.availableRooms { - color: var(--Text-Default); -} - -.filters { - display: flex; - gap: var(--Space-x1); - align-items: flex-start; -} - -@media screen and (min-width: 768px) { - .container { - grid-template-columns: 1fr auto; - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/RoomSize.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/RoomSize.tsx deleted file mode 100644 index 351469f11..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/RoomSize.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { Typography } from "@scandic-hotels/design-system/Typography" - -import type { RoomSizeProps } from "@/types/components/hotelReservation/selectRate/roomListItem" - -export default function RoomSize({ roomSize }: RoomSizeProps) { - const intl = useIntl() - - if (!roomSize) { - return null - } - - if (roomSize.min === roomSize.max) { - return ( - <> - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} -

    -
    - -

    - {intl.formatMessage( - { - defaultMessage: "{roomSize} m²", - }, - { roomSize: roomSize.min } - )} -

    -
    - - ) - } - return ( - <> - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} -

    -
    - -

    - {intl.formatMessage( - { - defaultMessage: "{roomSizeMin} - {roomSizeMax} m²", - }, - { - roomSizeMin: roomSize.min, - roomSizeMax: roomSize.max, - } - )} -

    -
    - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/ToggleSidePeek.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/ToggleSidePeek.tsx deleted file mode 100644 index c72977480..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/ToggleSidePeek.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client" - -import { useIntl } from "react-intl" - -import { Button } from "@scandic-hotels/design-system/Button" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" - -import useSidePeekStore from "@/stores/sidepeek" - -import styles from "./details.module.css" - -import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" -import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps" - -export default function ToggleSidePeek({ - hotelId, - roomTypeCode, -}: ToggleSidePeekProps) { - const intl = useIntl() - const openSidePeek = useSidePeekStore((state) => state.openSidePeek) - - return ( - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/details.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/details.module.css deleted file mode 100644 index 93429b6d0..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/details.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.specification { - align-items: center; - display: flex; - justify-content: center; - gap: var(--Space-x1); -} - -.roomDetails { - display: flex; - flex-direction: column; - text-align: center; - gap: var(--Space-x1); - padding-bottom: var(--Space-x05); -} - -.sidePeekButton { - width: 100%; -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx deleted file mode 100644 index ecccd4ca8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Details/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { Typography } from "@scandic-hotels/design-system/Typography" - -import RoomSize from "./RoomSize" - -import styles from "./details.module.css" - -import type { RoomInfo } from "@/contexts/SelectRate/types" - -type Props = { - roomInfo: RoomInfo -} - -export default function Details({ roomInfo }: Props) { - const intl = useIntl() - - const { name, occupancy, roomSize } = roomInfo || {} - - return ( - <> -
    - {occupancy && ( - -

    - {occupancy.max === occupancy.min - ? intl.formatMessage( - { - defaultMessage: - "{guests, plural, one {# guest} other {# guests}}", - }, - { guests: occupancy.max } - ) - : intl.formatMessage( - { - defaultMessage: "{min}-{max} guests", - }, - { - min: occupancy.min, - max: occupancy.max, - } - )} -

    -
    - )} - -
    -
    - -

    {name}

    -
    -
    - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/breakfastMessage.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/breakfastMessage.module.css deleted file mode 100644 index 54b465b81..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/breakfastMessage.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.message { - display: flex; - align-items: center; - text-align: center; - gap: var(--Space-x1); -} - -.breakfastMessage { - flex: 0 1 auto; -} - -.divider { - flex: 1; - min-width: 5%; -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/getBreakfastMessage.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/getBreakfastMessage.ts deleted file mode 100644 index 7d085a619..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/getBreakfastMessage.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HotelTypeEnum } from "@scandic-hotels/trpc/enums/hotelType" - -export function getBreakfastMessage( - publicBreakfastIncluded: boolean, - memberBreakfastIncluded: boolean, - hotelType: string | undefined, - userIsLoggedIn: boolean, - msgs: Record< - "included" | "noSelection" | "scandicgo" | "notIncluded", - string - >, - roomNr: number -) { - if (hotelType === HotelTypeEnum.ScandicGo) { - return msgs.scandicgo - } - - if (userIsLoggedIn && memberBreakfastIncluded && roomNr === 1) { - return msgs.included - } - - if (publicBreakfastIncluded && memberBreakfastIncluded) { - return msgs.included - } - - if (!publicBreakfastIncluded && !memberBreakfastIncluded) { - return msgs.notIncluded - } - - return msgs.notIncluded -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx deleted file mode 100644 index db027d0d8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/BreakfastMessage/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client" -import { useSession } from "next-auth/react" -import { useIntl } from "react-intl" - -import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import { Divider } from "@scandic-hotels/design-system/Divider" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import { isValidClientSession } from "@/utils/clientSession" - -import { getBreakfastMessage } from "./getBreakfastMessage" - -import styles from "./breakfastMessage.module.css" - -export function BreakfastMessage({ - breakfastIncludedMember, - breakfastIncludedStandard, - hasRegularRates, - roomIndex, -}: { - breakfastIncludedMember: boolean - breakfastIncludedStandard: boolean - hasRegularRates: boolean - roomIndex: number -}) { - const intl = useIntl() - const { hotel } = useSelectRateContext() - const roomNr = roomIndex + 1 - - // TODO: Replace with context value when we have support for dropdown "Show all rates" - const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum - const hotelType = hotel.data?.hotel.hotelType - - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) - - const breakfastMessages = { - included: intl.formatMessage({ - defaultMessage: "Breakfast is included.", - }), - notIncluded: intl.formatMessage({ - defaultMessage: "Breakfast excluded, add in next step.", - }), - noSelection: intl.formatMessage({ - defaultMessage: "Select a rate", - }), - scandicgo: intl.formatMessage({ - defaultMessage: "Breakfast deal can be purchased at the hotel.", - }), - } - - const breakfastMessage = getBreakfastMessage( - breakfastIncludedStandard, - breakfastIncludedMember, - hotelType, - isUserLoggedIn, - breakfastMessages, - roomNr - ) - - const isDiscount = selectedFilter === BookingCodeFilterEnum.Discounted - - if (isDiscount || !hasRegularRates) { - return null - } - - return ( -
    - - -

    {breakfastMessage}

    -
    - -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx deleted file mode 100644 index 3eac9f406..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Campaign.tsx +++ /dev/null @@ -1,284 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import CampaignRateCard from "@scandic-hotels/design-system/CampaignRateCard" -import NoRateAvailableCard from "@scandic-hotels/design-system/NoRateAvailableCard" - -import { - sumPackages, - sumPackagesRequestedPrice, -} from "@/components/HotelReservation/utils" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useRateTitles from "@/hooks/booking/useRateTitles" -import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" - -import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" - -import type { - AvailabilityWithRoomInfo, - Package, -} from "@/contexts/SelectRate/types" - -type CampaignProps = { - nights: number - campaign: AvailabilityWithRoomInfo["campaign"] - roomIndex: number - roomTypeCode: string - selectedPackages: Package[] -} - -export default function Campaign({ - campaign, - roomIndex, - nights, - roomTypeCode, - selectedPackages, -}: CampaignProps) { - // TODO: Replace with context value when we have support for dropdown "Show all rates" - const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum - - const isCampaignRate = campaign.some( - (c) => - c.rateDefinition.isCampaignRate || c.rateDefinitionMember?.isCampaignRate - ) - - if (selectedFilter === BookingCodeFilterEnum.Discounted && !isCampaignRate) { - return null - } - - if (selectedFilter === BookingCodeFilterEnum.Discounted) { - campaign = campaign.filter((product) => product.bookingCode) - } - - return campaign.map((product, ix) => { - return ( - - ) - }) -} - -function Inner({ - product, - roomIndex, - roomTypeCode, - selectedPackages, - nights, -}: { - roomIndex: number - nights: number - roomTypeCode: string - product: AvailabilityWithRoomInfo["campaign"][number] - selectedPackages: Package[] -}) { - const roomNr = roomIndex + 1 - const { - isRateSelected, - actions: { selectRate }, - } = useSelectRateContext() - - const rateTitles = useRateTitles() - const isUserLoggedIn = useIsUserLoggedIn() - const intl = useIntl() - const night = intl - .formatMessage({ - defaultMessage: "night", - }) - .toUpperCase() - - const standardPriceMsg = intl.formatMessage({ - defaultMessage: "Standard price", - }) - - const memberPriceMsg = intl.formatMessage({ - defaultMessage: "Member price", - }) - - if (!product.public) { - return ( - - ) - } - - const rateTermDetails = product.rateDefinitionMember - ? [ - { - title: product.bookingCode - ? product.rateDefinition.title - : standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - { - title: product.bookingCode - ? product.rateDefinitionMember.title - : memberPriceMsg, - terms: product.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: product.bookingCode - ? product.rateDefinition.title - : standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - ] - const isSelected = isRateSelected({ - roomIndex, - rate: { ...product, type: "campaign" }, - roomTypeCode, - }) - - let bannerText = intl.formatMessage({ - defaultMessage: "Campaign", - }) - if (product.bookingCode) { - bannerText = product.bookingCode - } - - if (product.rateDefinition.breakfastIncluded) { - bannerText = `${bannerText} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast included", - })}` - } else { - bannerText = `${bannerText} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast excluded", - })}` - } - - const pkgsSum = sumPackages(selectedPackages) - const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) - - const pricePerNight = calculatePricePerNightPriceProduct( - product.public.localPrice.pricePerNight, - product.public.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - - const pricePerNightMember = product.member - ? calculatePricePerNightPriceProduct( - product.member.localPrice.pricePerNight, - product.member.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - - const isMainRoom = roomIndex === 0 - const isMainRoomAndLoggedIn = isMainRoom && isUserLoggedIn - - let approximateRatePrice = undefined - if (isMainRoomAndLoggedIn && pricePerNightMember) { - approximateRatePrice = pricePerNightMember.totalRequestedPrice - } else if ( - pricePerNight.totalRequestedPrice && - pricePerNightMember?.totalRequestedPrice - ) { - approximateRatePrice = `${pricePerNight.totalRequestedPrice}/${pricePerNightMember.totalRequestedPrice}` - } else if (pricePerNight.totalRequestedPrice) { - approximateRatePrice = pricePerNight.totalRequestedPrice - } - - const approximateRate = - approximateRatePrice && product.public.requestedPrice - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: approximateRatePrice, - unit: product.public.requestedPrice.currency, - } - : undefined - - const rateCode = isMainRoomAndLoggedIn - ? product.member!.rateCode - : product.public!.rateCode - - const counterRateCode = isMainRoomAndLoggedIn - ? product.public?.rateCode - : product.member?.rateCode - - const campaignMemberLabel = - product.rateDefinitionMember?.title || memberPriceMsg - - return ( - - selectRate({ - roomIndex, - rateCode: rateCode, - counterRateCode: counterRateCode, - roomTypeCode, - bookingCode: product.bookingCode, - }) - } - isSelected={isSelected} - isHighlightedRate={ - !!product.rateDefinition?.displayPriceRed || isMainRoomAndLoggedIn - } - memberRate={ - pricePerNightMember && !isMainRoomAndLoggedIn - ? { - label: memberPriceMsg, - price: pricePerNightMember.totalPrice, - unit: `${product.member!.localPrice.currency}/${night}`, - } - : undefined - } - comparisonRate={ - isMainRoomAndLoggedIn - ? { - price: pricePerNight.totalPrice, - unit: product.public.localPrice.currency, - } - : undefined - } - name={`rateCode-${roomNr}-${product.public.rateCode}`} - paymentTerm={rateTitles[product.rate].paymentTerm} - rate={{ - label: isMainRoomAndLoggedIn ? campaignMemberLabel : standardPriceMsg, - price: - isMainRoomAndLoggedIn && pricePerNightMember - ? pricePerNightMember.totalPrice - : pricePerNight.totalPrice, - - unit: `${product.public.localPrice.currency}/${night}`, - }} - rateTitle={rateTitles[product.rate].title} - omnibusRate={ - product.public.localPrice.omnibusPricePerNight - ? { - label: intl - .formatMessage({ - defaultMessage: "Lowest price (last 30 days)", - }) - .toUpperCase(), - price: product.public.localPrice.omnibusPricePerNight.toString(), - unit: product.public.localPrice.currency, - } - : undefined - } - rateTermDetails={rateTermDetails} - value={product.public.rateCode} - /> - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx deleted file mode 100644 index bb3a19b00..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx +++ /dev/null @@ -1,390 +0,0 @@ -"use client" - -import { useIntl } from "react-intl" - -import CodeRateCard from "@scandic-hotels/design-system/CodeRateCard" - -import { - sumPackages, - sumPackagesRequestedPrice, -} from "@/components/HotelReservation/utils" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useRateTitles from "@/hooks/booking/useRateTitles" - -import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" - -import type { CodeProduct } from "@scandic-hotels/trpc/types/roomAvailability" - -import type { Package } from "@/contexts/SelectRate/types" - -type CodeProps = { - nights: number - roomTypeCode: string - code: CodeProduct[] - roomIndex: number - selectedPackages: Package[] -} - -export default function Code({ - code, - nights, - roomTypeCode, - roomIndex, - selectedPackages, -}: CodeProps) { - return code.map((product) => { - return ( - - ) - }) -} - -function InnerCode({ - codeProduct, - roomIndex, - roomTypeCode, - nights, - selectedPackages, -}: { - codeProduct: CodeProduct - roomIndex: number - roomTypeCode: string - nights: number - selectedPackages: Package[] -}) { - const { - input: { bookingCode }, - actions: { selectRate }, - isRateSelected, - } = useSelectRateContext() - - function handleSelectRate(rateCode: string) { - selectRate({ roomIndex, rateCode, roomTypeCode }) - } - - const bannerText = useBannerText({ - bookingCode: bookingCode ?? "", - breakfastIncluded: codeProduct.rateDefinition.breakfastIncluded, - }) - - const pkgsSum = sumPackages(selectedPackages) - const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) - - const isSelected = isRateSelected({ - roomIndex, - roomTypeCode, - rate: { ...codeProduct, type: "code" }, - }) - - if ("corporateCheque" in codeProduct) { - return ( - - ) - } - - if ("voucher" in codeProduct) { - return ( - - ) - } - - if (codeProduct.public) { - return ( - - ) - } - - return null -} - -function useBannerText({ - bookingCode, - breakfastIncluded, -}: { - breakfastIncluded: boolean - bookingCode: string -}) { - const intl = useIntl() - - if (breakfastIncluded) { - return `${bookingCode} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast included", - })}` - } else { - return `${bookingCode} ∙ ${intl.formatMessage({ - defaultMessage: "Breakfast excluded", - })}` - } -} - -function CorporateChequeCode({ - codeProduct, - roomIndex, - bannerText, - packagesSum, - handleSelectRate, - isSelected, -}: { - codeProduct: Extract - roomIndex: number - roomTypeCode: string - bannerText: string - packagesSum: ReturnType - handleSelectRate: (rateCode: string) => void - isSelected: boolean -}) { - const roomNr = roomIndex + 1 - const intl = useIntl() - const rateTitles = useRateTitles() - const { localPrice, rateCode, requestedPrice } = codeProduct.corporateCheque - - const rateTermDetails = getRateTermDetails(codeProduct) - - let price = `${localPrice.numberOfCheques} CC` - - if (localPrice.additionalPricePerStay) { - price = `${price} + ${localPrice.additionalPricePerStay + packagesSum.price}` - } else if (packagesSum.price) { - price = `${price} + ${packagesSum.price}` - } - - const currency = - localPrice.additionalPricePerStay > 0 || packagesSum.price > 0 - ? (localPrice.currency ?? packagesSum.currency ?? "") - : "" - - const approximateRate = - requestedPrice?.additionalPricePerStay && requestedPrice?.currency - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: - `${requestedPrice.numberOfCheques} CC + ` + - requestedPrice.additionalPricePerStay, - unit: requestedPrice.currency, - } - : undefined - - return ( - - handleSelectRate(codeProduct.corporateCheque.rateCode) - } - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[codeProduct.rate].paymentTerm} - rate={{ - label: codeProduct.rateDefinition?.title, - price, - unit: currency, - }} - rateTitle={rateTitles[codeProduct.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) -} - -function PublicCode({ - codeProduct, - roomIndex, - bannerText, - packagesSum, - packagesSumRequested, - nights, - handleSelectRate, - isSelected, -}: { - codeProduct: Extract - roomIndex: number - roomTypeCode: string - bannerText: string - packagesSum: ReturnType - packagesSumRequested: ReturnType - nights: number - handleSelectRate: (rateCode: string) => void - isSelected: boolean -}) { - const roomNr = roomIndex + 1 - const intl = useIntl() - const rateTitles = useRateTitles() - if (!codeProduct.public) { - return null - } - - const rateTermDetails = getRateTermDetails(codeProduct) - - const night = intl - .formatMessage({ - defaultMessage: "night", - }) - .toUpperCase() - - const { localPrice, rateCode, requestedPrice } = codeProduct.public - const pricePerNight = calculatePricePerNightPriceProduct( - localPrice.pricePerNight, - requestedPrice?.pricePerNight, - nights, - packagesSum.price, - packagesSumRequested.price - ) - - const approximateRate = - pricePerNight.totalRequestedPrice && requestedPrice?.currency - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: pricePerNight.totalRequestedPrice, - unit: requestedPrice.currency, - } - : undefined - - const regularPricePerNight = calculatePricePerNightPriceProduct( - localPrice.regularPricePerNight, - requestedPrice?.regularPricePerNight, - nights, - packagesSum.price, - packagesSumRequested.price - ) - - const comparisonRate = - +regularPricePerNight.totalPrice > +pricePerNight.totalPrice - ? { - price: regularPricePerNight.totalPrice, - unit: localPrice.currency, - } - : undefined - - return ( - handleSelectRate(codeProduct.public!.rateCode)} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[codeProduct.rate].paymentTerm} - rate={{ - label: codeProduct.rateDefinition?.title, - price: pricePerNight.totalPrice, - unit: `${localPrice.currency}/${night}`, - }} - rateTitle={rateTitles[codeProduct.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) -} - -function VoucherCode({ - codeProduct, - bannerText, - packagesSum, - roomIndex, - handleSelectRate, - isSelected, -}: { - codeProduct: Extract - roomIndex: number - roomTypeCode: string - bannerText: string - packagesSum: ReturnType - handleSelectRate: (rateCode: string) => void - isSelected: boolean -}) { - const roomNr = roomIndex + 1 - const intl = useIntl() - const rateTitles = useRateTitles() - const { numberOfVouchers, rateCode } = codeProduct.voucher - - const rateTermDetails = getRateTermDetails(codeProduct) - - const voucherMsg = intl - .formatMessage({ - defaultMessage: "Voucher", - }) - .toUpperCase() - let price = `${numberOfVouchers} ${voucherMsg}` - if (packagesSum.price) { - price = `${price} + ${packagesSum.price}` - } - return ( - handleSelectRate(codeProduct.voucher.rateCode)} - isSelected={isSelected} - name={`rateCode-${roomNr}-${rateCode}`} - paymentTerm={rateTitles[codeProduct.rate].paymentTerm} - rate={{ - label: codeProduct.rateDefinition?.title, - price, - unit: packagesSum.currency ?? "", - }} - rateTitle={rateTitles[codeProduct.rate].title} - rateTermDetails={rateTermDetails} - value={rateCode} - /> - ) -} - -function getRateTermDetails(codeProduct: CodeProduct): RateTermDetails { - return codeProduct.rateDefinitionMember - ? [ - { - title: codeProduct.rateDefinition.title, - terms: codeProduct.rateDefinition.generalTerms, - }, - { - title: codeProduct.rateDefinitionMember.title, - terms: codeProduct.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: codeProduct.rateDefinition.title, - terms: codeProduct.rateDefinition.generalTerms, - }, - ] -} - -type RateTermDetails = { title: string; terms: string[] }[] diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx deleted file mode 100644 index 8937efb99..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Redemptions.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import PointsRateCard from "@scandic-hotels/design-system/PointsRateCard" - -import { sumPackages } from "@/components/HotelReservation/utils" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useRateTitles from "@/hooks/booking/useRateTitles" - -import type { - AvailabilityWithRoomInfo, - Package, -} from "@/contexts/SelectRate/types" - -type RedemptionsProps = { - redemptions: AvailabilityWithRoomInfo["redemptions"] - roomTypeCode: string - selectedPackages: Package[] - roomIndex: number -} - -export default function Redemptions({ - redemptions, - roomTypeCode, - roomIndex, - selectedPackages, -}: RedemptionsProps) { - const intl = useIntl() - const rateTitles = useRateTitles() - const { - actions: { selectRate }, - selectedRates, - } = useSelectRateContext() - - // TODO: Replace with context value when we have support for dropdown "Show all rates" - const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum - const selectedRate = selectedRates.forRoom(roomIndex) - - if ( - selectedFilter === BookingCodeFilterEnum.Discounted || - !redemptions.length - ) { - return null - } - - const rewardNight = intl.formatMessage({ - defaultMessage: "Reward night", - }) - const pkgsSum = sumPackages(selectedPackages) - - const breakfastIncluded = intl.formatMessage({ - defaultMessage: "Breakfast included", - }) - const breakfastExcluded = intl.formatMessage({ - defaultMessage: "Breakfast excluded", - }) - - const selectedRateCode = - selectedRate && - "redemption" in selectedRate && - selectedRate.roomInfo.roomTypeCode === roomTypeCode - ? selectedRate.redemption.rateCode - : "" - - const rates = redemptions.map((r) => { - let additionalPrice - if (r.redemption.localPrice.additionalPricePerStay) { - additionalPrice = - r.redemption.localPrice.additionalPricePerStay + pkgsSum.price - } else if (pkgsSum.price) { - additionalPrice = pkgsSum.price - } - let additionalPriceCurrency - if (r.redemption.localPrice.currency) { - additionalPriceCurrency = r.redemption.localPrice.currency - } else if (pkgsSum.currency) { - additionalPriceCurrency = pkgsSum.currency - } - return { - additionalPrice: - additionalPrice && additionalPriceCurrency - ? { - currency: additionalPriceCurrency, - price: additionalPrice.toString(), - } - : undefined, - currency: "PTS", - isDisabled: !r.redemption.hasEnoughPoints, - points: r.redemption.localPrice.pointsPerStay.toString(), - rateCode: r.redemption.rateCode, - } - }) - - const notEnoughPoints = rates.every((rate) => rate.isDisabled) - const firstRedemption = redemptions[0] - const bannerText = firstRedemption.rateDefinition.breakfastIncluded - ? `${rewardNight} ∙ ${breakfastIncluded}` - : `${rewardNight} ∙ ${breakfastExcluded}` - - const rateTermDetails = [ - { - title: rateTitles[firstRedemption.rate].title, - terms: firstRedemption.rateDefinition.generalTerms, - }, - ] - - return ( - { - selectRate({ - roomIndex: roomIndex, - rateCode: rateCode, - roomTypeCode: roomTypeCode, - }) - }} - paymentTerm={rateTitles[firstRedemption.rate].paymentTerm} - rates={rates} - rateTitle={rateTitles[firstRedemption.rate].title} - rateTermDetails={rateTermDetails} - selectedRate={selectedRateCode} - isNotEnoughPoints={notEnoughPoints} - notEnoughPointsText={intl.formatMessage({ - defaultMessage: "Not enough points", - })} - /> - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx deleted file mode 100644 index 2bfdeb31e..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Regular.tsx +++ /dev/null @@ -1,257 +0,0 @@ -"use client" -import { useSession } from "next-auth/react" -import { useIntl } from "react-intl" - -import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import NoRateAvailableCard from "@scandic-hotels/design-system/NoRateAvailableCard" -import RegularRateCard from "@scandic-hotels/design-system/RegularRateCard" - -import { - sumPackages, - sumPackagesRequestedPrice, -} from "@/components/HotelReservation/utils" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" -import useRateTitles from "@/hooks/booking/useRateTitles" -import { isValidClientSession } from "@/utils/clientSession" - -import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" - -import type { Package } from "@scandic-hotels/trpc/types/packages" - -import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" - -interface Rate { - label: string - price: string - unit: string -} - -interface Rates { - memberRate?: Rate - rate?: Rate -} -type RegularRateProps = { - nights: number - regular: AvailabilityWithRoomInfo["regular"] - roomIndex: number - roomTypeCode: string - selectedPackages: Package[] -} - -export function RegularRate({ - nights, - regular, - roomTypeCode, - roomIndex, - selectedPackages, -}: RegularRateProps) { - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) - - return regular.map((product, ix) => ( - - )) -} - -function Inner({ - product, - isUserLoggedIn, - nights, - roomTypeCode, - roomIndex, - selectedPackages, -}: { - product: AvailabilityWithRoomInfo["regular"][number] - isUserLoggedIn: boolean - nights: number - roomTypeCode: string - roomIndex: number - selectedPackages: Package[] -}) { - const intl = useIntl() - - const rateTitles = useRateTitles() - const { - isRateSelected, - bookingCodeFilter, - actions: { selectRate }, - } = useSelectRateContext() - - const isMainRoom = roomIndex === 0 - - if (bookingCodeFilter === BookingCodeFilterEnum.Discounted) { - return null - } - - const night = intl - .formatMessage({ - defaultMessage: "night", - }) - .toUpperCase() - const pkgsSum = sumPackages(selectedPackages) - const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) - - const standardPriceMsg = intl.formatMessage({ - defaultMessage: "Standard price", - }) - - const memberPriceMsg = intl.formatMessage({ - defaultMessage: "Member price", - }) - - const approxMsg = intl.formatMessage({ - defaultMessage: "Approx.", - }) - - const { member, public: standard } = product - const isMainRoomAndLoggedIn = isMainRoom && isUserLoggedIn - const isMainRoomLoggedInWithoutMember = - isMainRoomAndLoggedIn && !product.member - const noRateAvailable = !product.member && !product.public - const hideStandardPrice = isMainRoomAndLoggedIn && !!member - const isNotLoggedInAndOnlyMemberRate = !isUserLoggedIn && !standard - const rateCode = hideStandardPrice ? member.rateCode : standard?.rateCode - const counterRateCode = isMainRoomAndLoggedIn - ? standard?.rateCode - : member?.rateCode - - if ( - noRateAvailable || - isMainRoomLoggedInWithoutMember || - !rateCode || - isNotLoggedInAndOnlyMemberRate - ) { - return ( - - ) - } - - const memberPricePerNight = member - ? calculatePricePerNightPriceProduct( - member.localPrice.pricePerNight, - member.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - const standardPricePerNight = standard - ? calculatePricePerNightPriceProduct( - standard.localPrice.pricePerNight, - standard.requestedPrice?.pricePerNight, - nights, - pkgsSum.price, - pkgsSumRequested.price - ) - : undefined - - let approximateMemberRatePrice = null - const rates: Rates = {} - if (memberPricePerNight) { - rates.memberRate = { - label: memberPriceMsg, - price: memberPricePerNight.totalPrice, - unit: `${member!.localPrice.currency}/${night}`, - } - - if (memberPricePerNight.totalRequestedPrice) { - approximateMemberRatePrice = memberPricePerNight.totalRequestedPrice - } - } - - let approximateStandardRatePrice = null - if (standardPricePerNight) { - rates.rate = { - label: standardPriceMsg, - price: standardPricePerNight.totalPrice, - unit: `${standard!.localPrice.currency}/${night}`, - } - - if (standardPricePerNight.totalRequestedPrice && !isUserLoggedIn) { - approximateStandardRatePrice = standardPricePerNight.totalRequestedPrice - } - } - - let approximatePrice = "" - if (approximateStandardRatePrice && approximateMemberRatePrice) { - approximatePrice = `${approximateStandardRatePrice}/${approximateMemberRatePrice}` - } else if (approximateStandardRatePrice) { - approximatePrice = approximateStandardRatePrice - } else if (approximateMemberRatePrice) { - approximatePrice = approximateMemberRatePrice - } - - const requestedCurrency = - standard?.requestedPrice?.currency || member?.requestedPrice?.currency - const approximateRate = - approximatePrice && requestedCurrency - ? { - label: approxMsg, - price: approximatePrice, - unit: requestedCurrency, - } - : undefined - - const rateTermDetails = product.rateDefinitionMember - ? [ - { - title: standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - { - title: memberPriceMsg, - terms: product.rateDefinitionMember.generalTerms, - }, - ] - : [ - { - title: standardPriceMsg, - terms: product.rateDefinition.generalTerms, - }, - ] - - const isSelected = isRateSelected({ - roomIndex, - rate: { ...product, type: "regular" }, - roomTypeCode, - }) - - const isMemberRateActive = isUserLoggedIn && isMainRoom && !!member - - return ( - { - selectRate({ - roomIndex: roomIndex, - rateCode: rateCode, - roomTypeCode: roomTypeCode, - counterRateCode: counterRateCode, - }) - }} - isMemberRateActive={isMemberRateActive} - isSelected={isSelected} - name={`rateCode-${roomIndex + 1}-${rateCode}`} - paymentTerm={rateTitles[product.rate].paymentTerm} - rateTitle={rateTitles[product.rate].title} - value={rateCode} - rateTermDetails={rateTermDetails} - /> - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx deleted file mode 100644 index 95a2dfd53..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client" - -import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter" -import { Divider } from "@scandic-hotels/design-system/Divider" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { BreakfastMessage } from "./BreakfastMessage" -import Campaign from "./Campaign" -import Code from "./Code" -import Redemptions from "./Redemptions" -import { RegularRate } from "./Regular" - -import type { Package } from "@scandic-hotels/trpc/types/packages" - -import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" - -export interface RatesProps { - roomConfiguration: AvailabilityWithRoomInfo - roomIndex: number - selectedPackages: Package[] -} -export function Rates({ - roomConfiguration: { - breakfastIncludedInAllRates, - breakfastIncludedInAllRatesMember, - campaign, - code, - redemptions, - regular, - roomTypeCode, - }, - selectedPackages, - roomIndex, -}: RatesProps) { - const { - bookingCodeFilter, - input: { nights }, - } = useSelectRateContext() - - const sharedProps = { - nights, - roomTypeCode, - roomIndex, - selectedPackages, - } - const showAllRates = bookingCodeFilter === BookingCodeFilterEnum.All - const hasBookingCodeRates = !!(campaign.length || code.length) - const hasRegularRates = !!regular.length - const showDivider = showAllRates && hasBookingCodeRates && hasRegularRates - - return ( - <> - - - - {showDivider ? : null} - - - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/isSelected.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/isSelected.ts deleted file mode 100644 index 759ff1ee2..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/isSelected.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { - CorporateChequeProduct, - PriceProduct, - VoucherProduct, -} from "@scandic-hotels/trpc/types/roomAvailability" - -import type { SelectedRate } from "@/types/stores/rates" - -export function isSelectedPriceProduct( - product: PriceProduct, - selectedRate: SelectedRate | null, - roomTypeCode: string -) { - if (!selectedRate || roomTypeCode !== selectedRate.roomTypeCode) { - return false - } - - const { member, public: standard } = product - let isSelected = false - if ( - "member" in selectedRate.product && - selectedRate.product.member && - member - ) { - isSelected = selectedRate.product.member.rateCode === member.rateCode - } - - if ( - "public" in selectedRate.product && - selectedRate.product.public && - standard - ) { - isSelected = selectedRate.product.public.rateCode === standard.rateCode - } - - return isSelected -} - -export function isSelectedCorporateCheque( - product: CorporateChequeProduct, - selectedRate: SelectedRate | null, - roomTypeCode: string -) { - if (!selectedRate || !("corporateCheque" in selectedRate.product)) { - return false - } - - const isSameRateCode = - product.corporateCheque.rateCode === - selectedRate.product.corporateCheque.rateCode - const isSameRoomTypeCode = selectedRate.roomTypeCode === roomTypeCode - return isSameRateCode && isSameRoomTypeCode -} - -export function isSelectedVoucher( - product: VoucherProduct, - selectedRate: SelectedRate | null, - roomTypeCode: string -) { - if (!selectedRate || !("voucher" in selectedRate.product)) { - return false - } - - const isSameRateCode = - product.voucher.rateCode === selectedRate.product.voucher.rateCode - const isSameRoomTypeCode = selectedRate.roomTypeCode === roomTypeCode - return isSameRateCode && isSameRoomTypeCode -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/totalPricePerNight.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/totalPricePerNight.ts deleted file mode 100644 index f6b77ebac..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/totalPricePerNight.ts +++ /dev/null @@ -1,27 +0,0 @@ -export function calculatePricePerNightPriceProduct( - pricePerNight: number, - requestedPricePerNight: number | undefined, - nights: number, - packagesSumLocal: number, - packagesSumRequested: number -) { - const totalPrice = packagesSumLocal - ? Math.floor(pricePerNight + packagesSumLocal / nights) - : Math.floor(pricePerNight) - - let totalRequestedPrice = undefined - if (requestedPricePerNight) { - if (packagesSumRequested) { - totalRequestedPrice = Math.floor( - requestedPricePerNight + packagesSumRequested / nights - ) - } else { - totalRequestedPrice = Math.floor(requestedPricePerNight) - } - } - - return { - totalPrice: totalPrice.toString(), - totalRequestedPrice: totalRequestedPrice?.toString(), - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/image.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/image.module.css deleted file mode 100644 index 7ee68ca47..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/image.module.css +++ /dev/null @@ -1,48 +0,0 @@ -.imageContainer { - margin: 0 calc(-1 * var(--Spacing-x2)); - min-height: 190px; - position: relative; - border-radius: var(--Corner-radius-lg) var(--Corner-radius-lg) 0 0; -} - -div[data-multiroom="true"] .imageContainer { - margin: 0; -} - -.chipContainer { - display: flex; - flex-direction: row; - gap: var(--Spacing-x1); - left: 12px; - position: absolute; - top: 12px; - z-index: 1; -} - -.chip { - background-color: var(--Main-Grey-White); - border-radius: var(--Corner-radius-sm); - padding: var(--Spacing-x-half) var(--Spacing-x1); -} - -.imageContainer img { - aspect-ratio: 16/9; - max-width: 100%; - object-fit: cover; -} - -.toggleSidePeek { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - bottom: 0; - color: var(--Component-Button-Brand-Secondary-On-fill-Inverted); - background-color: var(--Surface-Brand-Primary-1-OnSurface-Default); - height: 40px; - width: 100%; -} - -.inventory { - color: var(--Text-Interactive-Default); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx deleted file mode 100644 index 02a4cd4f9..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomImage/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client" -import { memo } from "react" -import { useIntl } from "react-intl" - -import ImageGallery from "@scandic-hotels/design-system/ImageGallery" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import { IconForFeatureCode } from "@/components/HotelReservation/utils" -import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" - -import ToggleSidePeek from "../Details/ToggleSidePeek" - -import styles from "./image.module.css" - -import type { ApiImage } from "@scandic-hotels/trpc/types/hotel" -import type { PackageEnum } from "@scandic-hotels/trpc/types/packages" -import type { RoomConfiguration } from "@scandic-hotels/trpc/types/roomAvailability" - -export type RoomListItemImageProps = Pick< - RoomConfiguration, - "roomType" | "roomTypeCode" | "roomsLeft" -> & { - selectedPackages: PackageEnum[] - images: ApiImage[] - hotelId: string -} - -const RoomImage = memo(function RoomImage({ - roomsLeft, - roomType, - roomTypeCode, - selectedPackages, - images, - hotelId, -}: RoomListItemImageProps) { - const galleryImages = mapApiImagesToGalleryImages(images || []) - - return ( -
    -
    - - - {selectedPackages.map((pkg) => ( - - {IconForFeatureCode({ featureCode: pkg, size: 16 })} - - ))} -
    - -
    - {roomTypeCode && ( - - )} -
    -
    - ) -}) - -export default RoomImage - -function LowInventoryTag({ roomsLeft }: { roomsLeft: number }) { - const intl = useIntl() - const showLowInventory = roomsLeft > 0 && roomsLeft < 5 - - if (!showLowInventory) { - return null - } - - return ( - - -

    - {intl.formatMessage( - { - defaultMessage: "{amount, number} left", - }, - { amount: roomsLeft } - )} -

    -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/index.tsx deleted file mode 100644 index f5e70ea62..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client" -import { useIntl } from "react-intl" - -import Caption from "@scandic-hotels/design-system/Caption" -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" - -import styles from "./notAvailable.module.css" - -export default function RoomNotAvailable() { - const intl = useIntl() - return ( -
    -
    - - - {intl.formatMessage({ - defaultMessage: "This room is not available", - })} - -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/notAvailable.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/notAvailable.module.css deleted file mode 100644 index 0625564e2..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/RoomNotAvailable/notAvailable.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.noRooms { - background-color: var(--Base-Surface-Secondary-light-Normal); - border-radius: var(--Corner-radius-md); - display: flex; - gap: var(--Spacing-x1); - margin: 0; - padding: var(--Spacing-x2); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx deleted file mode 100644 index 64726dd25..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" - -import Details from "./Details" -import { listItemVariants } from "./listItemVariants" -import { Rates } from "./Rates" -import RoomImage from "./RoomImage" -import RoomNotAvailable from "./RoomNotAvailable" - -import styles from "./roomListItem.module.css" - -import type { Package } from "@scandic-hotels/trpc/types/packages" - -import type { AvailabilityWithRoomInfo } from "@/contexts/SelectRate/types" - -export type RoomListItemProps = { - room: AvailabilityWithRoomInfo - selectedPackages: Package[] - roomIndex: number - hotelId: string -} - -export function RoomListItem({ - room, - selectedPackages, - roomIndex, - hotelId, -}: RoomListItemProps) { - if (!room || !room.roomInfo) { - return null - } - - const classNames = listItemVariants({ - availability: - room.status === AvailabilityEnum.NotAvailable - ? "noAvailability" - : "default", - }) - - return ( -
  • - pkg.code)} - images={room.roomInfo.images ?? []} - hotelId={hotelId} - /> -
    - -
    - {room.status === AvailabilityEnum.NotAvailable ? ( - - ) : ( - - )} -
    -
  • - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/listItemVariants.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/listItemVariants.ts deleted file mode 100644 index 8c8099c85..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/listItemVariants.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { cva } from "class-variance-authority" - -import styles from "./roomListItem.module.css" - -export const listItemVariants = cva(styles.listItem, { - variants: { - availability: { - noAvailability: styles.noAvailability, - default: "", - }, - }, -}) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/roomListItem.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/roomListItem.module.css deleted file mode 100644 index dff223529..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomListItem/roomListItem.module.css +++ /dev/null @@ -1,25 +0,0 @@ -.listItem { - align-content: flex-start; - background-color: #fff; - border: 1px solid var(--Base-Border-Subtle); - border-radius: var(--Corner-radius-lg); - display: grid; - font-size: 14px; - gap: var(--Spacing-x-one-and-half); - padding: 0 var(--Spacing-x2) var(--Spacing-x2); - position: relative; -} - -div[data-multiroom="true"] .listItem { - border: none; - padding: 0; -} - -.listItem.noAvailability { - opacity: 0.6; -} - -.container { - display: grid; - gap: var(--Spacing-x2); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx deleted file mode 100644 index c90ac9037..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -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/SelectRate2/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx deleted file mode 100644 index e3b3df8b7..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/ScrollToList.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client" -import { useEffect } from "react" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import styles from "./rooms.module.css" - -export default function ScrollToList() { - const { - input: { isMultiRoom }, - selectedRates, - } = useSelectRateContext() - const selectedRateCode = selectedRates.rates[0] - ? `${selectedRates.rates[0].rateDefinition.rateCode}${selectedRates.rates[0].roomInfo.roomTypeCode}` - : null - - useEffect(() => { - if (isMultiRoom) { - return - } - - if (!selectedRateCode) { - return - } - - // Required to prevent the history.pushState on the first selection - // to scroll user back to top - requestAnimationFrame(() => { - const SCROLL_OFFSET = 173 // summary on mobile is 163px - - const selectedRateCard: HTMLElement | null = document.querySelector( - `.${styles.roomList} label:has(input[type=radio]:checked)` - ) - - if (selectedRateCard) { - const elementPosition = selectedRateCard.getBoundingClientRect().top - const windowHeight = window.innerHeight - const offsetPosition = - elementPosition + - window.scrollY - - (windowHeight - selectedRateCard.offsetHeight - SCROLL_OFFSET) - - window.scrollTo({ - top: offsetPosition, - behavior: "instant", - }) - } - }) - }, [isMultiRoom, selectedRateCode]) - - return null -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/index.tsx deleted file mode 100644 index 174c5b3a2..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { RoomListItem } from "./RoomListItem" -import { RoomsListSkeleton } from "./RoomsListSkeleton" -import ScrollToList from "./ScrollToList" - -import styles from "./rooms.module.css" - -export default function RoomsList({ roomIndex }: { roomIndex: number }) { - const { getAvailabilityForRoom, isFetching, input, getPackagesForRoom } = - useSelectRateContext() - - if (isFetching) { - return - } - - const hotelId = input?.data?.booking.hotelId - if (!hotelId) { - throw new Error("Hotel ID is required to display room availability") - } - - return ( - <> - -
      - {getAvailabilityForRoom(roomIndex)?.map((room, ix) => { - return ( - - ) - })} -
    - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/rooms.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/rooms.module.css deleted file mode 100644 index fab1703c8..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/rooms.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.roomList { - list-style: none; - display: grid; - gap: var(--Spacing-x2); - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - overflow: hidden; -} - -.roomList > li { - width: 100%; -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css deleted file mode 100644 index 23ccc3f2c..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/RoomsList/roomsListSkeleton.module.css +++ /dev/null @@ -1,17 +0,0 @@ -.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/SelectRate2/RoomsContainer/Rooms/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/index.tsx deleted file mode 100644 index 95aa2049f..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client" - -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { MultiRoomWrapper } from "./MultiRoomWrapper" -import NoAvailabilityAlert from "./NoAvailabilityAlert" -import { RoomsHeader } from "./RoomsHeader" -import RoomsList from "./RoomsList" - -import styles from "./rooms.module.css" - -export default function Rooms() { - const { - availability, - input: { isMultiRoom }, - } = useSelectRateContext() - - if (!availability) { - return null - } - - return ( -
    - {availability.data?.map((_room, idx) => { - return ( - - - - - - ) - })} -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/rooms.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/rooms.module.css deleted file mode 100644 index 1d29321cc..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/Rooms/rooms.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.content { - max-width: var(--max-width-page); - margin: 0 auto; - display: flex; - flex-direction: column; - gap: var(--Spacing-x2); - padding: var(--Spacing-x5) 0; -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.module.css deleted file mode 100644 index 4e9a9d583..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.container { - margin: 0 auto; - max-width: var(--max-width-page); -} - -.filterContainer { - height: 38px; -} - -.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; - margin-top: 20px; - gap: var(--Spacing-x2); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.tsx deleted file mode 100644 index f14ca0e06..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/RoomsContainerSkeleton.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" - -import styles from "./RoomsContainerSkeleton.module.css" - -type Props = { - count?: number -} - -export function RoomsContainerSkeleton({ count = 4 }: Props) { - return ( -
    -
    -
    - {Array.from({ length: count }).map((_, index) => ( - - ))} -
    -
    - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.module.css b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.module.css deleted file mode 100644 index 490c64c24..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.errorContainer { - margin: 0 auto; - padding: var(--Spacing-x-one-and-half) 0; - width: 100%; - max-width: var(--max-width-page); -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.tsx deleted file mode 100644 index 4cd54f188..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/RoomsContainer/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client" - -import { TRPCClientError } from "@trpc/client" -import { useIntl } from "react-intl" - -import { AlertTypeEnum } from "@scandic-hotels/trpc/types/alertType" - -import Alert from "@/components/TempDesignSystem/Alert" -import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" - -import { RateSummary } from "./RateSummary" -import Rooms from "./Rooms" -import { RoomsContainerSkeleton } from "./RoomsContainerSkeleton" - -import styles from "./index.module.css" - -import type { AppRouter } from "@scandic-hotels/trpc/routers/appRouter" - -import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer" - -export function RoomsContainer({}: RoomsContainerProps) { - const intl = useIntl() - - const { - availability: { error, isFetching, isError }, - input: { hasError: hasInputError }, - } = useSelectRateContext() - - if (isFetching) { - return - } - - if (isError || hasInputError) { - const errorMessage = getErrorMessage(error, intl) - - return ( -
    - -
    - ) - } - - return ( - <> - - - - ) -} - -function getErrorMessage(error: unknown, intl: ReturnType) { - if (!isTRPCClientError(error)) { - return intl.formatMessage({ - defaultMessage: "Something went wrong", - }) - } - - const firstError = error.data?.zodError?.formErrors?.at(0) - - switch (firstError) { - case "FROMDATE_INVALID": - case "TODATE_INVALID": - case "TODATE_MUST_BE_AFTER_FROMDATE": - case "FROMDATE_CANNOT_BE_IN_THE_PAST": { - return intl.formatMessage({ - defaultMessage: "Invalid dates", - }) - } - default: - return intl.formatMessage({ - defaultMessage: "Something went wrong", - }) - } -} - -function isTRPCClientError( - cause: unknown -): cause is TRPCClientError { - return cause instanceof TRPCClientError -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/index.tsx deleted file mode 100644 index 7dc0f8afe..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client" - -import { useSearchParams } from "next/navigation" -import React from "react" - -import { - parseSelectRateSearchParams, - searchParamsToRecord, -} from "@scandic-hotels/booking-flow/utils/url" -import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" - -import TrackingSDK from "@/components/TrackingSDK" -import useLang from "@/hooks/useLang" - -import { getValidDates } from "../getValidDates" -import { getSelectRateTracking } from "./tracking" - -export default function Tracking({ - hotelId, - hotelName, - country, - city, -}: { - hotelId: string - hotelName: string - country: string - city: string -}) { - const lang = useLang() - const params = useSearchParams() - const booking = parseSelectRateSearchParams(searchParamsToRecord(params)) - - if (!booking) return null - - const { fromDate, toDate } = getValidDates(booking.fromDate, booking.toDate) - - const { rooms, searchType, bookingCode, city: paramCity } = booking - - const arrivalDate = fromDate.toDate() - const departureDate = toDate.toDate() - - const { hotelsTrackingData, pageTrackingData } = getSelectRateTracking({ - lang, - arrivalDate, - departureDate, - hotelId, - hotelName, - country, - hotelCity: city, - paramCity, - bookingCode, - isRedemption: searchType === SEARCH_TYPE_REDEMPTION, - rooms, - }) - - return ( - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/tracking.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/tracking.ts deleted file mode 100644 index 01c535cb9..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/Tracking/tracking.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { differenceInCalendarDays, format, isWeekend } from "date-fns" - -import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import type { Lang } from "@scandic-hotels/common/constants/language" - -import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate" -import { - TrackingChannelEnum, - type TrackingSDKHotelInfo, - type TrackingSDKPageData, -} from "@/types/components/tracking" -import type { ChildrenInRoom } from "@/utils/hotelSearchDetails" - -type SelectRateTrackingInput = { - lang: Lang - arrivalDate: Date - departureDate: Date - hotelId: string - hotelName: string - country: string | undefined - hotelCity: string | undefined - paramCity: string | undefined - bookingCode?: string - isRedemption?: boolean - rooms?: Room[] -} - -export function getSelectRateTracking({ - lang, - arrivalDate, - departureDate, - hotelId, - hotelName, - country, - hotelCity, - paramCity, - bookingCode, - isRedemption = false, - rooms = [], -}: SelectRateTrackingInput) { - const pageTrackingData: TrackingSDKPageData = { - channel: TrackingChannelEnum.hotelreservation, - domainLanguage: lang, - pageId: "select-rate", - pageName: "hotelreservation|select-rate", - pageType: "bookingroomsandratespage", - siteSections: "hotelreservation|select-rate", - siteVersion: "new-web", - } - - let adultsInRoom: number[] = [] - let childrenInRoom: ChildrenInRoom = null - if (rooms?.length) { - adultsInRoom = rooms.map((room) => room.adults ?? 0) - childrenInRoom = rooms.map((room) => room.childrenInRoom ?? null) - } - - const hotelsTrackingData: TrackingSDKHotelInfo = { - ageOfChildren: childrenInRoom - ?.map((c) => c?.map((k) => k.age).join(",") ?? "") - .join("|"), - arrivalDate: format(arrivalDate, "yyyy-MM-dd"), - bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday", - childBedPreference: childrenInRoom - ?.map((c) => c?.map((k) => ChildBedMapEnum[k.bed]).join(",") ?? "") - .join("|"), - country, - departureDate: format(departureDate, "yyyy-MM-dd"), - duration: differenceInCalendarDays(departureDate, arrivalDate), - hotelID: hotelId, - leadTime: differenceInCalendarDays(arrivalDate, new Date()), - noOfAdults: adultsInRoom.join(","), - noOfChildren: childrenInRoom?.map((kids) => kids?.length ?? 0).join(","), - noOfRooms: rooms?.length ?? 0, - region: hotelCity, - searchTerm: paramCity ?? hotelName, - searchType: "hotel", - bookingCode: bookingCode ?? "n/a", - rewardNight: isRedemption ? "yes" : "no", - specialRoomType: rooms - ?.map((room) => { - const packages = room.packages - ?.map((pkg) => { - if (pkg === RoomPackageCodeEnum.ACCESSIBILITY_ROOM) { - return "accessibility" - } else if (pkg === RoomPackageCodeEnum.ALLERGY_ROOM) { - return "allergy friendly" - } else if (pkg === RoomPackageCodeEnum.PET_ROOM) { - return "pet room" - } else { - return "" - } - }) - .join(",") - - return packages ?? "" - }) - .join("|"), - } - - return { - hotelsTrackingData, - pageTrackingData, - } -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.test.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.test.ts deleted file mode 100644 index 07579fc52..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" - -import { getValidFromDate, getValidToDate } from "./getValidDates" - -const NOW = new Date("2020-10-01T00:00:00Z") - -describe("getValidFromDate", () => { - beforeAll(() => { - vi.useFakeTimers({ now: NOW }) - }) - - afterAll(() => { - vi.useRealTimers() - }) - - describe("getValidFromDate", () => { - it("returns today when empty string is provided", () => { - const actual = getValidFromDate("") - expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z") - }) - - it("returns today when undefined is provided", () => { - const actual = getValidFromDate(undefined) - expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z") - }) - - it("returns given date in utc", () => { - const actual = getValidFromDate("2024-01-01") - expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z") - }) - }) - - describe("getValidToDate", () => { - it("returns day after fromDate when empty string is provided", () => { - const actual = getValidToDate("", NOW) - expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z") - }) - - it("returns day after fromDate when undefined is provided", () => { - const actual = getValidToDate(undefined, NOW) - expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z") - }) - - it("returns given date in utc", () => { - const actual = getValidToDate("2024-01-01", NOW) - expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z") - }) - - it("fallsback to day after fromDate when given date is before fromDate", () => { - const actual = getValidToDate("2020-09-30", NOW) - expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z") - }) - }) -}) diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.ts b/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.ts deleted file mode 100644 index a883fd8ba..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/getValidDates.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { dt } from "@scandic-hotels/common/dt" - -import type { Dayjs } from "dayjs" - -/** - * Get valid dates from stringFromDate and stringToDate making sure that they are not in the past and chronologically correct - * @example const { fromDate, toDate} = getValidDates("2021-01-01", "2021-01-02") - */ -export function getValidDates( - stringFromDate: string | undefined, - stringToDate: string | undefined -): { fromDate: Dayjs; toDate: Dayjs } { - const fromDate = getValidFromDate(stringFromDate) - const toDate = getValidToDate(stringToDate, fromDate) - - return { fromDate, toDate } -} - -/** - * Get valid fromDate from stringFromDate making sure that it is not in the past - */ -export function getValidFromDate(stringFromDate: string | undefined): Dayjs { - const now = dt().utc() - if (!stringFromDate) { - return now - } - const toDate = dt(stringFromDate) - - const yesterday = now.subtract(1, "day") - if (!toDate.isAfter(yesterday)) { - return now - } - - return toDate -} - -/** - * Get valid toDate from stringToDate making sure that it is after fromDate - */ -export function getValidToDate( - stringToDate: string | undefined, - fromDate: Dayjs | Date -): Dayjs { - const tomorrow = dt().utc().add(1, "day") - if (!stringToDate) { - return tomorrow - } - - const toDate = dt(stringToDate) - if (toDate.isAfter(fromDate)) { - return toDate - } - - return tomorrow -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate2/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate2/index.tsx deleted file mode 100644 index deb80f7a5..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectRate2/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { cookies } from "next/headers" - -import { FamilyAndFriendsCodes } from "@/constants/booking" - -import { HotelInfoCard } from "@/components/HotelReservation/SelectRate2/HotelInfoCard" -import { RoomsContainer } from "@/components/HotelReservation/SelectRate2/RoomsContainer" - -import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert" -import AvailabilityError from "./AvailabilityError" -import Tracking from "./Tracking" - -import type { RouterOutput } from "@scandic-hotels/trpc/client" - -import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate" - -export default async function SelectRatePage({ - booking, - hotelData, -}: { - hotelData: NonNullable - booking: SelectRateBooking -}) { - const bookingCode = booking.bookingCode - - let isInValidFNF = false - if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) { - const cookieStore = await cookies() - isInValidFNF = cookieStore.get("sc")?.value !== "1" - } - return ( - <> - - - {isInValidFNF ? ( - - ) : ( - - )} - - - - - - ) -}