{hotel.name}
{addressStr}
{addressStr}
{hotel.description}
'use client'
import { cx } from 'class-variance-authority'
import { type ReadonlyURLSearchParams, useSearchParams } from 'next/navigation'
import { memo, useState } from 'react'
import { useFocusWithin } from 'react-aria'
import { useIntl } from 'react-intl'
import {
alternativeHotelsMap,
selectHotelMap,
selectRate,
} from '@scandic-hotels/common/constants/routes/hotelReservation'
import { getSingleDecimal } from '@scandic-hotels/common/utils/numberFormatting'
import Caption from '../Caption'
import { Divider } from '../Divider'
import { FacilityToIcon } from '../FacilityToIcon'
import HotelLogoIcon from '../Icons/Logos'
import ImageGallery, { GalleryImage } from '../ImageGallery'
import Link from '../OldDSLink'
import { Typography } from '../Typography'
import { HotelPointsRow } from './HotelPointsRow'
import { NoPriceAvailableCard } from './NoPriceAvailableCard'
import HotelChequeCard from './HotelChequeCard'
import { HotelPriceCard } from './HotelPriceCard'
import HotelVoucherCard from './HotelVoucherCard'
import { hotelCardVariants } from './variants'
import styles from './hotelCard.module.css'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import { FacilityEnum } from '@scandic-hotels/common/constants/facilities'
import { HotelType } from '@scandic-hotels/common/constants/hotelType'
import type { Lang } from '@scandic-hotels/common/constants/language'
import { RateTypeEnum } from '@scandic-hotels/common/constants/rateType'
import { BookingCodeChip } from '../BookingCodeChip'
import { TripAdvisorChip } from '../TripAdvisorChip'
type Price = {
pricePerStay: number
pricePerNight: number
currency: string
}
export type HotelCardProps = {
hotel: {
id: string
hotelType: HotelType
name: string
description?: string
detailedFacilities: { name: string; id: FacilityEnum }[]
address: {
city: string
streetAddress: string
}
ratings?: {
tripAdvisor?: number
}
}
prices:
| {
public?: {
rateType: RateTypeEnum
localPrice: Price
requestedPrice?: Price
}
member?: {
rateType: RateTypeEnum
localPrice: Price
requestedPrice?: Price
}
voucher?: {
numberOfVouchers: number
rateCode: string
rateType: RateTypeEnum
}
bonusCheque?: {
rateCode: string
rateType: RateTypeEnum
localPrice: {
additionalPricePerStay: number
currency: CurrencyEnum | null | undefined
numberOfCheques: number
}
requestedPrice?: {
additionalPricePerStay: number
currency: CurrencyEnum | null | undefined
numberOfCheques: number
}
}
redemptions?: {
rateCode: string
hasEnoughPoints: boolean
localPrice: {
additionalPricePerStay: number
pointsPerStay: number
currency: CurrencyEnum | null | undefined
}
}[]
}
| undefined
images: GalleryImage[]
distanceToCityCenter: number
isUserLoggedIn: boolean
type?: 'mapListing' | 'pageListing'
state?: 'default' | 'active'
bookingCode?: string | null
isAlternative?: boolean
isPartnerBrand: boolean
pointsCurrency?: CurrencyEnum
fullPrice: boolean
isCampaignWithBookingCode: boolean
lang: Lang
belowInfoSlot: React.ReactNode
onHover: () => void
onHoverEnd: () => void
onFocusIn: () => void
onFocusOut: () => void
onAddressClick: () => void
}
export const HotelCardComponent = memo(
({
prices,
hotel,
distanceToCityCenter,
isUserLoggedIn,
state = 'default',
type = 'pageListing',
bookingCode = '',
isAlternative,
isPartnerBrand,
pointsCurrency,
images,
lang,
belowInfoSlot,
fullPrice,
isCampaignWithBookingCode,
onAddressClick,
onHover,
onHoverEnd,
onFocusIn,
onFocusOut,
}: HotelCardProps) => {
const searchParams = useSearchParams()
const [isFocusWithin, setIsFocusWithin] = useState(false)
const { focusWithinProps } = useFocusWithin({
onFocusWithin: onFocusIn,
onBlurWithin: onFocusOut,
onFocusWithinChange: (isFocusWithin) => {
setIsFocusWithin(isFocusWithin)
},
})
const intl = useIntl()
const amenities = hotel.detailedFacilities.slice(0, 5)
const classNames = hotelCardVariants({
type,
state,
})
const mapUrl = isAlternative
? alternativeHotelsMap(lang)
: selectHotelMap(lang)
const handleAddressClick = (event: React.MouseEvent) => {
event.preventDefault()
onAddressClick()
}
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`
const hasInsufficientPoints = !prices?.redemptions?.some(
(r) => r.hasEnoughPoints
)
const notEnoughPointsLabel = intl.formatMessage({
id: 'booking.notEnoughPoints',
defaultMessage: 'Not enough points',
})
const isDisabled = prices?.redemptions?.length && hasInsufficientPoints
const isCampaign =
prices?.public?.rateType === RateTypeEnum.PublicPromotion ||
prices?.member?.rateType === RateTypeEnum.PublicPromotion
const showBookingCodeChip = bookingCode || isCampaign
function onMouseEnter() {
if (!isFocusWithin) {
onHover()
}
}
function onMouseLeave() {
if (!isFocusWithin) {
onHoverEnd()
}
}
return (
{addressStr} {addressStr} {hotel.description}{hotel.name}