Merge branch 'master' into feat/sw-929-release-preps
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
@@ -10,7 +10,7 @@ import styles from "./page.module.css"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default async function MyPages({
|
||||
params,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Suspense } from "react"
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Sidebar from "@/components/MyPages/Sidebar"
|
||||
import Surprises from "@/components/MyPages/Surprises"
|
||||
|
||||
// import Surprises from "@/components/MyPages/Surprises"
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
export default async function MyPagesLayout({
|
||||
@@ -24,9 +24,7 @@ export default async function MyPagesLayout({
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{/* TODO: Waiting on new API stuff
|
||||
<Surprises />
|
||||
*/}
|
||||
<Surprises />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ProfilePage from "../page"
|
||||
|
||||
export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default ProfilePage
|
||||
|
||||
@@ -7,7 +7,7 @@ import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default async function ProfilePage({ params }: PageArgs<LangParams>) {
|
||||
setLang(params.lang)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
|
||||
|
||||
import { isSignupPage } from "@/constants/routes/signup"
|
||||
import { env } from "@/env/server"
|
||||
import { getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import HotelPage from "@/components/ContentType/HotelPage"
|
||||
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
|
||||
export { generateMetadata } from "@/utils/generateMetadata"
|
||||
|
||||
export default function ContentTypePage({
|
||||
export default async function ContentTypePage({
|
||||
params,
|
||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||
setLang(params.lang)
|
||||
@@ -57,7 +58,12 @@ export default function ContentTypePage({
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return notFound()
|
||||
}
|
||||
return <HotelPage />
|
||||
const hotelPageData = await getHotelPage()
|
||||
return hotelPageData ? (
|
||||
<HotelPage hotelId={hotelPageData.hotel_page_id} />
|
||||
) : (
|
||||
notFound()
|
||||
)
|
||||
default:
|
||||
const type: never = params.contentType
|
||||
console.error(`Unsupported content type given: ${type}`)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import BookingConfirmation from "@/components/HotelReservation/BookingConfirmation"
|
||||
import Header from "@/components/HotelReservation/BookingConfirmation/Header"
|
||||
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
|
||||
import Summary from "@/components/HotelReservation/BookingConfirmation/Summary"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
@@ -12,11 +14,12 @@ export default async function BookingConfirmationPage({
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { confirmationNumber: string }>) {
|
||||
setLang(params.lang)
|
||||
const confirmationNumber = searchParams.confirmationNumber
|
||||
void getBookingConfirmation(confirmationNumber)
|
||||
void getBookingConfirmation(searchParams.confirmationNumber)
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<BookingConfirmation confirmationNumber={confirmationNumber} />
|
||||
</main>
|
||||
<div className={styles.main}>
|
||||
<Header confirmationNumber={searchParams.confirmationNumber} />
|
||||
<Rooms />
|
||||
<Summary />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.layout {
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
import { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export default function PaymentCallbackLayout({
|
||||
children,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return notFound()
|
||||
}
|
||||
return <div className={styles.layout}>{children}</div>
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import {
|
||||
BOOKING_CONFIRMATION_NUMBER,
|
||||
PaymentErrorCodeEnum,
|
||||
} from "@/constants/booking"
|
||||
import { Lang } from "@/constants/languages"
|
||||
import {
|
||||
bookingConfirmation,
|
||||
payment,
|
||||
} from "@/constants/routes/hotelReservation"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import PaymentCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback"
|
||||
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function PaymentCallbackPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<
|
||||
LangParams,
|
||||
{ status: "error" | "success" | "cancel"; confirmationNumber?: string }
|
||||
>) {
|
||||
console.log(`[payment-callback] callback started`)
|
||||
const lang = params.lang
|
||||
const status = searchParams.status
|
||||
const confirmationNumber = searchParams.confirmationNumber
|
||||
|
||||
if (status === "success" && confirmationNumber) {
|
||||
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}`
|
||||
|
||||
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
|
||||
redirect(confirmationUrl)
|
||||
}
|
||||
|
||||
const returnUrl = payment(lang)
|
||||
const searchObject = new URLSearchParams()
|
||||
|
||||
if (confirmationNumber) {
|
||||
try {
|
||||
const bookingStatus = await serverClient().booking.status({
|
||||
confirmationNumber,
|
||||
})
|
||||
if (bookingStatus.metadata) {
|
||||
searchObject.set(
|
||||
"errorCode",
|
||||
bookingStatus.metadata.errorCode?.toString() ?? ""
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
||||
)
|
||||
if (status === "cancel") {
|
||||
searchObject.set("errorCode", PaymentErrorCodeEnum.Cancelled.toString())
|
||||
}
|
||||
if (status === "error") {
|
||||
searchObject.set("errorCode", PaymentErrorCodeEnum.Failed.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PaymentCallback
|
||||
returnUrl={returnUrl.toString()}
|
||||
searchObject={searchObject}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1 +1,21 @@
|
||||
export { default } from "../page"
|
||||
import { getHotelData } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function HotelSidePeek({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { hotel: string }>) {
|
||||
if (!searchParams.hotel) {
|
||||
return <SidePeek hotel={null} />
|
||||
}
|
||||
|
||||
const hotel = await getHotelData({
|
||||
hotelId: searchParams.hotel,
|
||||
language: params.lang,
|
||||
})
|
||||
|
||||
return <SidePeek hotel={hotel} />
|
||||
}
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
import { getHotelData } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function HotelSidePeek({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { hotel: string }>) {
|
||||
const search = new URLSearchParams(searchParams)
|
||||
const { hotel: hotelId } = getQueryParamsForEnterDetails(search)
|
||||
|
||||
if (!hotelId) {
|
||||
return <SidePeek hotel={null} />
|
||||
}
|
||||
|
||||
const hotel = await getHotelData({
|
||||
hotelId: hotelId,
|
||||
language: params.lang,
|
||||
})
|
||||
|
||||
return <SidePeek hotel={hotel} />
|
||||
export default function HotelSidePeekSlot() {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
|
||||
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
|
||||
@@ -58,6 +58,10 @@ export default async function SelectHotelMapPage({
|
||||
|
||||
const hotelPins = getHotelPins(hotels)
|
||||
const filterList = getFiltersFromHotels(hotels)
|
||||
const cityCoordinates = await getCityCoordinates({
|
||||
city: city.name,
|
||||
hotel: { address: hotels[0].hotelData.address.streetAddress },
|
||||
})
|
||||
|
||||
return (
|
||||
<MapModal>
|
||||
@@ -67,6 +71,7 @@ export default async function SelectHotelMapPage({
|
||||
mapId={googleMapId}
|
||||
hotels={hotels}
|
||||
filterList={filterList}
|
||||
cityCoordinates={cityCoordinates}
|
||||
/>
|
||||
</MapModal>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2) 0 var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.header nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cityInformation {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -65,13 +69,19 @@
|
||||
var(--Spacing-x5);
|
||||
}
|
||||
|
||||
.header nav {
|
||||
display: block;
|
||||
max-width: var(--max-width-navigation);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.sorter {
|
||||
display: block;
|
||||
width: 339px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 auto;
|
||||
margin: var(--Spacing-x3) auto 0;
|
||||
display: flex;
|
||||
max-width: var(--max-width-navigation);
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { selectHotelMap } from "@/constants/routes/hotelReservation"
|
||||
import {
|
||||
selectHotel,
|
||||
selectHotelMap,
|
||||
} from "@/constants/routes/hotelReservation"
|
||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import {
|
||||
@@ -19,6 +23,8 @@ import {
|
||||
import { ChevronRightIcon } from "@/components/Icons"
|
||||
import StaticMap from "@/components/Maps/StaticMap"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import Breadcrumbs from "@/components/TempDesignSystem/Breadcrumbs"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
@@ -45,8 +51,12 @@ export default async function SelectHotelPage({
|
||||
(location) =>
|
||||
location.name.toLowerCase() === searchParams.city.toLowerCase()
|
||||
)
|
||||
|
||||
if (!city) return notFound()
|
||||
|
||||
const isCityWithCountry = (city: any): city is { country: string } =>
|
||||
"country" in city
|
||||
|
||||
const intl = await getIntl()
|
||||
const selectHotelParams = new URLSearchParams(searchParams)
|
||||
const selectHotelParamsObject =
|
||||
@@ -65,12 +75,36 @@ export default async function SelectHotelPage({
|
||||
})
|
||||
|
||||
const filterList = getFiltersFromHotels(hotels)
|
||||
const breadcrumbs = [
|
||||
{
|
||||
title: intl.formatMessage({ id: "Home" }),
|
||||
href: `/${params.lang}`,
|
||||
uid: "home-page",
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: "Hotel reservation" }),
|
||||
href: `/${params.lang}/hotelreservation`,
|
||||
uid: "hotel-reservation",
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: "Select hotel" }),
|
||||
href: `${selectHotel(params.lang)}/?${selectHotelParams}`,
|
||||
uid: "select-hotel",
|
||||
},
|
||||
{
|
||||
title: city.name,
|
||||
uid: city.id,
|
||||
},
|
||||
]
|
||||
|
||||
const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined)
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||
</Suspense>
|
||||
<div className={styles.title}>
|
||||
<div className={styles.cityInformation}>
|
||||
<Subtitle>{city.name}</Subtitle>
|
||||
@@ -94,6 +128,7 @@ export default async function SelectHotelPage({
|
||||
<div className={styles.mapContainer}>
|
||||
<StaticMap
|
||||
city={searchParams.city}
|
||||
country={isCityWithCountry(city) ? city.country : undefined}
|
||||
width={340}
|
||||
height={180}
|
||||
zoomLevel={11}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { beforeAll, describe, expect, it } from "@jest/globals"
|
||||
|
||||
import { getValidFromDate, getValidToDate } from "./getValidDates"
|
||||
|
||||
const NOW = new Date("2020-10-01T00:00:00Z")
|
||||
let originalTz: string | undefined
|
||||
|
||||
describe("getValidFromDate", () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers({ now: NOW })
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
describe("getValidFromDate", () => {
|
||||
it("returns today when empty string is provided", () => {
|
||||
const actual = getValidFromDate("")
|
||||
expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns today when undefined is provided", () => {
|
||||
const actual = getValidFromDate(undefined)
|
||||
expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns given date in utc", () => {
|
||||
const actual = getValidFromDate("2024-01-01")
|
||||
expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getValidToDate", () => {
|
||||
it("returns day after fromDate when empty string is provided", () => {
|
||||
const actual = getValidToDate("", NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns day after fromDate when undefined is provided", () => {
|
||||
const actual = getValidToDate(undefined, NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns given date in utc", () => {
|
||||
const actual = getValidToDate("2024-01-01", NOW)
|
||||
expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("fallsback to day after fromDate when given date is before fromDate", () => {
|
||||
const actual = getValidToDate("2020-09-30", NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Dayjs } from "dayjs"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
/**
|
||||
* Get valid dates from stringFromDate and stringToDate making sure that they are not in the past and chronologically correct
|
||||
* @example const { fromDate, toDate} = getValidDates("2021-01-01", "2021-01-02")
|
||||
*/
|
||||
export function getValidDates(
|
||||
stringFromDate: string | undefined,
|
||||
stringToDate: string | undefined
|
||||
): { fromDate: Dayjs; toDate: Dayjs } {
|
||||
const fromDate = getValidFromDate(stringFromDate)
|
||||
const toDate = getValidToDate(stringToDate, fromDate)
|
||||
|
||||
return { fromDate, toDate }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid fromDate from stringFromDate making sure that it is not in the past
|
||||
*/
|
||||
export function getValidFromDate(stringFromDate: string | undefined): Dayjs {
|
||||
const now = dt().utc()
|
||||
if (!stringFromDate) {
|
||||
return now
|
||||
}
|
||||
const toDate = dt(stringFromDate)
|
||||
|
||||
const yesterday = now.subtract(1, "day")
|
||||
if (!toDate.isAfter(yesterday)) {
|
||||
return now
|
||||
}
|
||||
|
||||
return toDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid toDate from stringToDate making sure that it is after fromDate
|
||||
*/
|
||||
export function getValidToDate(
|
||||
stringToDate: string | undefined,
|
||||
fromDate: Dayjs | Date
|
||||
): Dayjs {
|
||||
const tomorrow = dt().utc().add(1, "day")
|
||||
if (!stringToDate) {
|
||||
return tomorrow
|
||||
}
|
||||
|
||||
const toDate = dt(stringToDate)
|
||||
if (toDate.isAfter(fromDate)) {
|
||||
return toDate
|
||||
}
|
||||
|
||||
return tomorrow
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
import {
|
||||
getHotelData,
|
||||
getLocations,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
import { getHotelData, getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
|
||||
import Rooms from "@/components/HotelReservation/SelectRate/Rooms"
|
||||
import {
|
||||
generateChildrenString,
|
||||
getHotelReservationQueryParams,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer"
|
||||
import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton"
|
||||
import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
import { safeTry } from "@/utils/safeTry"
|
||||
|
||||
import { getValidDates } from "./getValidDates"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
@@ -45,71 +40,44 @@ export default async function SelectRatePage({
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const validFromDate =
|
||||
searchParams.fromDate &&
|
||||
dt(searchParams.fromDate).isAfter(dt().subtract(1, "day"))
|
||||
? searchParams.fromDate
|
||||
: dt().utc().format("YYYY-MM-DD")
|
||||
const validToDate =
|
||||
searchParams.toDate && dt(searchParams.toDate).isAfter(validFromDate)
|
||||
? searchParams.toDate
|
||||
: dt().utc().add(1, "day").format("YYYY-MM-DD")
|
||||
const adults = selectRoomParamsObject.room[0].adults || 1 // TODO: Handle multiple rooms
|
||||
const childrenCount = selectRoomParamsObject.room[0].child?.length
|
||||
const children = selectRoomParamsObject.room[0].child
|
||||
? generateChildrenString(selectRoomParamsObject.room[0].child)
|
||||
: undefined // TODO: Handle multiple rooms
|
||||
|
||||
const [hotelData, roomsAvailability, packages, user] = await Promise.all([
|
||||
getHotelData({ hotelId: searchParams.hotel, language: params.lang }),
|
||||
serverClient().hotel.availability.rooms({
|
||||
hotelId: parseInt(searchParams.hotel, 10),
|
||||
roomStayStartDate: validFromDate,
|
||||
roomStayEndDate: validToDate,
|
||||
adults,
|
||||
children,
|
||||
}),
|
||||
serverClient().hotel.packages.get({
|
||||
hotelId: searchParams.hotel,
|
||||
startDate: searchParams.fromDate,
|
||||
endDate: searchParams.toDate,
|
||||
adults,
|
||||
children: childrenCount,
|
||||
packageCodes: [
|
||||
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
||||
RoomPackageCodeEnum.PET_ROOM,
|
||||
RoomPackageCodeEnum.ALLERGY_ROOM,
|
||||
],
|
||||
}),
|
||||
getProfileSafely(),
|
||||
])
|
||||
|
||||
if (!roomsAvailability) {
|
||||
return "No rooms found" // TODO: Add a proper error message
|
||||
}
|
||||
|
||||
if (!hotelData) {
|
||||
return "No hotel data found" // TODO: Add a proper error message
|
||||
}
|
||||
|
||||
const roomCategories = hotelData?.included
|
||||
|
||||
const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce(
|
||||
(acc, room) => {
|
||||
return acc && room.status === "NotAvailable"
|
||||
},
|
||||
true
|
||||
const { fromDate, toDate } = getValidDates(
|
||||
searchParams.fromDate,
|
||||
searchParams.toDate
|
||||
)
|
||||
|
||||
const adults = selectRoomParamsObject.room[0].adults || 1 // TODO: Handle multiple rooms
|
||||
const children = selectRoomParamsObject.room[0].child // TODO: Handle multiple rooms
|
||||
|
||||
const [hotelData, hotelDataError] = await safeTry(
|
||||
getHotelData({ hotelId: searchParams.hotel, language: params.lang })
|
||||
)
|
||||
|
||||
if (!hotelData && !hotelDataError) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const hotelId = +searchParams.hotel
|
||||
return (
|
||||
<>
|
||||
<HotelInfoCard hotelData={hotelData} noAvailability={noRoomsAvailable} />
|
||||
<Rooms
|
||||
roomsAvailability={roomsAvailability}
|
||||
roomCategories={roomCategories ?? []}
|
||||
user={user}
|
||||
packages={packages ?? []}
|
||||
<HotelInfoCard
|
||||
hotelId={hotelId}
|
||||
lang={params.lang}
|
||||
fromDate={fromDate.toDate()}
|
||||
toDate={toDate.toDate()}
|
||||
adultCount={adults}
|
||||
childArray={children}
|
||||
/>
|
||||
|
||||
<Suspense key={hotelId} fallback={<RoomsContainerSkeleton />}>
|
||||
<RoomsContainer
|
||||
hotelId={hotelId}
|
||||
lang={params.lang}
|
||||
fromDate={fromDate.toDate()}
|
||||
toDate={toDate.toDate()}
|
||||
adultCount={adults}
|
||||
childArray={children}
|
||||
/>
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function LoadingHotelHeader() {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
.header {
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.descriptionContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.address {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.dividerContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: var(--Spacing-x4) 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x6);
|
||||
margin: 0 auto;
|
||||
/* simulates padding on viewport smaller than --max-width-navigation */
|
||||
width: min(
|
||||
calc(100dvw - (var(--Spacing-x2) * 2)),
|
||||
var(--max-width-navigation)
|
||||
);
|
||||
}
|
||||
|
||||
.titleContainer > h1 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dividerContainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.address {
|
||||
gap: var(--Spacing-x3);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import { getHotelData } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function HotelHeader({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { hotel: string }>) {
|
||||
const home = `/${params.lang}`
|
||||
if (!searchParams.hotel) {
|
||||
redirect(home)
|
||||
}
|
||||
const hotelData = await getHotelData({
|
||||
hotelId: searchParams.hotel,
|
||||
language: params.lang,
|
||||
})
|
||||
if (!hotelData?.data) {
|
||||
redirect(home)
|
||||
}
|
||||
|
||||
const intl = await getIntl()
|
||||
const hotel = hotelData.data.attributes
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.titleContainer}>
|
||||
<Title as="h3" level="h1">
|
||||
{hotel.name}
|
||||
</Title>
|
||||
<address className={styles.address}>
|
||||
<Caption color="textMediumContrast">
|
||||
{hotel.address.streetAddress}, {hotel.address.city}
|
||||
</Caption>
|
||||
<div>
|
||||
<Divider variant="vertical" color="subtle" />
|
||||
</div>
|
||||
<Caption color="textMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Distance in km to city centre" },
|
||||
{ number: hotel.location.distanceToCentre }
|
||||
)}
|
||||
</Caption>
|
||||
</address>
|
||||
</div>
|
||||
<div className={styles.dividerContainer}>
|
||||
<Divider variant="vertical" color="subtle" />
|
||||
</div>
|
||||
<div className={styles.descriptionContainer}>
|
||||
<Body color="baseTextHighContrast">
|
||||
{hotel.hotelContent.texts.descriptions.short}
|
||||
</Body>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function LoadingSummaryHeader() {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
.mobileSummary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desktopSummary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary {
|
||||
background-color: var(--Main-Grey-White);
|
||||
|
||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-bottom: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.mobileSummary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.desktopSummary {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
margin-top: calc(0px - var(--Spacing-x9));
|
||||
}
|
||||
|
||||
.summary {
|
||||
position: sticky;
|
||||
top: calc(
|
||||
var(--booking-widget-desktop-height) + var(--Spacing-x2) +
|
||||
var(--Spacing-x-half)
|
||||
);
|
||||
z-index: 10;
|
||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||
margin-top: calc(0px - var(--Spacing-x9));
|
||||
}
|
||||
|
||||
.shadow {
|
||||
display: block;
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
border-style: solid;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.hider {
|
||||
display: block;
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
position: sticky;
|
||||
top: calc(var(--booking-widget-desktop-height) - 6px);
|
||||
margin-top: var(--Spacing-x4);
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||
import {
|
||||
getPackages,
|
||||
getProfileSafely,
|
||||
getSelectedRoomAvailability,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Summary from "@/components/HotelReservation/EnterDetails/Summary"
|
||||
import { SummaryBottomSheet } from "@/components/HotelReservation/EnterDetails/Summary/BottomSheet"
|
||||
import {
|
||||
generateChildrenString,
|
||||
getQueryParamsForEnterDetails,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { LangParams, PageArgs, SearchParams } from "@/types/params"
|
||||
|
||||
export default async function SummaryPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, SearchParams<SelectRateSearchParams>>) {
|
||||
const selectRoomParams = new URLSearchParams(searchParams)
|
||||
const { hotel, rooms, fromDate, toDate } =
|
||||
getQueryParamsForEnterDetails(selectRoomParams)
|
||||
|
||||
const {
|
||||
adults,
|
||||
children,
|
||||
roomTypeCode,
|
||||
rateCode,
|
||||
packages: packageCodes,
|
||||
} = rooms[0] // TODO: Handle multiple rooms
|
||||
|
||||
const availability = await getSelectedRoomAvailability({
|
||||
hotelId: hotel,
|
||||
adults,
|
||||
children: children ? generateChildrenString(children) : undefined,
|
||||
roomStayStartDate: fromDate,
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
const user = await getProfileSafely()
|
||||
|
||||
const packages = packageCodes
|
||||
? await getPackages({
|
||||
hotelId: hotel,
|
||||
startDate: fromDate,
|
||||
endDate: toDate,
|
||||
adults,
|
||||
children: children?.length,
|
||||
packageCodes,
|
||||
})
|
||||
: null
|
||||
|
||||
if (!availability || !availability.selectedRoom) {
|
||||
console.error("No hotel or availability data", availability)
|
||||
// TODO: handle this case
|
||||
redirect(selectRate(params.lang))
|
||||
}
|
||||
|
||||
const prices =
|
||||
user && availability.memberRate
|
||||
? {
|
||||
local: {
|
||||
price: availability.memberRate.localPrice.pricePerStay,
|
||||
currency: availability.memberRate.localPrice.currency,
|
||||
},
|
||||
euro: availability.memberRate.requestedPrice
|
||||
? {
|
||||
price: availability.memberRate.requestedPrice.pricePerStay,
|
||||
currency: availability.memberRate.requestedPrice.currency,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
: {
|
||||
local: {
|
||||
price: availability.publicRate.localPrice.pricePerStay,
|
||||
currency: availability.publicRate.localPrice.currency,
|
||||
},
|
||||
euro: availability.publicRate?.requestedPrice
|
||||
? {
|
||||
price: availability.publicRate?.requestedPrice.pricePerStay,
|
||||
currency: availability.publicRate?.requestedPrice.currency,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.mobileSummary}>
|
||||
<SummaryBottomSheet>
|
||||
<div className={styles.summary}>
|
||||
<Summary
|
||||
showMemberPrice={!!(user && availability.memberRate)}
|
||||
room={{
|
||||
roomType: availability.selectedRoom.roomType,
|
||||
localPrice: prices.local,
|
||||
euroPrice: prices.euro,
|
||||
adults,
|
||||
children,
|
||||
rateDetails: availability.rateDetails,
|
||||
cancellationText: availability.cancellationText,
|
||||
packages,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SummaryBottomSheet>
|
||||
</div>
|
||||
<div className={styles.desktopSummary}>
|
||||
<div className={styles.hider} />
|
||||
<div className={styles.summary}>
|
||||
<Summary
|
||||
showMemberPrice={!!(user && availability.memberRate)}
|
||||
room={{
|
||||
roomType: availability.selectedRoom.roomType,
|
||||
localPrice: prices.local,
|
||||
euroPrice: prices.euro,
|
||||
adults,
|
||||
children,
|
||||
rateDetails: availability.rateDetails,
|
||||
cancellationText: availability.cancellationText,
|
||||
packages,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.shadow} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import {
|
||||
getCreditCardsSafely,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
export function preload() {
|
||||
void getProfileSafely()
|
||||
void getCreditCardsSafely()
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Due to css import issues with parallel routes we are forced to
|
||||
* use a regular css file and import it in the page.tsx
|
||||
* This is addressed in Next 15: https://github.com/vercel/next.js/pull/66300
|
||||
*/
|
||||
|
||||
.enter-details-layout {
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
}
|
||||
|
||||
.enter-details-layout__container {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3) var(--Spacing-x9);
|
||||
/* simulates padding on viewport smaller than --max-width-navigation */
|
||||
}
|
||||
|
||||
.enter-details-layout__content {
|
||||
margin: var(--Spacing-x3) var(--Spacing-x2) 0;
|
||||
}
|
||||
|
||||
.enter-details-layout__summaryContainer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.enter-details-layout__container {
|
||||
grid-template-columns: 1fr 340px;
|
||||
grid-template-rows: auto 1fr;
|
||||
margin: var(--Spacing-x5) auto 0;
|
||||
width: min(
|
||||
calc(100dvw - (var(--Spacing-x2) * 2)),
|
||||
var(--max-width-navigation)
|
||||
);
|
||||
}
|
||||
|
||||
.enter-details-layout__summaryContainer {
|
||||
position: static;
|
||||
display: grid;
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/-1;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
import DetailsProvider from "@/providers/DetailsProvider"
|
||||
|
||||
import { preload } from "./_preload"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export default async function StepLayout({
|
||||
children,
|
||||
hotelHeader,
|
||||
params,
|
||||
summary,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams> & {
|
||||
hotelHeader: React.ReactNode
|
||||
summary: React.ReactNode
|
||||
}
|
||||
>) {
|
||||
setLang(params.lang)
|
||||
preload()
|
||||
|
||||
const user = await getProfileSafely()
|
||||
|
||||
return (
|
||||
<DetailsProvider isMember={!!user}>
|
||||
<main className="enter-details-layout__layout">
|
||||
{hotelHeader}
|
||||
<div className={"enter-details-layout__container"}>
|
||||
<div className={"enter-details-layout__content"}>{children}</div>
|
||||
<aside className={"enter-details-layout__summaryContainer"}>
|
||||
{summary}
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
</DetailsProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3) var(--Spacing-x9);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: var(--Spacing-x3) var(--Spacing-x2) 0;
|
||||
}
|
||||
|
||||
.summary {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.container {
|
||||
grid-template-columns: 1fr 340px;
|
||||
grid-template-rows: auto 1fr;
|
||||
margin: var(--Spacing-x5) auto 0;
|
||||
/* simulates padding on viewport smaller than --max-width-navigation */
|
||||
width: min(
|
||||
calc(100dvw - (var(--Spacing-x2) * 2)),
|
||||
var(--max-width-navigation)
|
||||
);
|
||||
}
|
||||
|
||||
.summary {
|
||||
position: static;
|
||||
display: grid;
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/-1;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import "./enterDetailsLayout.css"
|
||||
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import {
|
||||
getBreakfastPackages,
|
||||
getCreditCardsSafely,
|
||||
getHotelData,
|
||||
getPackages,
|
||||
getProfileSafely,
|
||||
getSelectedRoomAvailability,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
@@ -13,16 +13,21 @@ import {
|
||||
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 HistoryStateManager from "@/components/HotelReservation/EnterDetails/HistoryStateManager"
|
||||
import Payment from "@/components/HotelReservation/EnterDetails/Payment"
|
||||
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
||||
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
||||
import Summary from "@/components/HotelReservation/EnterDetails/Summary"
|
||||
import {
|
||||
generateChildrenString,
|
||||
getQueryParamsForEnterDetails,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import { getIntl } from "@/i18n"
|
||||
import StepsProvider from "@/providers/StepsProvider"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
@@ -36,60 +41,78 @@ export default async function StepPage({
|
||||
params: { lang },
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, SelectRateSearchParams & { step: StepEnum }>) {
|
||||
if (!isValidStep(searchParams.step)) {
|
||||
return notFound()
|
||||
}
|
||||
setLang(lang)
|
||||
|
||||
const intl = await getIntl()
|
||||
const selectRoomParams = new URLSearchParams(searchParams)
|
||||
// Deleting step to avoid double searchparams after rewrite
|
||||
selectRoomParams.delete("step")
|
||||
const searchParamsString = selectRoomParams.toString()
|
||||
const booking = getQueryParamsForEnterDetails(selectRoomParams)
|
||||
|
||||
const {
|
||||
hotel: hotelId,
|
||||
rooms,
|
||||
rooms: [
|
||||
{ adults, children, roomTypeCode, rateCode, packages: packageCodes },
|
||||
], // TODO: Handle multiple rooms
|
||||
fromDate,
|
||||
toDate,
|
||||
} = getQueryParamsForEnterDetails(selectRoomParams)
|
||||
|
||||
const {
|
||||
adults,
|
||||
children,
|
||||
roomTypeCode,
|
||||
rateCode,
|
||||
packages: packageCodes,
|
||||
} = rooms[0] // TODO: Handle multiple rooms
|
||||
} = booking
|
||||
|
||||
const childrenAsString = children && generateChildrenString(children)
|
||||
|
||||
const breakfastInput = { adults, fromDate, hotelId, toDate }
|
||||
void getBreakfastPackages(breakfastInput)
|
||||
void getSelectedRoomAvailability({
|
||||
hotelId,
|
||||
const selectedRoomAvailabilityInput = {
|
||||
adults,
|
||||
children: childrenAsString,
|
||||
hotelId,
|
||||
packageCodes,
|
||||
rateCode,
|
||||
roomStayStartDate: fromDate,
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
}
|
||||
|
||||
const roomAvailability = await getSelectedRoomAvailability({
|
||||
hotelId,
|
||||
adults,
|
||||
children: childrenAsString,
|
||||
roomStayStartDate: fromDate,
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
void getProfileSafely()
|
||||
void getCreditCardsSafely()
|
||||
void getBreakfastPackages(breakfastInput)
|
||||
void getSelectedRoomAvailability(selectedRoomAvailabilityInput)
|
||||
if (packageCodes?.length) {
|
||||
void getPackages({
|
||||
adults,
|
||||
children: children?.length,
|
||||
endDate: toDate,
|
||||
hotelId,
|
||||
packageCodes,
|
||||
startDate: fromDate,
|
||||
})
|
||||
}
|
||||
|
||||
const packages = packageCodes
|
||||
? await getPackages({
|
||||
adults,
|
||||
children: children?.length,
|
||||
endDate: toDate,
|
||||
hotelId,
|
||||
packageCodes,
|
||||
startDate: fromDate,
|
||||
})
|
||||
: null
|
||||
|
||||
const roomAvailability = await getSelectedRoomAvailability(
|
||||
selectedRoomAvailabilityInput
|
||||
)
|
||||
const hotelData = await getHotelData({
|
||||
hotelId,
|
||||
language: lang,
|
||||
isCardOnlyPayment: roomAvailability?.mustBeGuaranteed,
|
||||
language: lang,
|
||||
})
|
||||
const breakfastPackages = await getBreakfastPackages(breakfastInput)
|
||||
const user = await getProfileSafely()
|
||||
const savedCreditCards = await getCreditCardsSafely()
|
||||
|
||||
if (!isValidStep(searchParams.step) || !hotelData || !roomAvailability) {
|
||||
if (!hotelData || !roomAvailability) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
@@ -121,66 +144,96 @@ export default async function StepPage({
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<StepsProvider
|
||||
<EnterDetailsProvider
|
||||
bedTypes={roomAvailability.bedTypes}
|
||||
booking={booking}
|
||||
breakfastPackages={breakfastPackages}
|
||||
isMember={!!user}
|
||||
searchParams={searchParamsString}
|
||||
packages={packages}
|
||||
roomRate={{
|
||||
memberRate: roomAvailability.memberRate,
|
||||
publicRate: roomAvailability.publicRate,
|
||||
}}
|
||||
searchParamsStr={selectRoomParams.toString()}
|
||||
step={searchParams.step}
|
||||
user={user}
|
||||
>
|
||||
<section>
|
||||
<HistoryStateManager />
|
||||
<SelectedRoom
|
||||
hotelId={hotelId}
|
||||
room={roomAvailability.selectedRoom}
|
||||
rateDescription={roomAvailability.cancellationText}
|
||||
/>
|
||||
<main>
|
||||
<HotelHeader hotelData={hotelData} />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<section>
|
||||
<HistoryStateManager />
|
||||
<SelectedRoom
|
||||
hotelId={hotelId}
|
||||
room={roomAvailability.selectedRoom}
|
||||
rateDescription={roomAvailability.cancellationText}
|
||||
/>
|
||||
|
||||
{/* TODO: How to handle no beds found? */}
|
||||
{roomAvailability.bedTypes ? (
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Select bed" })}
|
||||
step={StepEnum.selectBed}
|
||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||
>
|
||||
<BedType bedTypes={roomAvailability.bedTypes} />
|
||||
</SectionAccordion>
|
||||
) : null}
|
||||
{/* TODO: How to handle no beds found? */}
|
||||
{roomAvailability.bedTypes ? (
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Select bed" })}
|
||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||
step={StepEnum.selectBed}
|
||||
>
|
||||
<BedType bedTypes={roomAvailability.bedTypes} />
|
||||
</SectionAccordion>
|
||||
) : null}
|
||||
|
||||
{breakfastPackages?.length ? (
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Food options" })}
|
||||
step={StepEnum.breakfast}
|
||||
label={intl.formatMessage({ id: "Select breakfast options" })}
|
||||
>
|
||||
<Breakfast packages={breakfastPackages} />
|
||||
</SectionAccordion>
|
||||
) : null}
|
||||
{breakfastPackages?.length ? (
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Food options" })}
|
||||
label={intl.formatMessage({ id: "Select breakfast options" })}
|
||||
step={StepEnum.breakfast}
|
||||
>
|
||||
<Breakfast packages={breakfastPackages} />
|
||||
</SectionAccordion>
|
||||
) : null}
|
||||
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Details" })}
|
||||
step={StepEnum.details}
|
||||
label={intl.formatMessage({ id: "Enter your details" })}
|
||||
>
|
||||
<Details user={user} memberPrice={memberPrice} />
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Details" })}
|
||||
step={StepEnum.details}
|
||||
label={intl.formatMessage({ id: "Enter your details" })}
|
||||
>
|
||||
<Details user={user} memberPrice={memberPrice} />
|
||||
</SectionAccordion>
|
||||
|
||||
<SectionAccordion
|
||||
header={mustBeGuaranteed ? paymentGuarantee : payment}
|
||||
step={StepEnum.payment}
|
||||
label={mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod}
|
||||
>
|
||||
<Payment
|
||||
roomPrice={roomPrice}
|
||||
otherPaymentOptions={
|
||||
hotelData.data.attributes.merchantInformationData
|
||||
.alternatePaymentOptions
|
||||
}
|
||||
savedCreditCards={savedCreditCards}
|
||||
mustBeGuaranteed={mustBeGuaranteed}
|
||||
/>
|
||||
</SectionAccordion>
|
||||
</section>
|
||||
</StepsProvider>
|
||||
<SectionAccordion
|
||||
header={mustBeGuaranteed ? paymentGuarantee : payment}
|
||||
step={StepEnum.payment}
|
||||
label={
|
||||
mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod
|
||||
}
|
||||
>
|
||||
<Suspense>
|
||||
<Payment
|
||||
user={user}
|
||||
roomPrice={roomPrice}
|
||||
otherPaymentOptions={
|
||||
hotelData.data.attributes.merchantInformationData
|
||||
.alternatePaymentOptions
|
||||
}
|
||||
savedCreditCards={savedCreditCards}
|
||||
mustBeGuaranteed={mustBeGuaranteed}
|
||||
/>
|
||||
</Suspense>
|
||||
</SectionAccordion>
|
||||
</section>
|
||||
</div>
|
||||
<aside className={styles.summary}>
|
||||
<Summary
|
||||
adults={adults}
|
||||
fromDate={fromDate}
|
||||
hotelId={hotelId}
|
||||
kids={children}
|
||||
packageCodes={packageCodes}
|
||||
rateCode={rateCode}
|
||||
roomTypeCode={roomTypeCode}
|
||||
toDate={toDate}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
</EnterDetailsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import TrpcProvider from "@/lib/trpc/Provider"
|
||||
import TokenRefresher from "@/components/Auth/TokenRefresher"
|
||||
import AdobeSDKScript from "@/components/Current/AdobeSDKScript"
|
||||
import VwoScript from "@/components/Current/VwoScript"
|
||||
import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner"
|
||||
import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
|
||||
import { preloadUserTracking } from "@/components/TrackingSDK"
|
||||
import { getIntl } from "@/i18n"
|
||||
@@ -64,6 +65,7 @@ export default async function RootLayout({
|
||||
{footer}
|
||||
<ToastHandler />
|
||||
<TokenRefresher />
|
||||
<StorageCleaner />
|
||||
</TrpcProvider>
|
||||
</ServerIntlProvider>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user