Files
web/apps/scandic-web/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx
Bianca Widstam d9b858c823 Merged in feat/SW-3289-replace-sidepeek-hotel-reservation (pull request #2686)
feat(SW-3289): replace sidepeek

* fix(SW-3289): replace sidepeek

* fix(SW-3289): add wrapping prop and change prop name to buttonVariant

* fix(SW-3289): replace body with typography

* fix(SW-3289): fix intl message


Approved-by: Joakim Jäderberg
2025-08-22 11:43:39 +00:00

197 lines
6.8 KiB
TypeScript

import TripAdvisorChip from "@scandic-hotels/booking-flow/components/TripAdvisorChip"
import { dt } from "@scandic-hotels/common/dt"
import { getSingleDecimal } from "@scandic-hotels/common/utils/numberFormatting"
import { Divider } from "@scandic-hotels/design-system/Divider"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getHotelAlertsForBookingDates } from "../../utils"
import HotelDescription from "./HotelDescription"
import styles from "./hotelInfoCard.module.css"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate"
export type HotelInfoCardProps = {
booking: SelectRateBooking
hotel: Hotel
restaurants: Restaurant[]
additionalData: AdditionalData | undefined
}
export async function HotelInfoCard({
booking,
hotel,
restaurants,
additionalData,
}: HotelInfoCardProps) {
const intl = await getIntl()
const sortedFacilities = hotel.detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5)
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
const bookingFromDate = dt(booking.fromDate)
const bookingToDate = dt(booking.toDate)
const specialAlerts = getHotelAlertsForBookingDates(
hotel.specialAlerts,
bookingFromDate,
bookingToDate
)
return (
<article className={styles.container}>
<section className={styles.wrapper}>
<div className={styles.imageWrapper}>
<ImageGallery title={hotel.name} images={galleryImages} fill />
{hotel.ratings?.tripAdvisor && (
<TripAdvisorChip rating={hotel.ratings.tripAdvisor.rating} />
)}
</div>
<div className={styles.hotelContent}>
<div className={styles.hotelInformation}>
<Typography variant="Title/md">
<h1 className={styles.hotelName}>{hotel.name}</h1>
</Typography>
<div className={styles.hotelAddressDescription}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.hotelAddress}>
{intl.formatMessage(
{
defaultMessage:
"{address}, {city} ∙ {distanceToCityCenterInKm} km to city center",
},
{
address: hotel.address.streetAddress,
city: hotel.address.city,
distanceToCityCenterInKm: getSingleDecimal(
hotel.location.distanceToCentre / 1000
),
}
)}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.hotelDescription}>
{hotel.hotelContent.texts.descriptions?.medium}
</p>
</Typography>
<HotelDescription
key={hotel.operaId}
description={hotel.hotelContent.texts.descriptions?.medium}
hotel={hotel}
restaurants={restaurants}
additionalData={additionalData}
sortedFacilities={sortedFacilities}
/>
</div>
</div>
<Divider variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
{sortedFacilities?.map((facility) => (
<div className={styles.facilitiesItem} key={facility.id}>
<FacilityToIcon id={facility.id} color="Icon/Default" />
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>{facility.name}</p>
</Typography>
</div>
))}
</div>
<HotelDetailsSidePeek
hotel={{ ...hotel, url: null }}
restaurants={restaurants}
additionalHotelData={additionalData}
triggerLabel={intl.formatMessage({
defaultMessage: "See all amenities",
})}
buttonVariant="primary"
/>
</div>
</div>
</section>
{specialAlerts.map((alert) => (
<SpecialAlert key={alert.id} alert={alert} />
))}
</article>
)
}
function SpecialAlert({ alert }: { alert: Hotel["specialAlerts"][number] }) {
return (
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}>
<Alert
key={alert.id}
type={alert.type}
heading={alert.heading}
text={alert.text}
/>
</div>
)
}
export function HotelInfoCardSkeleton() {
return (
<article className={styles.container}>
<section className={styles.wrapper}>
<div className={styles.imageWrapper}>
<SkeletonShimmer height="100%" width="100%" />
</div>
<div className={styles.hotelContent}>
<div className={styles.hotelInformation}>
<SkeletonShimmer width="60ch" height="40px" />
<div className={styles.hotelAddressDescription}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<SkeletonShimmer width="40ch" />
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
<SkeletonShimmer width="60ch" />
<SkeletonShimmer width="58ch" />
<SkeletonShimmer width="45ch" />
</p>
</Typography>
</div>
</div>
<Divider variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
<Typography
variant="Body/Paragraph/mdBold"
className={styles.facilityTitle}
>
<SkeletonShimmer width="20ch" />
</Typography>
{[1, 2, 3, 4, 5]?.map((id) => {
return (
<div className={styles.facilitiesItem} key={id}>
<SkeletonShimmer width="10ch" />
</div>
)
})}
</div>
<div className={styles.hotelAlert}>
<SkeletonShimmer width="18ch" />
</div>
</div>
</div>
</section>
</article>
)
}