From ed833e714be96ba28660dffe44520d17c749c776 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Wed, 29 Oct 2025 14:10:12 +0000 Subject: [PATCH] Merged in feat/BOOK-426-add-campaign-tag-select-hotel (pull request #3023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feat/BOOK-426 add campaign tag select hotel * feat(BOOK-426): introduce campaign tag on select hotel card * feat(BOOK-426): remove redundant tags * feat(BOOK-426): fix comments, change to typography * feat(BOOK-426): fix comments, update to cx Approved-by: Erik Tiekstra Approved-by: Matilda Landström --- .../lib/components/HotelCardListing/index.tsx | 14 ++ .../lib/components/BookingCodeChip/index.tsx | 15 +- .../HotelPriceCard/hotelPriceCard.module.css | 10 ++ .../HotelCard/HotelPriceCard/index.tsx | 169 +++++++++++------- .../lib/components/HotelCard/index.tsx | 14 +- 5 files changed, 153 insertions(+), 69 deletions(-) diff --git a/packages/booking-flow/lib/components/HotelCardListing/index.tsx b/packages/booking-flow/lib/components/HotelCardListing/index.tsx index 036c2097b..40f3ed82b 100644 --- a/packages/booking-flow/lib/components/HotelCardListing/index.tsx +++ b/packages/booking-flow/lib/components/HotelCardListing/index.tsx @@ -4,6 +4,7 @@ import { useRouter, useSearchParams } from "next/navigation" import { useEffect, useMemo, useRef } from "react" import { useIntl } from "react-intl" +import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { alternativeHotelsMap, selectHotelMap, @@ -82,6 +83,18 @@ export default function HotelCardListing({ isBookingCodeRateAvailable && activeCodeFilter === BookingCodeFilterEnum.Discounted + const isCampaignWithBookingCode = + !!bookingCode && + hotelData + .filter((hotel) => hotel.availability.bookingCode) + .every( + (hotel) => + hotel.availability.productType?.public?.rateType === + RateTypeEnum.PublicPromotion || + hotel.availability.productType?.member?.rateType === + RateTypeEnum.PublicPromotion + ) + const unfilteredHotelCount = showOnlyBookingCodeRates ? hotelData.filter((hotel) => hotel.availability.bookingCode).length : hotelData.length @@ -233,6 +246,7 @@ export default function HotelCardListing({ } type={type} bookingCode={bookingCode} + isCampaignWithBookingCode={isCampaignWithBookingCode} isAlternative={isAlternative} /> diff --git a/packages/design-system/lib/components/BookingCodeChip/index.tsx b/packages/design-system/lib/components/BookingCodeChip/index.tsx index a1e4a919b..4c83a9733 100644 --- a/packages/design-system/lib/components/BookingCodeChip/index.tsx +++ b/packages/design-system/lib/components/BookingCodeChip/index.tsx @@ -7,6 +7,7 @@ import { MaterialIcon } from '../Icons/MaterialIcon' import { Typography } from '../Typography' import styles from './bookingCodeChip.module.css' +import { cx } from 'class-variance-authority' import { IconButton } from '../IconButton' type BaseBookingCodeChipProps = { @@ -14,6 +15,7 @@ type BaseBookingCodeChipProps = { bookingCode?: string | null isCampaign?: boolean isUnavailable?: boolean + isCampaignUnavailable?: boolean withText?: boolean filledIcon?: boolean } @@ -34,6 +36,7 @@ export function BookingCodeChip({ alignCenter, bookingCode, isCampaign, + isCampaignUnavailable, isUnavailable, withText = true, filledIcon = false, @@ -42,7 +45,7 @@ export function BookingCodeChip({ }: BookingCodeChipProps) { const intl = useIntl() - if (isCampaign) { + if (isCampaign || isCampaignUnavailable) { return ( -

+

{intl.formatMessage({ @@ -115,7 +122,9 @@ export function BookingCodeChip({ className={alignCenter ? styles.center : undefined} >

{withText && ( diff --git a/packages/design-system/lib/components/HotelCard/HotelPriceCard/hotelPriceCard.module.css b/packages/design-system/lib/components/HotelCard/HotelPriceCard/hotelPriceCard.module.css index 2ba0f3ca4..5016f19f2 100644 --- a/packages/design-system/lib/components/HotelCard/HotelPriceCard/hotelPriceCard.module.css +++ b/packages/design-system/lib/components/HotelCard/HotelPriceCard/hotelPriceCard.module.css @@ -16,6 +16,16 @@ padding: var(--Spacing-x-quarter) 0; } +.redColor { + color: var(--Text-Accent-Primary); +} +.defaultColor { + color: var(--Text-Default); +} +.secondaryColor { + color: var(--Text-Secondary); +} + .price { display: flex; gap: var(--Spacing-x-half); diff --git a/packages/design-system/lib/components/HotelCard/HotelPriceCard/index.tsx b/packages/design-system/lib/components/HotelCard/HotelPriceCard/index.tsx index fe9b17579..1aa53911c 100644 --- a/packages/design-system/lib/components/HotelCard/HotelPriceCard/index.tsx +++ b/packages/design-system/lib/components/HotelCard/HotelPriceCard/index.tsx @@ -1,12 +1,9 @@ import { cx } from 'class-variance-authority' import { useIntl } from 'react-intl' -import Body from '../../Body' -import Caption from '../../Caption' import { Divider } from '../../Divider' -import Subtitle from '../../Subtitle' import { RateTypeEnum } from '@scandic-hotels/common/constants/rateType' - +import { Typography } from '../../Typography' import styles from './hotelPriceCard.module.css' type Price = { @@ -23,12 +20,14 @@ export type PriceCardProps = { } isMemberPrice?: boolean className?: string + isCampaign?: boolean } export function HotelPriceCard({ productTypePrices, isMemberPrice = false, className, + isCampaign = false, }: PriceCardProps) { const intl = useIntl() const isRegularOrPublicPromotionRate = @@ -41,79 +40,111 @@ export function HotelPriceCard({ (isMemberPrice ? (

- - {intl.formatMessage({ - id: 'booking.memberPrice', - defaultMessage: 'Member price', - })} - + +

+ {intl.formatMessage({ + id: 'booking.memberPrice', + defaultMessage: 'Member price', + })} +

+
) : (
- - {intl.formatMessage({ - id: 'booking.standardPrice', - defaultMessage: 'Standard price', - })} - + +

+ {intl.formatMessage({ + id: 'booking.standardPrice', + defaultMessage: 'Standard price', + })} +

+
))}
- - {intl.formatMessage({ - id: 'common.from', - defaultMessage: 'From', - })} - +

+ {intl.formatMessage({ + id: 'common.from', + defaultMessage: 'From', + })} +

+
- - {productTypePrices.localPrice.pricePerNight} - - {productTypePrices.localPrice.pricePerNight}

+ + - {productTypePrices.localPrice.currency} - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - - / - {intl.formatMessage({ - id: 'common.night', - defaultMessage: 'night', - })} - - +

+ {productTypePrices.localPrice.currency} + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + + / + {intl.formatMessage({ + id: 'common.night', + defaultMessage: 'night', + })} + +

+
{productTypePrices?.requestedPrice && (
- - {intl.formatMessage({ - id: 'booking.approx', - defaultMessage: 'Approx.', - })} - + +

+ {intl.formatMessage({ + id: 'booking.approx', + defaultMessage: 'Approx.', + })} +

+
- - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - {`${productTypePrices.requestedPrice.pricePerNight} `} - {productTypePrices.requestedPrice.currency} - + +

+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + {`${productTypePrices.requestedPrice.pricePerNight} `} + {productTypePrices.requestedPrice.currency} +

+
)} @@ -125,19 +156,29 @@ export function HotelPriceCard({
- - {intl.formatMessage({ - id: 'common.total', - defaultMessage: 'Total', - })} - + +

+ {intl.formatMessage({ + id: 'common.total', + defaultMessage: 'Total', + })} +

+
- - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - {`${productTypePrices.localPrice.pricePerStay} `} - {productTypePrices.localPrice.currency} - + +

+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + {`${productTypePrices.localPrice.pricePerStay} `} + {productTypePrices.localPrice.currency} +

+
diff --git a/packages/design-system/lib/components/HotelCard/index.tsx b/packages/design-system/lib/components/HotelCard/index.tsx index 1a20700d8..c2c2af3ef 100644 --- a/packages/design-system/lib/components/HotelCard/index.tsx +++ b/packages/design-system/lib/components/HotelCard/index.tsx @@ -108,7 +108,7 @@ export type HotelCardProps = { isAlternative?: boolean pointsCurrency?: CurrencyEnum fullPrice: boolean - + isCampaignWithBookingCode: boolean lang: Lang belowInfoSlot: React.ReactNode @@ -133,6 +133,7 @@ export const HotelCard = memo( lang, belowInfoSlot, fullPrice, + isCampaignWithBookingCode, onAddressClick, onHover, onHoverEnd, @@ -166,6 +167,10 @@ export const HotelCard = memo( }) const isDisabled = prices?.redemptions?.length && hasInsufficientPoints + const isCampaign = + prices?.public?.rateType === RateTypeEnum.PublicPromotion || + prices?.member?.rateType === RateTypeEnum.PublicPromotion + const showBookingCodeChip = bookingCode || isCampaign return (
) : ( <> - {bookingCode && ( + {showBookingCodeChip && ( )} {(!isUserLoggedIn || @@ -280,6 +289,7 @@ export const HotelCard = memo( )} {prices.member && (