Files
web/apps/scandic-web/components/CampaignBanner/index.tsx
Bianca Widstam 86f9fb13b6 Merged in fix/BOOK-704-tracking-campaign (pull request #3400)
fix(BOOK-704): add tag name to campaign banner tracking

* fix(BOOK-704): add tag name to campaign banner tracking

* fix(BOOK-704): handleclose tag name


Approved-by: Erik Tiekstra
Approved-by: Matilda Landström
2026-01-08 11:18:13 +00:00

157 lines
4.3 KiB
TypeScript

"use client"
import NextLink from "next/link"
import { usePathname } from "next/navigation"
import { useCallback, useEffect, useRef, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { debounce } from "@scandic-hotels/common/utils/debounce"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { trackClick } from "@scandic-hotels/tracking/base"
import { trpc } from "@scandic-hotels/trpc/client"
import useLang from "@/hooks/useLang"
import { DesktopCampaignBanner } from "./Desktop"
import { MobileCampaignBanner } from "./Mobile"
import { shouldShowCampaignBanner } from "./utils"
import styles from "./campaignBanner.module.css"
import type { CampaignBannerProps } from "@/components/CampaignBanner/types"
export default function CampaignBanner() {
const lang = useLang()
const intl = useIntl()
const pathname = usePathname()
const campaignBannerRef = useRef<HTMLDivElement>(null)
const isMobile = useMediaQuery("(max-width: 767px)")
const [closedPaths, setClosedPaths] = useState<Set<string>>(new Set())
const [
{ data: siteConfig, isLoading: siteConfigLoading },
{ data: campaignBanner, isLoading: campaignBannerLoading },
] = trpc.useQueries((t) => [
t.contentstack.base.siteConfig({ lang }, { refetchInterval: 60_000 }),
t.contentstack.base.sitewideCampaignBanner.get(
{ lang },
{ refetchInterval: 360_000 }
),
])
const isOnSamePage = pathname === campaignBanner?.link?.url
const sitewideAlertType = siteConfig?.sitewideAlert?.type || null
const shouldShowBanner = shouldShowCampaignBanner(
pathname,
lang,
closedPaths,
sitewideAlertType
)
const isVisible =
!siteConfigLoading &&
!campaignBannerLoading &&
!!campaignBanner &&
shouldShowBanner
const updateHeightRefCallback = useCallback((node: HTMLDivElement | null) => {
if (node) {
const debouncedUpdate = debounce(([entry]) => {
const height = entry.contentRect.height
document.documentElement.style.setProperty(
"--campaign-banner-height",
`${height}px`
)
}, 100)
const observer = new ResizeObserver(debouncedUpdate)
observer.observe(node)
return () => {
if (node) {
observer.unobserve(node)
}
observer.disconnect()
}
}
}, [])
useEffect(() => {
if (!isVisible) {
document.documentElement.style.removeProperty("--campaign-banner-height")
}
}, [isVisible])
if (!isVisible) {
return null
}
function handleClose() {
trackClick(`${campaignBanner?.tag} close`)
setClosedPaths((prev) => new Set(prev).add(pathname))
}
return (
<div
className={styles.campaignBanner}
ref={(node) => {
campaignBannerRef.current = node
return updateHeightRefCallback(node)
}}
>
<div className={styles.content}>
<InnerContent
link={isOnSamePage ? null : campaignBanner.link}
tag={campaignBanner.tag}
>
{isMobile ? (
<MobileCampaignBanner
tag={campaignBanner.tag}
text={campaignBanner.text}
link={isOnSamePage ? null : campaignBanner.link}
bookingCode={campaignBanner.booking_code}
/>
) : (
<DesktopCampaignBanner
tag={campaignBanner.tag}
text={campaignBanner.text}
link={isOnSamePage ? null : campaignBanner.link}
bookingCode={campaignBanner.booking_code}
/>
)}
</InnerContent>
<IconButton
className={styles.closeButton}
variant="Muted"
onPress={handleClose}
aria-label={intl.formatMessage({
id: "campaignBanner.dismissBanner",
defaultMessage: "Dismiss banner",
})}
iconName="close"
/>
</div>
</div>
)
}
function InnerContent({
link,
children,
tag,
}: React.PropsWithChildren<{
link: CampaignBannerProps["link"]
tag: string
}>) {
return link ? (
<NextLink
href={link.url}
className={styles.innerContent}
onClick={() => trackClick(`${tag} campaign banner`)}
>
{children}
</NextLink>
) : (
<div className={styles.innerContent}>{children}</div>
)
}