"use client" import { TRPCClientError } from "@trpc/client" import { useEffect } from "react" import { useIntl } from "react-intl" import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert" import { Alert } from "@scandic-hotels/design-system/Alert" import { trackNoAvailability } from "@scandic-hotels/tracking/NoAvailabilityTracking" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" import { useSelectRateContext } from "../../../contexts/SelectRate/SelectRateContext" import useLang from "../../../hooks/useLang" import { mapPackageToLabel } from "../../../utils/getRoomFeatureDescription" import { trackLowestRoomPrice, trackRoomsLoaded } from "../Tracking/tracking" import ExtraCostAlert from "./ExtraCostAlert" import { RateSummary } from "./RateSummary" import Rooms from "./Rooms" import { RoomsContainerSkeleton } from "./RoomsContainerSkeleton" import styles from "./index.module.css" import type { HotelData } from "@scandic-hotels/trpc/types/hotel" interface RoomsContainerProps extends Pick, Pick {} export function RoomsContainer({}: RoomsContainerProps) { const intl = useIntl() const lang = useLang() const { hotel, availability: { error, isFetching, isError, data: availabilityData }, input: { hasError: hasInputError, errorCode, data: inputData }, getLowestRoomPrice, } = useSelectRateContext() const { price: lowestRoomPrice, currency: lowestRoomPriceCurrency } = getLowestRoomPrice() ?? {} const dataAvailable = availabilityData?.length const { hotelId, fromDate, toDate, bookingCode, searchType, rooms } = inputData?.booking ?? {} const specialRoomType = rooms ?.map((room) => { const packages = room.packages ?.map((pkg) => { mapPackageToLabel(pkg) }) .join(",") return packages ?? "" }) .join("|") useEffect(() => { if (!dataAvailable) return if (!hotelId || !fromDate || !toDate || !lowestRoomPrice) return trackLowestRoomPrice({ hotelId: hotelId, arrivalDate: fromDate, departureDate: toDate, lowestPrice: lowestRoomPrice.toString(), currency: lowestRoomPriceCurrency, }) }, [ dataAvailable, hotelId, fromDate, toDate, lowestRoomPrice, lowestRoomPriceCurrency, ]) useEffect(() => { if (isFetching) return trackRoomsLoaded() }, [isFetching]) useEffect(() => { if (isFetching || !availabilityData || !toDate || !fromDate) return const shouldTrackNoAvailability = availabilityData.some((room) => { if (!room || "error" in room) return false return room.roomConfigurations.every( (roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable ) }) if (shouldTrackNoAvailability) { trackNoAvailability({ specialRoomType, searchTerm: hotel.data?.additionalData?.name || "", lang, fromDate, toDate, noOfRooms: rooms?.length, searchType: "hotel", hotelId, rewardNight: searchType === SEARCH_TYPE_REDEMPTION ? "yes" : "no", bookingCode, pageId: "select-rate", pageName: "hotelreservation|select-rate", pageType: "bookingroomsandratespage", siteSections: "hotelreservation|select-rate", domain: typeof window !== "undefined" ? window.location.host : "", }) } }, [ isFetching, availabilityData, bookingCode, hotelId, fromDate, toDate, specialRoomType, hotel.data?.additionalData?.name, rooms?.length, searchType, lang, ]) if (isFetching) { return } if (isError || hasInputError) { const errorMessage = getErrorMessage(error ?? errorCode, intl) return (
) } return ( <> ) } function getErrorMessage(error: unknown, intl: ReturnType) { let errorCode = "" if (error instanceof TRPCClientError) { errorCode = error.data?.zodError?.formErrors?.at(0) } else if (typeof error == "string") { errorCode = error } switch (errorCode) { case "FROMDATE_INVALID": case "TODATE_INVALID": case "TODATE_MUST_BE_AFTER_FROMDATE": case "FROMDATE_CANNOT_BE_IN_THE_PAST": { return intl.formatMessage({ id: "selectRate.invalidDatesErrorMessage", defaultMessage: "Invalid dates", }) } default: return intl.formatMessage({ id: "errorMessage.somethingWentWrong", defaultMessage: "Something went wrong!", }) } }