fix: we showed duplicate rooms because every bed represents a room

This commit is contained in:
Simon Emanuelsson
2025-03-19 10:01:05 +01:00
parent 200ed55a2c
commit cf91d3d947
12 changed files with 356 additions and 234 deletions

View File

@@ -17,94 +17,89 @@ import styles from "./hotelInfoCard.module.css"
import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard"
export default async function HotelInfoCard({ hotelData }: HotelInfoCardProps) {
const hotel = hotelData?.hotel
export default async function HotelInfoCard({ hotel }: HotelInfoCardProps) {
const intl = await getIntl()
const sortedFacilities = hotel?.detailedFacilities
const sortedFacilities = hotel.detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5)
const galleryImages = mapApiImagesToGalleryImages(hotel?.galleryImages || [])
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
return (
<article className={styles.container}>
{hotel && (
<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}>
<Title as="h2" textTransform="uppercase">
{hotel.name}
</Title>
<div className={styles.hotelAddressDescription}>
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{
id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center",
},
{
address: hotel.address.streetAddress,
city: hotel.address.city,
distanceToCityCenterInKm: getSingleDecimal(
hotel.location.distanceToCentre / 1000
),
}
)}
</Caption>
<Body color="uiTextHighContrast">
{hotel.hotelContent.texts.descriptions?.medium}
</Body>
</div>
</div>
<Divider color="subtle" variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
<Body textTransform="bold" className={styles.facilityTitle}>
{intl.formatMessage({ id: "At the hotel" })}
</Body>
{sortedFacilities?.map((facility) => {
const IconComponent = mapFacilityToIcon(facility.id)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && (
<IconComponent
className={styles.facilitiesIcon}
color="grey80"
/>
)}
<Body color="uiTextHighContrast">{facility.name}</Body>
</div>
)
})}
</div>
<ReadMore
label={intl.formatMessage({ id: "See all amenities" })}
hotelId={hotel.operaId}
hotel={hotel}
showCTA={false}
/>
<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}>
<Title as="h2" textTransform="uppercase">
{hotel.name}
</Title>
<div className={styles.hotelAddressDescription}>
<Caption color="uiTextMediumContrast">
{intl.formatMessage(
{
id: "{address}, {city} ∙ {distanceToCityCenterInKm} km to city center",
},
{
address: hotel.address.streetAddress,
city: hotel.address.city,
distanceToCityCenterInKm: getSingleDecimal(
hotel.location.distanceToCentre / 1000
),
}
)}
</Caption>
<Body color="uiTextHighContrast">
{hotel.hotelContent.texts.descriptions?.medium}
</Body>
</div>
</div>
</section>
)}
{hotel?.specialAlerts.map((alert) => {
return (
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}>
<Alert
key={alert.id}
type={alert.type}
heading={alert.heading}
text={alert.text}
<Divider color="subtle" variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
<Body textTransform="bold" className={styles.facilityTitle}>
{intl.formatMessage({ id: "At the hotel" })}
</Body>
{sortedFacilities?.map((facility) => {
const IconComponent = mapFacilityToIcon(facility.id)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && (
<IconComponent
className={styles.facilitiesIcon}
color="grey80"
/>
)}
<Body color="uiTextHighContrast">{facility.name}</Body>
</div>
)
})}
</div>
<ReadMore
label={intl.formatMessage({ id: "See all amenities" })}
hotelId={hotel.operaId}
hotel={hotel}
showCTA={false}
/>
</div>
)
})}
</div>
</section>
{hotel.specialAlerts.map((alert) => (
<div className={styles.hotelAlert} key={`wrapper_${alert.id}`}>
<Alert
key={alert.id}
type={alert.type}
heading={alert.heading}
text={alert.text}
/>
</div>
))}
</article>
)
}

View File

@@ -69,8 +69,6 @@ function getBreakfastMessage(
export default function RoomCard({ roomConfiguration }: RoomCardProps) {
const intl = useIntl()
const lessThanFiveRoomsLeft =
roomConfiguration.roomsLeft > 0 && roomConfiguration.roomsLeft < 5
const searchParams = useSearchParams()
const bookingCode = searchParams.get("bookingCode")
@@ -85,6 +83,8 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
}))
const { isMainRoom, roomAvailability, roomNr, selectedPackage } =
useRoomContext()
const showLowInventory =
roomConfiguration.roomsLeft > 0 && roomConfiguration.roomsLeft < 5
if (!roomAvailability || !("rateDefinitions" in roomAvailability)) {
return null
@@ -177,7 +177,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
<li className={classNames}>
<div className={styles.imageContainer}>
<div className={styles.chipContainer}>
{lessThanFiveRoomsLeft ? (
{showLowInventory ? (
<span className={styles.chip}>
<Footnote color="burgundy" textTransform="uppercase">
{intl.formatMessage(
@@ -211,16 +211,16 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
<Caption color="uiTextMediumContrast">
{occupancy.max === occupancy.min
? intl.formatMessage(
{ id: "{guests, plural, one {# guest} other {# guests}}" },
{ guests: occupancy.max }
)
{ id: "{guests, plural, one {# guest} other {# guests}}" },
{ guests: occupancy.max }
)
: intl.formatMessage(
{ id: "{min}-{max} guests" },
{
min: occupancy.min,
max: occupancy.max,
}
)}
{ id: "{min}-{max} guests" },
{
min: occupancy.min,
max: occupancy.max,
}
)}
</Caption>
)}
<RoomSize roomSize={roomSize} />
@@ -293,7 +293,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
title={rateTitle}
rateTitle={
product.public &&
product.public?.rateType !== RateTypeEnum.Regular
product.public?.rateType !== RateTypeEnum.Regular
? rateDefinition?.title
: undefined
}

View File

@@ -17,7 +17,6 @@ export function RoomsContainer({
childArray,
fromDate,
hotelData,
hotelId,
isUserLoggedIn,
toDate,
}: RoomsContainerProps) {
@@ -29,7 +28,7 @@ export function RoomsContainer({
const { data: roomsAvailability, isPending: isLoadingAvailability } =
useRoomsAvailability(
adultArray,
hotelId,
hotelData.hotel.id,
fromDateString,
toDateString,
lang,
@@ -42,7 +41,7 @@ export function RoomsContainer({
childArray,
fromDateString,
toDateString,
hotelId,
hotelData.hotel.id,
lang
)
@@ -50,10 +49,6 @@ export function RoomsContainer({
return <RoomsContainerSkeleton />
}
if (!hotelData?.hotel) {
return null
}
if (packages === null) {
// TODO: Log packages error
console.error("[RoomsContainer] unable to fetch packages")

View File

@@ -5,9 +5,7 @@ import { Suspense } from "react"
import { getHotel } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth"
import HotelInfoCard, {
HotelInfoCardSkeleton,
} from "@/components/HotelReservation/SelectRate/HotelInfoCard"
import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer"
import TrackingSDK from "@/components/TrackingSDK"
import { setLang } from "@/i18n/serverContext"
@@ -33,17 +31,21 @@ export default async function SelectRatePage({
const { adultsInRoom, childrenInRoom, hotel, noOfRooms, selectHotelParams } =
searchDetails
const { fromDate, toDate } = getValidDates(
selectHotelParams.fromDate,
selectHotelParams.toDate
)
const hotelData = await getHotel({
hotelId: hotel.id,
isCardOnlyPayment: false,
language: params.lang,
})
if (!hotelData) {
return notFound()
}
const { fromDate, toDate } = getValidDates(
selectHotelParams.fromDate,
selectHotelParams.toDate
)
const session = await auth()
const isUserLoggedIn = isValidSession(session)
@@ -59,20 +61,17 @@ export default async function SelectRatePage({
hotel.id,
hotel.name,
noOfRooms,
hotelData?.hotel.address.country,
hotelData?.hotel.address.city,
hotelData.hotel.address.country,
hotelData.hotel.address.city,
selectHotelParams.city
)
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
const hotelId = +hotel.id
const suspenseKey = stringify(searchParams)
return (
<>
<Suspense fallback={<HotelInfoCardSkeleton />}>
<HotelInfoCard hotelData={hotelData} />
</Suspense>
<HotelInfoCard hotel={hotelData.hotel} />
<RoomsContainer
adultArray={adultsInRoom}
@@ -80,7 +79,6 @@ export default async function SelectRatePage({
childArray={childrenInRoom}
fromDate={arrivalDate}
hotelData={hotelData}
hotelId={hotelId}
isUserLoggedIn={isUserLoggedIn}
toDate={departureDate}
/>

View File

@@ -6,38 +6,22 @@ import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
export function useRoomsAvailability(
adultsCount: number[],
hotelId: number,
hotelId: string,
fromDateString: string,
toDateString: string,
lang: Lang,
childArray: ChildrenInRoom,
bookingCode?: string
) {
const roomsAvailability =
trpc.hotel.availability.roomsCombinedAvailability.useQuery({
adultsCount,
bookingCode,
childArray,
hotelId,
lang,
roomStayEndDate: toDateString,
roomStayStartDate: fromDateString,
})
const data = roomsAvailability.data?.map((ra) => {
if (ra.status === "fulfilled") {
return ra.value
}
return {
details: ra.reason,
error: "request_failure",
}
return trpc.hotel.availability.roomsCombinedAvailability.useQuery({
adultsCount,
bookingCode,
childArray,
hotelId,
lang,
roomStayEndDate: toDateString,
roomStayStartDate: fromDateString,
})
return {
...roomsAvailability,
data,
}
}
export function useHotelPackages(
@@ -45,14 +29,14 @@ export function useHotelPackages(
childArray: ChildrenInRoom,
fromDateString: string,
toDateString: string,
hotelId: number,
hotelId: string,
lang: Lang
) {
return trpc.hotel.packages.get.useQuery({
adults: adultArray[0], // Using the first adult count
children: childArray?.[0]?.length, // Using the first children count
endDate: toDateString,
hotelId: hotelId.toString(),
hotelId,
packageCodes: [
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
RoomPackageCodeEnum.PET_ROOM,