fix(BOOK-506): Hiding campaign banner on more hotel-reservation routes and make whole banner clickable

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2025-11-06 14:48:13 +00:00
parent 78ac3b29c3
commit 11d53d0b15
6 changed files with 114 additions and 124 deletions

View File

@@ -1,11 +1,9 @@
"use client" "use client"
import NextLink from "next/link"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { trackClick } from "@scandic-hotels/tracking/base"
import { MarqueeText } from "@/components/MarqueeText" import { MarqueeText } from "@/components/MarqueeText"
@@ -22,7 +20,7 @@ export function DesktopCampaignBanner({
const intl = useIntl() const intl = useIntl()
return ( return (
<div className={styles.innerContent}> <>
<Typography variant="Tag/sm"> <Typography variant="Tag/sm">
<span className={styles.tag}>{tag}</span> <span className={styles.tag}>{tag}</span>
</Typography> </Typography>
@@ -53,22 +51,16 @@ export function DesktopCampaignBanner({
{link ? ( {link ? (
<Typography variant="Link/sm"> <Typography variant="Link/sm">
<NextLink <span className={styles.fakeLink}>
href={link.url} {link.title ||
className={styles.link} intl.formatMessage({
onClick={() => trackClick("BW read more")} id: "common.readMore",
> defaultMessage: "Read more",
<span> })}
{link.title || </span>
intl.formatMessage({
id: "common.readMore",
defaultMessage: "Read more",
})}
</span>
</NextLink>
</Typography> </Typography>
) : null} ) : null}
</MarqueeText> </MarqueeText>
</div> </>
) )
} }

View File

@@ -1,12 +1,10 @@
"use client" "use client"
import { cx } from "class-variance-authority" import { cx } from "class-variance-authority"
import NextLink from "next/link"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { trackClick } from "@scandic-hotels/tracking/base"
import { MarqueeText } from "@/components/MarqueeText" import { MarqueeText } from "@/components/MarqueeText"
@@ -22,91 +20,63 @@ export function MobileCampaignBanner({
}: CampaignBannerProps) { }: CampaignBannerProps) {
const intl = useIntl() const intl = useIntl()
return ( if (bookingCode) {
<InnerContent link={link} bookingCode={bookingCode}> return (
{bookingCode ? ( <p>
<p> <Typography variant="Title/Overline/sm">
<Typography variant="Title/Overline/sm"> <span className={cx(styles.tag, styles.withBookingCode)}>{tag}</span>
<span className={cx(styles.tag, styles.withBookingCode)}> </Typography>
{tag} <Typography variant="Body/Supporting text (caption)/smRegular">
</span> <span>
</Typography> {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<Typography variant="Body/Supporting text (caption)/smRegular"> <> {text} </>
<span> <Typography variant="Body/Supporting text (caption)/smBold">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} <span className={styles.bookingCode}>
<> {text} </> {intl.formatMessage(
<Typography variant="Body/Supporting text (caption)/smBold"> {
<span className={styles.bookingCode}> id: "campaignBanner.codeWithBookingCode",
{intl.formatMessage( defaultMessage: "Code: {bookingCode}",
{ },
id: "campaignBanner.codeWithBookingCode", { bookingCode }
defaultMessage: "Code: {bookingCode}", )}
}, <MaterialIcon
{ bookingCode } icon="arrow_forward"
)} color="CurrentColor"
<MaterialIcon size={16}
icon="arrow_forward" />
color="CurrentColor" </span>
size={16}
/>
</span>
</Typography>
</span>
</Typography>
</p>
) : (
<>
<Typography variant="Tag/sm">
<span className={styles.tag}>{tag}</span>
</Typography>
<MarqueeText
backgroundColor="var(--Surface-Brand-Primary-3-Default)"
className={styles.marquee}
textWrapperClassName={styles.marqueeText}
>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>{text}</span>
</Typography> </Typography>
{link ? ( </span>
<Typography variant="Link/sm"> </Typography>
<span> </p>
{link.title || )
intl.formatMessage({ }
id: "common.readMore",
defaultMessage: "Read more",
})}
</span>
</Typography>
) : null}
</MarqueeText>
</>
)}
</InnerContent>
)
}
function InnerContent({ return (
link, <>
bookingCode, <Typography variant="Tag/sm">
children, <span className={styles.tag}>{tag}</span>
}: React.PropsWithChildren<Pick<CampaignBannerProps, "link" | "bookingCode">>) { </Typography>
return link ? ( <MarqueeText
<NextLink backgroundColor="var(--Surface-Brand-Primary-3-Default)"
href={link.url} className={styles.marquee}
className={cx(styles.innerContent, { textWrapperClassName={styles.marqueeText}
[styles.withBookingCode]: !!bookingCode, >
})} <Typography variant="Body/Supporting text (caption)/smRegular">
onClick={() => trackClick("BW campaign banner")} <span>{text}</span>
> </Typography>
{children} {link ? (
</NextLink> <Typography variant="Link/sm">
) : ( <span className={styles.fakeLink}>
<div {link.title ||
className={cx(styles.innerContent, { intl.formatMessage({
[styles.withBookingCode]: !!bookingCode, id: "common.readMore",
})} defaultMessage: "Read more",
> })}
{children} </span>
</div> </Typography>
) : null}
</MarqueeText>
</>
) )
} }

View File

@@ -22,10 +22,7 @@
text-decoration: none; text-decoration: none;
color: var(--Text-Inverted); color: var(--Text-Inverted);
padding: var(--Space-x025) 0; padding: var(--Space-x025) 0;
gap: var(--Space-x15);
&:not(.withBookingCode) {
gap: var(--Space-x15);
}
} }
.text { .text {
@@ -61,10 +58,14 @@
z-index: 1; z-index: 1;
} }
.link { .fakeLink {
color: var(--Text-Inverted); color: var(--Text-Inverted);
} }
.innerContent:hover .fakeLink {
opacity: 0.7;
}
.bookingCode { .bookingCode {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@@ -7,6 +7,9 @@ const SHARED_HIDE_ROUTES = [
"/hotelreservation/details", "/hotelreservation/details",
"/hotelreservation/booking-confirmation", "/hotelreservation/booking-confirmation",
"/hotelreservation/my-stay", "/hotelreservation/my-stay",
"/hotelreservation/gla-payment-callback",
"/hotelreservation/payment-callback",
"/hotelreservation/get-booking",
] ]
export const CAMPAIGN_BANNER_HIDE_CONDITIONS = { export const CAMPAIGN_BANNER_HIDE_CONDITIONS = {

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import NextLink from "next/link"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { useCallback, useEffect, useRef, useState } from "react" import { useCallback, useEffect, useRef, useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -19,6 +20,8 @@ import { shouldShowCampaignBanner } from "./utils"
import styles from "./campaignBanner.module.css" import styles from "./campaignBanner.module.css"
import type { CampaignBannerProps } from "@/components/CampaignBanner/types"
export default function CampaignBanner() { export default function CampaignBanner() {
const lang = useLang() const lang = useLang()
const intl = useIntl() const intl = useIntl()
@@ -97,21 +100,23 @@ export default function CampaignBanner() {
}} }}
> >
<div className={styles.content}> <div className={styles.content}>
{isMobile ? ( <InnerContent link={isOnSamePage ? null : campaignBanner.link}>
<MobileCampaignBanner {isMobile ? (
tag={campaignBanner.tag} <MobileCampaignBanner
text={campaignBanner.text} tag={campaignBanner.tag}
link={isOnSamePage ? null : campaignBanner.link} text={campaignBanner.text}
bookingCode={campaignBanner.booking_code} link={isOnSamePage ? null : campaignBanner.link}
/> bookingCode={campaignBanner.booking_code}
) : ( />
<DesktopCampaignBanner ) : (
tag={campaignBanner.tag} <DesktopCampaignBanner
text={campaignBanner.text} tag={campaignBanner.tag}
link={isOnSamePage ? null : campaignBanner.link} text={campaignBanner.text}
bookingCode={campaignBanner.booking_code} link={isOnSamePage ? null : campaignBanner.link}
/> bookingCode={campaignBanner.booking_code}
)} />
)}
</InnerContent>
<IconButton <IconButton
className={styles.closeButton} className={styles.closeButton}
theme="Inverted" theme="Inverted"
@@ -128,3 +133,22 @@ export default function CampaignBanner() {
</div> </div>
) )
} }
function InnerContent({
link,
children,
}: React.PropsWithChildren<{
link: CampaignBannerProps["link"]
}>) {
return link ? (
<NextLink
href={link.url}
className={styles.innerContent}
onClick={() => trackClick("BW campaign banner")}
>
{children}
</NextLink>
) : (
<div className={styles.innerContent}>{children}</div>
)
}

View File

@@ -22,7 +22,7 @@ export function shouldShowCampaignBanner(
const fullRoute = removeTrailingSlash( const fullRoute = removeTrailingSlash(
removeMultipleSlashes(`/${lang}${route}`) removeMultipleSlashes(`/${lang}${route}`)
) )
return cleanPathname === fullRoute return cleanPathname.startsWith(fullRoute)
} }
) )