Files
web/apps/scandic-web/components/Blocks/CampaignHotelListing/Client.tsx
Erik Tiekstra 270249c6c4 feat(SW-2973): Added bookingCode if available to links inside campaign pages
* feat(SW-2973): Moved block types to trpc lib

Approved-by: Matilda Landström
2025-07-04 10:15:01 +00:00

159 lines
4.9 KiB
TypeScript

"use client"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { Button } from "@scandic-hotels/design-system/Button"
import {
MaterialIcon,
type MaterialIconProps,
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useHotelListingDataStore } from "@/stores/hotel-listing-data"
import CampaignHotelListingSkeleton from "@/components/Blocks/CampaignHotelListing/CampaignHotelListingSkeleton"
import HotelFilterAndSort from "@/components/HotelFilterAndSort"
import HotelListingItem from "./HotelListingItem"
import styles from "./campaignHotelListing.module.css"
interface CampaignHotelListingClientProps {
heading: string
preamble?: string | null
bookingCode?: string | null
visibleCountMobile: 3 | 6
visibleCountDesktop: 3 | 6
isMainBlock: boolean
}
export default function CampaignHotelListingClient({
heading,
preamble,
visibleCountMobile,
visibleCountDesktop,
isMainBlock,
bookingCode,
}: CampaignHotelListingClientProps) {
const intl = useIntl()
const isMobile = useMediaQuery("(max-width: 767px)")
const scrollRef = useRef<HTMLElement>(null)
const { activeHotels, isLoading } = useHotelListingDataStore((state) => ({
activeHotels: state.activeHotels,
isLoading: state.isLoading,
}))
const initialCount = isMobile ? visibleCountMobile : visibleCountDesktop // Initial number of activeHotels to show
const thresholdCount = initialCount + 3 // This is the threshold at which we start showing the "Show More" button
const showAllThreshold = initialCount * 3 // This is the threshold at which we show the "Show All" button
const incrementCount = initialCount // Number of activeHotels to increment when the button is clicked
const [visibleCount, setVisibleCount] = useState(() =>
// Set initial visible count based on the number of activeHotels and the threshold
activeHotels.length <= thresholdCount ? activeHotels.length : initialCount
)
// Only show the show more/less button if the length of activeHotels exceeds the threshold count
const showButton = activeHotels.length > thresholdCount
// Determine if we are at the stage where the user can click to show all activeHotels
const canShowAll =
activeHotels.length > visibleCount &&
(visibleCount + incrementCount > showAllThreshold ||
visibleCount + incrementCount >= activeHotels.length)
function handleButtonClick() {
if (visibleCount < activeHotels.length) {
if (canShowAll) {
setVisibleCount(activeHotels.length)
} else {
setVisibleCount((prev) =>
Math.min(prev + incrementCount, activeHotels.length)
)
}
} else {
setVisibleCount(initialCount)
if (scrollRef.current) {
scrollRef.current.scrollIntoView({ behavior: "smooth" })
}
}
}
let buttonText = intl.formatMessage({
defaultMessage: "Show more",
})
let iconDirection: MaterialIconProps["icon"] = "keyboard_arrow_down"
if (visibleCount === activeHotels.length) {
buttonText = intl.formatMessage({
defaultMessage: "Show less",
})
iconDirection = "keyboard_arrow_up"
} else if (canShowAll) {
buttonText = intl.formatMessage({
defaultMessage: "Show all",
})
}
if (isLoading) {
return <CampaignHotelListingSkeleton />
}
return (
<section
className={cx(styles.hotelListingSection, {
[styles.isMainBlock]: isMainBlock,
})}
ref={scrollRef}
>
<header className={styles.header}>
<Typography variant={isMainBlock ? "Title/md" : "Title/Subtitle/lg"}>
<h3 className={styles.heading}>{heading}</h3>
</Typography>
{isMainBlock ? <HotelFilterAndSort /> : null}
{preamble ? (
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.preamble}>{preamble}</p>
</Typography>
) : null}
</header>
<ul className={styles.list}>
{activeHotels.map(({ hotel, url }, index) => {
const urlWithOptionalBookingCode = bookingCode
? `${url}?bookingCode=${bookingCode}`
: url
return (
<li
key={hotel.id}
className={cx(styles.listItem, {
[styles.hidden]: index >= visibleCount,
})}
>
<HotelListingItem
hotel={hotel}
url={urlWithOptionalBookingCode}
/>
</li>
)
})}
</ul>
{showButton ? (
<Button
variant="Text"
color="Primary"
size="Medium"
typography="Body/Paragraph/mdBold"
onPress={handleButtonClick}
>
<MaterialIcon icon={iconDirection} color="CurrentColor" />
{buttonText}
</Button>
) : null}
</section>
)
}