Files
web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx
Joakim Jäderberg 873183ec2f * move setLang() to a root layout
* 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
2025-02-19 09:06:37 +00:00

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>
)
}