Feat/lokalise rebuild * chore(lokalise): update translation ids * chore(lokalise): easier to switch between projects * chore(lokalise): update translation ids * . * . * . * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * chore(lokalise): new translations * merge * switch to errors for missing id's * merge * sync translations Approved-by: Linus Flood
162 lines
5.0 KiB
TypeScript
162 lines
5.0 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({
|
|
id: "common.showMore",
|
|
defaultMessage: "Show more",
|
|
})
|
|
let iconDirection: MaterialIconProps["icon"] = "keyboard_arrow_down"
|
|
|
|
if (visibleCount === activeHotels.length) {
|
|
buttonText = intl.formatMessage({
|
|
id: "common.showLess",
|
|
defaultMessage: "Show less",
|
|
})
|
|
iconDirection = "keyboard_arrow_up"
|
|
} else if (canShowAll) {
|
|
buttonText = intl.formatMessage({
|
|
id: "common.showAll",
|
|
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>
|
|
)
|
|
}
|