234 lines
8.4 KiB
TypeScript
234 lines
8.4 KiB
TypeScript
"use client"
|
|
|
|
import { useParams } from "next/dist/client/components/navigation"
|
|
import { memo, useCallback } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { HotelLogo, MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
|
|
|
import { selectRate } from "@/constants/routes/hotelReservation"
|
|
import { useHotelsMapStore } from "@/stores/hotels-map"
|
|
|
|
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
|
|
import ImageGallery from "@/components/ImageGallery"
|
|
import Button from "@/components/TempDesignSystem/Button"
|
|
import Divider from "@/components/TempDesignSystem/Divider"
|
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
import Link from "@/components/TempDesignSystem/Link"
|
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
|
import { getSingleDecimal } from "@/utils/numberFormatting"
|
|
|
|
import ReadMore from "../ReadMore"
|
|
import TripAdvisorChip from "../TripAdvisorChip"
|
|
import HotelChequeCard from "./HotelChequeCard"
|
|
import HotelPointsRow from "./HotelPointsRow"
|
|
import HotelPriceCard from "./HotelPriceCard"
|
|
import HotelVoucherCard from "./HotelVoucherCard"
|
|
import NoPriceAvailableCard from "./NoPriceAvailableCard"
|
|
import { hotelCardVariants } from "./variants"
|
|
|
|
import styles from "./hotelCard.module.css"
|
|
|
|
import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
|
import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
|
|
import { RateTypeEnum } from "@/types/enums/rateType"
|
|
import type { Lang } from "@/constants/languages"
|
|
|
|
function HotelCard({
|
|
hotelData: { availability, hotel },
|
|
isUserLoggedIn,
|
|
state = "default",
|
|
type = HotelCardListingTypeEnum.PageListing,
|
|
bookingCode = "",
|
|
}: HotelCardProps) {
|
|
const params = useParams()
|
|
const lang = params.lang as Lang
|
|
const intl = useIntl()
|
|
const { setActiveHotelPin, setActiveHotelCard } = useHotelsMapStore()
|
|
|
|
const handleMouseEnter = useCallback(() => {
|
|
setActiveHotelPin(hotel.name)
|
|
}, [setActiveHotelPin, hotel])
|
|
|
|
const handleMouseLeave = useCallback(() => {
|
|
setActiveHotelPin(null)
|
|
setActiveHotelCard(null)
|
|
}, [setActiveHotelPin, setActiveHotelCard])
|
|
|
|
const amenities = hotel.detailedFacilities.slice(0, 5)
|
|
|
|
const classNames = hotelCardVariants({
|
|
type,
|
|
state,
|
|
})
|
|
|
|
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`
|
|
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
|
|
const fullPrice =
|
|
availability.productType?.public?.rateType === RateTypeEnum.Regular ||
|
|
availability.productType?.member?.rateType === RateTypeEnum.Regular
|
|
const price = availability.productType
|
|
|
|
return (
|
|
<article
|
|
className={classNames}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<div>
|
|
<div className={styles.imageContainer}>
|
|
<ImageGallery title={hotel.name} images={galleryImages} fill />
|
|
{hotel.ratings?.tripAdvisor && (
|
|
<TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className={styles.hotelContent}>
|
|
<section className={styles.hotelInformation}>
|
|
<div className={styles.titleContainer}>
|
|
<HotelLogo hotelId={hotel.operaId} hotelType={hotel.hotelType} />
|
|
<Subtitle textTransform="capitalize" color="uiTextHighContrast">
|
|
{hotel.name}
|
|
</Subtitle>
|
|
<div className={styles.addressContainer}>
|
|
<address className={styles.address}>
|
|
<Caption color="uiTextPlaceholder">{addressStr}</Caption>
|
|
</address>
|
|
<address className={styles.addressMobile}>
|
|
<Caption color="burgundy" type="underline" asChild>
|
|
<Link
|
|
href={`https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`}
|
|
target="_blank"
|
|
aria-label={intl.formatMessage({
|
|
id: "Driving directions",
|
|
})}
|
|
title={intl.formatMessage({
|
|
id: "Driving directions",
|
|
})}
|
|
color="burgundy"
|
|
size="small"
|
|
>
|
|
{addressStr}
|
|
</Link>
|
|
</Caption>
|
|
</address>
|
|
<div>
|
|
<Divider variant="vertical" color="subtle" />
|
|
</div>
|
|
<Caption color="uiTextPlaceholder">
|
|
{intl.formatMessage(
|
|
{ id: "{number} km to city center" },
|
|
{
|
|
number: getSingleDecimal(
|
|
hotel.location.distanceToCentre / 1000
|
|
),
|
|
}
|
|
)}
|
|
</Caption>
|
|
</div>
|
|
</div>
|
|
<Body className={styles.hotelDescription}>
|
|
{hotel.hotelContent.texts.descriptions?.short}
|
|
</Body>
|
|
<div className={styles.facilities}>
|
|
{amenities.map((facility) => {
|
|
const Icon = (
|
|
<FacilityToIcon id={facility.id} color="Icon/Default" />
|
|
)
|
|
return (
|
|
<div className={styles.facilitiesItem} key={facility.id}>
|
|
{Icon && Icon}
|
|
<Caption color="uiTextMediumContrast">
|
|
{facility.name}
|
|
</Caption>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
<ReadMore
|
|
label={intl.formatMessage({ id: "See hotel details" })}
|
|
hotelId={hotel.operaId}
|
|
hotel={hotel}
|
|
showCTA={true}
|
|
/>
|
|
</section>
|
|
<div className={styles.prices}>
|
|
{!availability.productType ? (
|
|
<NoPriceAvailableCard />
|
|
) : (
|
|
<>
|
|
{bookingCode && (
|
|
<span className={`${fullPrice ? styles.strikedText : ""}`}>
|
|
<IconChip
|
|
color="blue"
|
|
icon={<MaterialIcon icon="sell" size={20} />}
|
|
>
|
|
{bookingCode}
|
|
</IconChip>
|
|
</span>
|
|
)}
|
|
{(!isUserLoggedIn ||
|
|
!price?.member ||
|
|
(bookingCode && !fullPrice)) &&
|
|
price?.public && (
|
|
<HotelPriceCard productTypePrices={price.public} />
|
|
)}
|
|
{availability.productType.member && (
|
|
<HotelPriceCard
|
|
productTypePrices={availability.productType.member}
|
|
isMemberPrice
|
|
/>
|
|
)}
|
|
{price?.voucher && (
|
|
<HotelVoucherCard productTypeVoucher={price.voucher} />
|
|
)}
|
|
{price?.bonusCheque && (
|
|
<HotelChequeCard productTypeCheque={price.bonusCheque} />
|
|
)}
|
|
{price?.redemptions?.length ? (
|
|
<div className={styles.pointsCard}>
|
|
<Caption>
|
|
{intl.formatMessage({ id: "Available rates" })}
|
|
</Caption>
|
|
{price.redemptions.map((redemption) => (
|
|
<HotelPointsRow
|
|
key={redemption.rateCode}
|
|
pointsPerStay={redemption.localPrice.pointsPerStay}
|
|
additionalPricePerStay={
|
|
redemption.localPrice.additionalPricePerStay
|
|
}
|
|
additionalPriceCurrency={
|
|
redemption.localPrice.currency ?? undefined
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
<Button
|
|
asChild
|
|
theme="base"
|
|
intent="primary"
|
|
size="small"
|
|
className={styles.button}
|
|
>
|
|
<Link
|
|
href={`${selectRate(lang)}?hotel=${hotel.operaId}`}
|
|
color="none"
|
|
keepSearchParams
|
|
>
|
|
{intl.formatMessage({ id: "See rooms" })}
|
|
</Link>
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|
|
|
|
export default memo(HotelCard)
|