* fix: findLang only returns acceptable languages * fix: fallback to use header x-lang if we haven't setLang yet * fix: languageSchema, allow uppercase Approved-by: Linus Flood
259 lines
9.1 KiB
TypeScript
259 lines
9.1 KiB
TypeScript
import { notFound } from "next/navigation"
|
|
import { Suspense } from "react"
|
|
|
|
import {
|
|
getBreakfastPackages,
|
|
getHotel,
|
|
getPackages,
|
|
getProfileSafely,
|
|
getSelectedRoomAvailability,
|
|
} from "@/lib/trpc/memoizedRequests"
|
|
|
|
import BedType from "@/components/HotelReservation/EnterDetails/BedType"
|
|
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
|
import Details from "@/components/HotelReservation/EnterDetails/Details"
|
|
import HotelHeader from "@/components/HotelReservation/EnterDetails/Header"
|
|
import Payment from "@/components/HotelReservation/EnterDetails/Payment"
|
|
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
|
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
|
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
|
|
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
|
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
|
import { getIntl } from "@/i18n"
|
|
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
|
import { convertSearchParamsToObj } from "@/utils/url"
|
|
|
|
import styles from "./page.module.css"
|
|
|
|
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
|
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
|
import { type SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
import { StepEnum } from "@/types/enums/step"
|
|
import type { LangParams, PageArgs } from "@/types/params"
|
|
import type { Packages } from "@/types/requests/packages"
|
|
|
|
export interface RoomData {
|
|
bedTypes?: BedTypeSelection[]
|
|
mustBeGuaranteed?: boolean
|
|
breakfastIncluded?: boolean
|
|
packages: Packages | null
|
|
cancellationText: string
|
|
rateDetails: string[]
|
|
roomType: string
|
|
roomRate: RoomRate
|
|
}
|
|
|
|
export default async function DetailsPage({
|
|
params: { lang },
|
|
searchParams,
|
|
}: PageArgs<LangParams, SelectRateSearchParams>) {
|
|
const intl = await getIntl()
|
|
const selectRoomParams = new URLSearchParams(searchParams)
|
|
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
|
|
|
|
void getProfileSafely()
|
|
|
|
const breakfastInput = {
|
|
adults: 1,
|
|
fromDate: booking.fromDate,
|
|
hotelId: booking.hotelId,
|
|
toDate: booking.toDate,
|
|
}
|
|
const breakfastPackages = await getBreakfastPackages(breakfastInput)
|
|
const roomsData: RoomData[] = []
|
|
|
|
for (let room of booking.rooms) {
|
|
const childrenAsString =
|
|
room.childrenInRoom && generateChildrenString(room.childrenInRoom)
|
|
|
|
const selectedRoomAvailabilityInput = {
|
|
adults: room.adults,
|
|
children: childrenAsString,
|
|
hotelId: booking.hotelId,
|
|
packageCodes: room.packages,
|
|
rateCode: room.rateCode,
|
|
roomStayStartDate: booking.fromDate,
|
|
roomStayEndDate: booking.toDate,
|
|
roomTypeCode: room.roomTypeCode,
|
|
}
|
|
|
|
const packages = room.packages
|
|
? await getPackages({
|
|
adults: room.adults,
|
|
children: room.childrenInRoom?.length,
|
|
endDate: booking.toDate,
|
|
hotelId: booking.hotelId,
|
|
packageCodes: room.packages,
|
|
startDate: booking.fromDate,
|
|
})
|
|
: null
|
|
|
|
const roomAvailability = await getSelectedRoomAvailability(
|
|
selectedRoomAvailabilityInput //
|
|
)
|
|
|
|
if (!roomAvailability) {
|
|
continue // TODO: handle no room availability
|
|
}
|
|
|
|
roomsData.push({
|
|
bedTypes: roomAvailability.bedTypes,
|
|
packages,
|
|
mustBeGuaranteed: roomAvailability.mustBeGuaranteed,
|
|
breakfastIncluded: roomAvailability.breakfastIncluded,
|
|
cancellationText: roomAvailability.cancellationText,
|
|
rateDetails: roomAvailability.rateDetails ?? [],
|
|
roomType: roomAvailability.selectedRoom.roomType,
|
|
roomRate: {
|
|
memberRate: roomAvailability?.memberRate,
|
|
publicRate: roomAvailability.publicRate,
|
|
},
|
|
})
|
|
}
|
|
|
|
const isCardOnlyPayment = roomsData.some((room) => room?.mustBeGuaranteed)
|
|
const hotelData = await getHotel({
|
|
hotelId: booking.hotelId,
|
|
isCardOnlyPayment,
|
|
language: lang,
|
|
})
|
|
const user = await getProfileSafely()
|
|
// const userTrackingData = await getUserTracking()
|
|
|
|
if (!hotelData || !roomsData) {
|
|
return notFound()
|
|
}
|
|
|
|
// const arrivalDate = new Date(booking.fromDate)
|
|
// const departureDate = new Date(booking.toDate)
|
|
const hotelAttributes = hotelData.hotel
|
|
|
|
// TODO: add tracking
|
|
// const initialHotelsTrackingData: TrackingSDKHotelInfo = {
|
|
// searchTerm: searchParams.city,
|
|
// arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
|
// departureDate: format(departureDate, "yyyy-MM-dd"),
|
|
// noOfAdults: adults,
|
|
// noOfChildren: childrenInRoom?.length,
|
|
// ageOfChildren: childrenInRoom?.map((c) => c.age).join(","),
|
|
// childBedPreference: childrenInRoom
|
|
// ?.map((c) => ChildBedMapEnum[c.bed])
|
|
// .join("|"),
|
|
// noOfRooms: 1, // // TODO: Handle multiple rooms
|
|
// duration: differenceInCalendarDays(departureDate, arrivalDate),
|
|
// leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
|
// searchType: "hotel",
|
|
// bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
|
// country: hotelAttributes?.address.country,
|
|
// hotelID: hotelAttributes?.operaId,
|
|
// region: hotelAttributes?.address.city,
|
|
// }
|
|
|
|
const showBreakfastStep = Boolean(
|
|
breakfastPackages?.length && !roomsData[0].breakfastIncluded
|
|
)
|
|
|
|
return (
|
|
<EnterDetailsProvider
|
|
booking={booking}
|
|
showBreakfastStep={showBreakfastStep}
|
|
roomsData={roomsData}
|
|
searchParamsStr={selectRoomParams.toString()}
|
|
user={user}
|
|
vat={hotelAttributes.vat}
|
|
>
|
|
<main>
|
|
<HotelHeader hotelData={hotelData} />
|
|
<div className={styles.container}>
|
|
<div className={styles.content}>
|
|
{roomsData.map((room, idx) => (
|
|
<section key={idx}>
|
|
{roomsData.length > 1 && (
|
|
<header className={styles.header}>
|
|
<Title level="h2" as="h4">
|
|
{intl.formatMessage({ id: "Room" })} {idx + 1}
|
|
</Title>
|
|
</header>
|
|
)}
|
|
<SelectedRoom
|
|
hotelId={booking.hotelId}
|
|
roomType={room.roomType}
|
|
roomTypeCode={booking.rooms[idx].roomTypeCode}
|
|
rateDescription={room.cancellationText}
|
|
roomIndex={idx}
|
|
searchParamsStr={selectRoomParams.toString()}
|
|
/>
|
|
|
|
{room.bedTypes ? (
|
|
<SectionAccordion
|
|
header={intl.formatMessage({ id: "Select bed" })}
|
|
label={intl.formatMessage({ id: "Request bedtype" })}
|
|
step={StepEnum.selectBed}
|
|
roomIndex={idx}
|
|
>
|
|
<BedType bedTypes={room.bedTypes} roomIndex={idx} />
|
|
</SectionAccordion>
|
|
) : null}
|
|
|
|
{showBreakfastStep ? (
|
|
<SectionAccordion
|
|
header={intl.formatMessage({ id: "Food options" })}
|
|
label={intl.formatMessage({
|
|
id: "Select breakfast options",
|
|
})}
|
|
step={StepEnum.breakfast}
|
|
roomIndex={idx}
|
|
>
|
|
<Breakfast packages={breakfastPackages!} roomIndex={idx} />
|
|
</SectionAccordion>
|
|
) : null}
|
|
|
|
<SectionAccordion
|
|
header={intl.formatMessage({ id: "Details" })}
|
|
step={StepEnum.details}
|
|
label={intl.formatMessage({ id: "Enter your details" })}
|
|
roomIndex={idx}
|
|
>
|
|
<Details
|
|
user={idx === 0 ? user : null}
|
|
memberPrice={{
|
|
currency:
|
|
room?.roomRate.memberRate?.localPrice.currency ?? "", // TODO: how to handle undefined,
|
|
price:
|
|
room?.roomRate.memberRate?.localPrice.pricePerNight ??
|
|
0, // TODO: how to handle undefined,
|
|
}}
|
|
roomIndex={idx}
|
|
/>
|
|
</SectionAccordion>
|
|
</section>
|
|
))}
|
|
<Suspense>
|
|
<Payment
|
|
user={user}
|
|
otherPaymentOptions={
|
|
hotelAttributes.merchantInformationData
|
|
.alternatePaymentOptions
|
|
}
|
|
supportedCards={hotelAttributes.merchantInformationData.cards}
|
|
mustBeGuaranteed={isCardOnlyPayment}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
<aside className={styles.summary}>
|
|
<MobileSummary
|
|
isMember={!!user}
|
|
breakfastIncluded={roomsData[0].breakfastIncluded ?? false}
|
|
/>
|
|
<DesktopSummary
|
|
isMember={!!user}
|
|
breakfastIncluded={roomsData[0].breakfastIncluded ?? false}
|
|
/>
|
|
</aside>
|
|
</div>
|
|
</main>
|
|
</EnterDetailsProvider>
|
|
)
|
|
}
|