Files
web/apps/scandic-web/components/CampaignBanner/index.tsx
Erik Tiekstra 4ec1e85d84 Feat/BOOK-293 button adjustments
* feat(BOOK-293): Adjusted padding of the buttons to match Figma design
* feat(BOOK-293): Updated variants for IconButton
* feat(BOOK-113): Updated focus indicators on buttons and added default focus ring color
* feat(BOOK-293): Replaced buttons inside booking widget

Approved-by: Christel Westerberg
2025-12-15 07:05:31 +00:00

154 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 { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
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("BW 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}>
{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",
})}
>
<MaterialIcon color="CurrentColor" icon="close" />
</IconButton>
</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>
)
}