diff --git a/apps/scandic-web/components/BookingCodeChip/bookingCodeChip.module.css b/apps/scandic-web/components/BookingCodeChip/bookingCodeChip.module.css new file mode 100644 index 000000000..5d439bbfa --- /dev/null +++ b/apps/scandic-web/components/BookingCodeChip/bookingCodeChip.module.css @@ -0,0 +1,12 @@ +.bookingCodeChip { + display: flex; + gap: var(--Space-x05); +} + +.bookingCodeChip .unavailable { + text-decoration: line-through; +} + +.center { + justify-content: center; +} diff --git a/apps/scandic-web/components/BookingCodeChip/index.tsx b/apps/scandic-web/components/BookingCodeChip/index.tsx new file mode 100644 index 000000000..e76d61fcd --- /dev/null +++ b/apps/scandic-web/components/BookingCodeChip/index.tsx @@ -0,0 +1,90 @@ +import { useIntl } from "react-intl" + +import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import IconChip from "../TempDesignSystem/IconChip" + +import styles from "./bookingCodeChip.module.css" + +type BookingCodeChipProps = { + alignCenter?: boolean + bookingCode?: string | null + isBreakfastIncluded?: boolean + isCampaign?: boolean + isUnavailable?: boolean +} + +export default function BookingCodeChip({ + alignCenter, + bookingCode, + isBreakfastIncluded, + isCampaign, + isUnavailable, +}: BookingCodeChipProps) { + const intl = useIntl() + + if (isCampaign) { + return ( + } + className={alignCenter ? styles.center : undefined} + > +

+ + + {intl.formatMessage({ defaultMessage: "Campaign" })} + + + + + {isBreakfastIncluded + ? // eslint-disable-next-line formatjs/no-literal-string-in-jsx + `${bookingCode ?? ""} ${intl.formatMessage({ + defaultMessage: "Breakfast included", + })}` + : // eslint-disable-next-line formatjs/no-literal-string-in-jsx + `${bookingCode ?? ""} ${intl.formatMessage({ + defaultMessage: "Breakfast excluded", + })}`} + + +

+
+ ) + } + + if (!bookingCode) { + return null + } + + return ( + } + className={alignCenter ? styles.center : undefined} + > +

+ + + {intl.formatMessage({ defaultMessage: "Booking code" })} + + + + + {isUnavailable + ? intl.formatMessage( + { defaultMessage: "{code} unavailable" }, + { code: bookingCode } + ) + : intl.formatMessage( + { defaultMessage: "{code} applied" }, + { code: bookingCode } + )} + + +

+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/TotalPrice/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/TotalPrice/index.tsx index a5cccab40..a3c45905b 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/TotalPrice/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/TotalPrice/index.tsx @@ -6,6 +6,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { useBookingConfirmationStore } from "@/stores/booking-confirmation" +import BookingCodeChip from "@/components/BookingCodeChip" import SkeletonShimmer from "@/components/SkeletonShimmer" import Divider from "@/components/TempDesignSystem/Divider" @@ -23,6 +24,7 @@ export default function TotalPrice() { ) const hasAllRoomsLoaded = rooms.every((room) => room) + const bookingCode = rooms.find((room) => room?.bookingCode)?.bookingCode return ( <> @@ -52,6 +54,7 @@ export default function TotalPrice() { )} + {bookingCode && } ) } 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 ac13047ca..a039e1062 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -5,17 +5,15 @@ import { useIntl } from "react-intl" import { Button } from "@scandic-hotels/design-system/Button" import { IconButton } from "@scandic-hotels/design-system/IconButton" -import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" import { dt } from "@/lib/dt" +import BookingCodeChip from "@/components/BookingCodeChip" import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import Modal from "@/components/Modal" import Divider from "@/components/TempDesignSystem/Divider" -import IconChip from "@/components/TempDesignSystem/IconChip" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" @@ -97,6 +95,12 @@ export default function SummaryUI({ : false const priceDetailsRooms = mapToPrice(rooms, isMember, nights) + const isAllCampaignRate = rooms.every( + (room) => room.room.roomRate.rateDefinition.isCampaignRate + ) + const isAllBreakfastIncluded = rooms.every( + (room) => room.room.roomRate.rateDefinition.breakfastIncluded + ) return (
@@ -460,6 +464,7 @@ export default function SummaryUI({ - {booking.bookingCode && ( - - } - > - {intl.formatMessage( - { - defaultMessage: "Booking code: {value}", - }, - { - value: booking.bookingCode, - strong: (text) => ( - - {text} - - ), - } - )} - - - )} + {showSignupPromo && roomOneMemberPrice && !isMember ? ( diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx index 63b8a8061..ec2933243 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx @@ -5,18 +5,17 @@ import { useRouter, useSearchParams } from "next/navigation" import { memo } from "react" import { useIntl } from "react-intl" -import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon" import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" import { Typography } from "@scandic-hotels/design-system/Typography" import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation" import { useHotelsMapStore } from "@/stores/hotels-map" +import BookingCodeChip from "@/components/BookingCodeChip" import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" import ImageGallery from "@/components/ImageGallery" import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" -import IconChip from "@/components/TempDesignSystem/IconChip" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" @@ -39,7 +38,6 @@ import styles from "./hotelCard.module.css" import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" -import { RateTypeEnum } from "@/types/enums/rateType" import type { Lang } from "@/constants/languages" function HotelCard({ @@ -72,8 +70,8 @@ function HotelCard({ const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}` const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || []) const fullPrice = - availability.productType?.public?.rateType === RateTypeEnum.Regular || - availability.productType?.member?.rateType === RateTypeEnum.Regular + !availability.productType?.public?.bookingCode && + !availability.productType?.member?.bookingCode const price = availability.productType const hasInsufficientPoints = !price?.redemptions?.some( @@ -183,29 +181,10 @@ function HotelCard({ ) : ( <> {bookingCode && ( -
- - } - > - {intl.formatMessage( - { - defaultMessage: - "Booking code: {value}", - }, - { - value: bookingCode, - strong: (text) => ( - - {text} - - ), - } - )} - - -
+ )} {(!isUserLoggedIn || !price?.member || diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts index 308513701..49c815243 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts @@ -15,6 +15,8 @@ export function getHotelPins( (r) => r?.localPrice.pointsPerStay ) return { + bookingCode: + productType?.public?.bookingCode ?? productType?.member?.bookingCode, coordinates: { lat: hotel.location.latitude, lng: hotel.location.longitude, diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx index 311024d8d..84a661759 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx @@ -25,7 +25,6 @@ import { } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { AlertTypeEnum } from "@/types/enums/alert" import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter" -import { RateTypeEnum } from "@/types/enums/rateType" export default function HotelCardListing({ hotelData, @@ -44,16 +43,28 @@ export default function HotelCardListing({ const sortBy = searchParams.get("sort") ?? DEFAULT_SORT const bookingCode = searchParams.get("bookingCode") - // Special rates (corporate cheque, voucher and reward nights) will not have regular rate hotels availability - const isSpecialRate = hotelData.find( - (hotel) => - hotel.availability.productType?.bonusCheque || - hotel.availability.productType?.voucher || - hotel.availability.productType?.redemptions - ) + // Special rates (corporate cheque, voucher) will not show regular rate hotels availability + const isSpecialRate = bookingCode + ? hotelData.find( + (hotel) => + hotel.availability.productType?.bonusCheque || + hotel.availability.productType?.voucher + ) + : false const activeCodeFilter = useBookingCodeFilterStore( (state) => state.activeCodeFilter ) + const isBookingCodeRateAvailable = + bookingCode && !isSpecialRate + ? hotelData.some( + (hotel) => + hotel.availability.productType?.public?.bookingCode || + hotel.availability.productType?.member?.bookingCode + ) + : false + const showOnlyBookingCodeRates = + isBookingCodeRateAvailable && + activeCodeFilter !== BookingCodeFilterEnum.Discounted const hotels = useMemo(() => { const sortedHotels = getSortedHotels({ @@ -61,24 +72,14 @@ export default function HotelCardListing({ sortBy, bookingCode: isSpecialRate ? null : bookingCode, }) - const updatedHotelsList = - bookingCode && !isSpecialRate - ? sortedHotels.filter( - (hotel) => - !hotel.availability.productType || - activeCodeFilter === BookingCodeFilterEnum.All || - (activeCodeFilter === BookingCodeFilterEnum.Discounted && - hotel.availability.productType.public?.rateType !== - RateTypeEnum.Regular && - hotel.availability.productType.member?.rateType !== - RateTypeEnum.Regular) || - (activeCodeFilter === BookingCodeFilterEnum.Regular && - (hotel.availability.productType.public?.rateType === - RateTypeEnum.Regular || - hotel.availability.productType.member?.rateType === - RateTypeEnum.Regular)) - ) - : sortedHotels + + const updatedHotelsList = showOnlyBookingCodeRates + ? sortedHotels.filter( + (hotel) => + hotel.availability.productType?.public?.bookingCode || + hotel.availability.productType?.member?.bookingCode + ) + : sortedHotels if (!activeFilters.length) { return updatedHotelsList @@ -92,11 +93,11 @@ export default function HotelCardListing({ ) ) }, [ - activeCodeFilter, activeFilters, bookingCode, hotelData, sortBy, + showOnlyBookingCodeRates, isSpecialRate, ]) diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts b/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts index c9e189789..4d88d2472 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts +++ b/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts @@ -1,5 +1,4 @@ import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter" -import { RateTypeEnum } from "@/types/enums/rateType" import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" function getPricePerNight(hotel: HotelResponse): number { @@ -50,18 +49,13 @@ export function getSortedHotels({ if (bookingCode) { const bookingCodeRateHotels = availableHotels.filter( (hotel) => - hotel.availability.productType?.public?.rateType !== - RateTypeEnum.Regular && - hotel.availability.productType?.member?.rateType !== - RateTypeEnum.Regular && - !!hotel.availability.productType + hotel.availability.productType?.public?.bookingCode || + hotel.availability.productType?.member?.bookingCode ) const regularRateHotels = availableHotels.filter( (hotel) => - hotel.availability.productType?.public?.rateType === - RateTypeEnum.Regular || - hotel?.availability.productType?.member?.rateType === - RateTypeEnum.Regular + !hotel.availability.productType?.public?.bookingCode && + !hotel?.availability.productType?.member?.bookingCode ) return bookingCodeRateHotels diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/BookingCode.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/BookingCode.tsx index 021c833ce..3394c2893 100644 --- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/BookingCode.tsx +++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/BookingCode.tsx @@ -1,47 +1,32 @@ "use client" -import { useIntl } from "react-intl" -import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon" -import { Typography } from "@scandic-hotels/design-system/Typography" - -import IconChip from "@/components/TempDesignSystem/IconChip" +import BookingCodeChip from "@/components/BookingCodeChip" import styles from "./row.module.css" interface BookingCodeRowProps { bookingCode?: string + isBreakfastIncluded?: boolean + isCampaignRate?: boolean } -export default function BookingCodeRow({ bookingCode }: BookingCodeRowProps) { - const intl = useIntl() - +export default function BookingCodeRow({ + bookingCode, + isBreakfastIncluded, + isCampaignRate, +}: BookingCodeRowProps) { if (!bookingCode) { return null } - const text = intl.formatMessage( - { defaultMessage: "Booking code: {value}" }, - { - value: bookingCode, - strong: (text) => ( - - {text} - - ), - } - ) - return ( - - } - > - {text} - - + ) diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx index b2dbf9d68..729035922 100644 --- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx +++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx @@ -53,6 +53,7 @@ export interface Room { export interface PriceDetailsTableProps { bookingCode?: string fromDate: string + isCampaignRate?: boolean rooms: Room[] toDate: string totalPrice: Price @@ -62,6 +63,7 @@ export interface PriceDetailsTableProps { export default function PriceDetailsTable({ bookingCode, fromDate, + isCampaignRate, rooms, toDate, totalPrice, @@ -83,6 +85,8 @@ export default function PriceDetailsTable({ const allRoomsPackages: Package[] = rooms .flatMap((r) => r.packages) .filter((r): r is Package => !!r) + + const isAllBreakfastIncluded = rooms.every((room) => room.breakfastIncluded) return ( {rooms.map((room, idx) => { @@ -207,7 +211,11 @@ export default function PriceDetailsTable({ regularPrice={totalPrice.local.regularPrice} /> - +
) diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/bookingCodeFilter.module.css b/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/bookingCodeFilter.module.css index 0dd8e71b7..199f3684d 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/bookingCodeFilter.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/bookingCodeFilter.module.css @@ -4,12 +4,128 @@ width: 100%; } -.bookingCodeFilterSelect { - min-width: 200px; +.dialog { + border-radius: var(--Corner-radius-Medium); + background-color: var(--Surface-Primary-Default); + box-shadow: var(--popup-box-shadow); + max-width: 340px; +} + +.radioGroup { + display: grid; + gap: var(--Space-x1); + padding: 0; +} + +.radio { + padding: var(--Space-x1); +} + +.radio[data-hovered] { + cursor: pointer; +} +.radio[data-focus-visible]::before { + outline: 1px auto var(--Border-Interactive-Focus); +} + +.radio { + display: flex; + align-items: center; +} + +.radio::before { + flex-shrink: 0; + content: ""; + margin-right: var(--Space-x15); + background-color: var(--Surface-UI-Fill-Default); + width: 24px; + height: 24px; + border-radius: 50%; + box-shadow: inset 0 0 0 2px var(--Base-Border-Normal); +} + +.radio[data-selected]::before { + box-shadow: inset 0 0 0 8px var(--Surface-UI-Fill-Active); } @media screen and (max-width: 767px) { .bookingCodeFilter { - margin-bottom: var(--Spacing-x3); + margin-bottom: var(--Space-x3); + } +} + +.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); + padding: 0 var(--Space-x1); +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 var(--Space-x1); +} + +@media screen and (min-width: 768px) { + .radioGroup { + padding: var(--Space-x1); + } + + .modalOverlay { + 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/SelectHotel/BookingCodeFilter/index.tsx b/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/index.tsx index fe2e1cc58..d966b5d3d 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectHotel/BookingCodeFilter/index.tsx @@ -1,25 +1,37 @@ "use client" +import { useState } from "react" +import { + Dialog, + DialogTrigger, + Modal, + ModalOverlay, + Popover, + Radio, + RadioGroup, +} from "react-aria-components" import { useIntl } from "react-intl" +import { useMediaQuery } from "usehooks-ts" +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 { useBookingCodeFilterStore } from "@/stores/bookingCode-filter" -import Select from "@/components/TempDesignSystem/Select" - import styles from "./bookingCodeFilter.module.css" -import type { Key } from "react" - import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter" export default function BookingCodeFilter() { const intl = useIntl() + const [isOpen, setIsOpen] = useState(false) const activeCodeFilter = useBookingCodeFilterStore( (state) => state.activeCodeFilter ) const setFilter = useBookingCodeFilterStore((state) => state.setFilter) + const displayAsPopover = useMediaQuery("(min-width: 768px)") const bookingCodeFilterItems = [ { @@ -30,38 +42,130 @@ export default function BookingCodeFilter() { }, { label: intl.formatMessage({ - defaultMessage: "Full price rooms", - }), - value: BookingCodeFilterEnum.Regular, - }, - { - label: intl.formatMessage({ - defaultMessage: "See all", + defaultMessage: "All rates", }), value: BookingCodeFilterEnum.All, }, ] - function updateFilter(selectedFilter: Key) { + function updateFilter(selectedFilter: string) { setFilter(selectedFilter as BookingCodeFilterEnum) } return ( - <> -
- -
+ <> +
+ + + { + bookingCodeFilterItems.find( + (item) => item.value === selectedFilter + )?.label + } + + + {displayAsPopover ? ( + + + {({ close }) => { + function handleChangeFilterValue(value: string) { + updateFilterValue(value) + close() + } + return ( + + + {bookingCodeFilterItems.map((item) => ( + + {item.label} + + ))} + + + ) + }} + + + ) : ( + + + + {({ close }) => { + function handleChangeFilterValue(value: string) { + updateFilterValue(value) + close() + } + return ( + <> +
+ +

+ {intl.formatMessage({ + defaultMessage: "Room rates", + })} +

+
+ { + close() + }} + > + + +
+ + + {bookingCodeFilterItems.map((item) => ( + + {item.label} + + ))} + + + + ) + }} +
+
+
+ )} +
+
+ ) } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css index 455f18593..07b5efc61 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/roomsHeader.module.css @@ -11,7 +11,7 @@ .filters { display: flex; gap: var(--Space-x1); - align-items: center; + align-items: flex-start; } @media screen and (min-width: 768px) { 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 028e55c49..7d17bdda5 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 @@ -52,10 +52,6 @@ export default function Campaign({ campaign = campaign.filter((product) => product.bookingCode) } - if (selectedFilter === BookingCodeFilterEnum.Regular) { - campaign = campaign.filter((product) => !product.bookingCode) - } - const pkgsSum = sumPackages(selectedPackages) const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages) 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 1a3c50aa4..411bea895 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 @@ -21,7 +21,6 @@ import { import { calculatePricePerNightPriceProduct } from "./totalPricePerNight" import type { SharedRateCardProps } from "@/types/components/hotelReservation/selectRate/rates" -import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter" import type { CodeProduct } from "@/types/trpc/routers/hotel/roomAvailability" interface CodeProps extends SharedRateCardProps { @@ -35,8 +34,7 @@ export default function Code({ roomTypeCode, }: CodeProps) { const intl = useIntl() - const { roomNr, selectedFilter, selectedPackages, selectedRate } = - useRoomContext() + const { roomNr, selectedPackages, selectedRate } = useRoomContext() const bookingCode = useRatesStore((state) => state.booking.bookingCode) const rateTitles = useRateTitles() const night = intl @@ -45,10 +43,6 @@ export default function Code({ }) .toUpperCase() - if (selectedFilter === BookingCodeFilterEnum.Regular) { - return null - } - return code.map((product) => { let bannerText = "" if (product.rateDefinition.breakfastIncluded) { @@ -155,15 +149,16 @@ export default function Code({ pkgsSumRequested.price ) - const approximateRate = pricePerNight.totalRequestedPrice - ? { - label: intl.formatMessage({ - defaultMessage: "Approx.", - }), - price: pricePerNight.totalRequestedPrice, - unit: localPrice.currency, - } - : undefined + const approximateRate = + pricePerNight.totalRequestedPrice && requestedPrice?.currency + ? { + label: intl.formatMessage({ + defaultMessage: "Approx.", + }), + price: pricePerNight.totalRequestedPrice, + unit: requestedPrice.currency, + } + : undefined const regularPricePerNight = calculatePricePerNightPriceProduct( localPrice.regularPricePerNight, 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 abad0e890..f550cbd40 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 @@ -26,7 +26,6 @@ export default function Redemptions({ if ( selectedFilter === BookingCodeFilterEnum.Discounted || - selectedFilter === BookingCodeFilterEnum.Regular || !redemptions.length ) { return null 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 6b25946b7..d69cfbfb2 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 @@ -53,21 +53,14 @@ export default function Rates({ roomTypeCode, } const showAllRates = selectedFilter === BookingCodeFilterEnum.All - const showRegularRates = selectedFilter === BookingCodeFilterEnum.Regular const hasBookingCodeRates = !!(campaign.length || code.length) const hasRegularRates = !!regular.length - const notDiscountedPrice = showAllRates || showRegularRates - const notDiscountedAndHasRates = - notDiscountedPrice && hasBookingCodeRates && hasRegularRates - const isFetchingAndOnlyShowingRegularRates = - isFetchingAdditionalRate && !showRegularRates - const showDivider = - notDiscountedAndHasRates || isFetchingAndOnlyShowingRegularRates + const showDivider = showAllRates && hasBookingCodeRates && hasRegularRates return ( <> - + {showDivider ? : null} {isFetchingAdditionalRate ? ( diff --git a/apps/scandic-web/components/TempDesignSystem/IconChip/index.tsx b/apps/scandic-web/components/TempDesignSystem/IconChip/index.tsx index 561cb00a1..9d33d7db0 100644 --- a/apps/scandic-web/components/TempDesignSystem/IconChip/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/IconChip/index.tsx @@ -4,11 +4,18 @@ interface IconChipProps { color: "blue" | "green" | "red" | null | undefined icon: React.ReactNode children: React.ReactNode + className?: string } -export default function IconChip({ color, icon, children }: IconChipProps) { +export default function IconChip({ + color, + icon, + className, + children, +}: IconChipProps) { const classNames = iconChipVariants({ color: color, + className: className, }) return (
diff --git a/apps/scandic-web/server/routers/hotels/output.ts b/apps/scandic-web/server/routers/hotels/output.ts index 4f3df6c62..bc466d5f2 100644 --- a/apps/scandic-web/server/routers/hotels/output.ts +++ b/apps/scandic-web/server/routers/hotels/output.ts @@ -95,14 +95,25 @@ export const hotelSchema = z export const hotelsAvailabilitySchema = z.object({ data: z.array( z.object({ - attributes: z.object({ - checkInDate: z.string(), - checkOutDate: z.string(), - hotelId: z.number(), - occupancy: occupancySchema, - productType: productTypeSchema, - status: z.string(), - }), + attributes: z + .object({ + bookingCode: z.string().nullish(), + checkInDate: z.string(), + checkOutDate: z.string(), + hotelId: z.number(), + occupancy: occupancySchema, + productType: productTypeSchema, + status: z.string(), + }) + .transform((data) => { + if (data.bookingCode && data.productType?.public) { + data.productType.public.bookingCode = data.bookingCode + } + if (data.bookingCode && data.productType?.member) { + data.productType.member.bookingCode = data.bookingCode + } + return data + }), relationships: relationshipsSchema.optional(), type: z.string().optional(), }) diff --git a/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts b/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts index 98e3de49f..74c5c3065 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts @@ -46,6 +46,7 @@ const partialPriceSchema = z.object({ } return RateTypeEnum.Regular }), + bookingCode: z.string().nullish(), }) export const productTypeCorporateChequeSchema = z diff --git a/apps/scandic-web/stores/select-rate/index.ts b/apps/scandic-web/stores/select-rate/index.ts index 473da4451..e905dcec3 100644 --- a/apps/scandic-web/stores/select-rate/index.ts +++ b/apps/scandic-web/stores/select-rate/index.ts @@ -152,17 +152,13 @@ export function createRatesStore({ room.counterRateCode ) } - let selectedFilter const bookingCode = room.rateCode ? room.bookingCode : booking.bookingCode - if (isRedemptionBooking) { - selectedFilter = BookingCodeFilterEnum.All - } else if (bookingCode) { - selectedFilter = BookingCodeFilterEnum.Discounted - } else { - selectedFilter = BookingCodeFilterEnum.Regular - } + const selectedFilter = + bookingCode && !isRedemptionBooking + ? BookingCodeFilterEnum.Discounted + : BookingCodeFilterEnum.All return { actions: { @@ -301,7 +297,8 @@ export function createRatesStore({ return set( produce((state: RatesState) => { state.rooms[idx].selectedFilter = filter - state.rooms[idx].isFetchingAdditionalRate = true + state.rooms[idx].isFetchingAdditionalRate = + filter === BookingCodeFilterEnum.All }) ) }, diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts index 5cbbc3fe1..fe3d45677 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts @@ -29,6 +29,7 @@ type ImageSizes = z.infer["imageSizes"] type ImageMetaData = z.infer["metaData"] export type HotelPin = { + bookingCode?: string | null name: string coordinates: Coordinates publicPrice: number | null diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts index 4dde6c58d..5fd9fce1a 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts @@ -2,7 +2,9 @@ import type { Hotel } from "@/types/hotel" export type NoAvailabilityAlertProp = { hotelsLength: number + bookingCode?: string isAllUnavailable: boolean isAlternative?: boolean + isBookingCodeRateNotAvailable?: boolean operaId: Hotel["operaId"] } diff --git a/apps/scandic-web/types/enums/bookingCodeFilter.ts b/apps/scandic-web/types/enums/bookingCodeFilter.ts index d0d875429..e706d2c27 100644 --- a/apps/scandic-web/types/enums/bookingCodeFilter.ts +++ b/apps/scandic-web/types/enums/bookingCodeFilter.ts @@ -1,5 +1,4 @@ export enum BookingCodeFilterEnum { Discounted = "Discounted", - Regular = "Regular", All = "All", } diff --git a/packages/design-system/lib/components/RateCard/Campaign/index.tsx b/packages/design-system/lib/components/RateCard/Campaign/index.tsx index 6e7a97ea4..95e48ad79 100644 --- a/packages/design-system/lib/components/RateCard/Campaign/index.tsx +++ b/packages/design-system/lib/components/RateCard/Campaign/index.tsx @@ -56,7 +56,7 @@ export default function CampaignRateCard({ onChange={handleChange} />
- +

{bannerText}

diff --git a/packages/design-system/lib/components/RateCard/Code/index.tsx b/packages/design-system/lib/components/RateCard/Code/index.tsx index 8254e060a..0ec9e0c45 100644 --- a/packages/design-system/lib/components/RateCard/Code/index.tsx +++ b/packages/design-system/lib/components/RateCard/Code/index.tsx @@ -52,7 +52,7 @@ export default function CodeRateCard({ onChange={handleChange} />
- +

{bannerText}

diff --git a/packages/design-system/lib/components/RateCard/Points/index.tsx b/packages/design-system/lib/components/RateCard/Points/index.tsx index f32a430a7..2829a1348 100644 --- a/packages/design-system/lib/components/RateCard/Points/index.tsx +++ b/packages/design-system/lib/components/RateCard/Points/index.tsx @@ -38,7 +38,7 @@ export default function PointsRateCard({ return (
- +

{bannerText}

diff --git a/packages/design-system/lib/components/RateCard/rate-card.module.css b/packages/design-system/lib/components/RateCard/rate-card.module.css index 6b5a78d4a..1d2d8a741 100644 --- a/packages/design-system/lib/components/RateCard/rate-card.module.css +++ b/packages/design-system/lib/components/RateCard/rate-card.module.css @@ -152,35 +152,15 @@ label:not(:has(.radio:checked)) .checkIcon { align-items: center; gap: var(--Space-x1); } -.variant-campaign { - background-color: var(--Surface-Brand-Primary-1-Default); -} - -.variant-campaign:hover { - background-color: var(--Scandic-Peach-20); -} .variant-campaign .banner { - background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent); -} - -.variant-code { - background-color: var(--Surface-Feedback-Information); -} - -.variant-code:hover { - background-color: var(--Scandic-Blue-10); + background-color: var(--Surface-Accent-3); } .variant-code .banner { background-color: var(--Surface-Feedback-Information-Accent); } -.variant-points:hover { - background-color: var(--Scandic-Grey-00); - cursor: default; -} - .variant-points .banner { background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary); }