diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx index 58a216006..75101475a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx @@ -14,7 +14,10 @@ export default async function HotelHeader({ if (!searchParams.hotel) { redirect(home) } - const hotel = await getHotelData(searchParams.hotel, params.lang) + const hotel = await getHotelData({ + hotelId: searchParams.hotel, + language: params.lang, + }) if (!hotel?.data) { redirect(home) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx index 13b770699..deca843c3 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx @@ -13,7 +13,10 @@ export default async function HotelSidePeek({ if (!searchParams.hotel) { redirect(`/${params.lang}`) } - const hotel = await getHotelData(searchParams.hotel, params.lang) + const hotel = await getHotelData({ + hotelId: searchParams.hotel, + language: params.lang, + }) if (!hotel?.data) { redirect(`/${params.lang}`) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx new file mode 100644 index 000000000..38444447f --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx @@ -0,0 +1,78 @@ +import { notFound } from "next/navigation" + +import { + getProfileSafely, + getSelectedRoomAvailability, +} from "@/lib/trpc/memoizedRequests" + +import Summary from "@/components/HotelReservation/EnterDetails/Summary" +import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" + +import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" +import { LangParams, PageArgs, SearchParams } from "@/types/params" + +export default async function SummaryPage({ + params, + searchParams, +}: PageArgs>) { + const selectRoomParams = new URLSearchParams(searchParams) + const { hotel, adults, children, roomTypeCode, rateCode, fromDate, toDate } = + getQueryParamsForEnterDetails(selectRoomParams) + + if (!roomTypeCode || !rateCode) { + console.log("No roomTypeCode or rateCode") + return notFound() + } + + const availability = await getSelectedRoomAvailability({ + hotelId: parseInt(hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) + const user = await getProfileSafely() + + if (!availability) { + console.error("No hotel or availability data", availability) + // TODO: handle this case + return null + } + + const prices = user + ? { + local: { + price: availability.memberRate?.localPrice.pricePerStay, + currency: availability.memberRate?.localPrice.currency, + }, + euro: { + price: availability.memberRate?.requestedPrice?.pricePerStay, + currency: availability.memberRate?.requestedPrice?.currency, + }, + } + : { + local: { + price: availability.publicRate?.localPrice.pricePerStay, + currency: availability.publicRate?.localPrice.currency, + }, + euro: { + price: availability.publicRate?.requestedPrice?.pricePerStay, + currency: availability.publicRate?.requestedPrice?.currency, + }, + } + + return ( + + ) +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.module.css b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.module.css index 4f337ccb2..296eea04d 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.module.css @@ -1,5 +1,4 @@ .layout { - min-height: 100dvh; background-color: var(--Scandic-Brand-Warm-White); } @@ -9,7 +8,6 @@ grid-template-columns: 1fr 340px; grid-template-rows: auto 1fr; margin: var(--Spacing-x5) auto 0; - padding-top: var(--Spacing-x6); /* simulates padding on viewport smaller than --max-width-navigation */ width: min( calc(100dvw - (var(--Spacing-x2) * 2)), @@ -17,8 +15,81 @@ ); } -.summary { - align-self: flex-start; +.summaryContainer { grid-column: 2 / 3; grid-row: 1/-1; } + +.summary { + background-color: var(--Main-Grey-White); + + border-color: var(--Primary-Light-On-Surface-Divider-subtle); + border-style: solid; + border-width: 1px; + border-radius: var(--Corner-radius-Large); + + z-index: 1; +} + +.hider { + display: none; +} + +.shadow { + display: none; +} + +@media screen and (min-width: 950px) { + .summaryContainer { + 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(--booking-widget-desktop-height) + var(--Spacing-x-one-and-half) + ); + margin-top: calc(0px - var(--Spacing-x9)); + border-bottom: none; + border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; + } + + .hider { + display: block; + background-color: var(--Scandic-Brand-Warm-White); + position: sticky; + margin-top: var(--Spacing-x4); + top: calc( + var(--booking-widget-desktop-height) + + var(--booking-widget-desktop-height) - 6px + ); + height: 40px; + } + + .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; + } +} + +@media screen and (min-width: 1367px) { + .summary { + top: calc( + var(--booking-widget-desktop-height) + var(--Spacing-x2) + + var(--Spacing-x-half) + ); + } + + .hider { + top: calc(var(--booking-widget-desktop-height) - 6px); + } +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx index 271d19e6d..c38349b26 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx @@ -1,16 +1,16 @@ import EnterDetailsProvider from "@/components/HotelReservation/EnterDetails/Provider" import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom" -import Summary from "@/components/HotelReservation/EnterDetails/Summary" import { setLang } from "@/i18n/serverContext" import { preload } from "./page" import styles from "./layout.module.css" -import { StepEnum } from "@/types/components/enterDetails/step" +import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import type { LangParams, LayoutArgs } from "@/types/params" export default async function StepLayout({ + summary, children, hotelHeader, params, @@ -19,19 +19,21 @@ export default async function StepLayout({ LayoutArgs & { hotelHeader: React.ReactNode sidePeek: React.ReactNode - } ->) { + summary: React.ReactNode + }>) { setLang(params.lang) preload() return ( - +
{hotelHeader}
{children} -
{sidePeek} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index 9a673c26c..e5bf9f73b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -6,7 +6,9 @@ import { getHotelData, getProfileSafely, getRoomAvailability, + getSelectedRoomAvailability, } from "@/lib/trpc/memoizedRequests" +import { HotelIncludeEnum } from "@/server/routers/hotels/input" import BedType from "@/components/HotelReservation/EnterDetails/BedType" import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast" @@ -14,10 +16,11 @@ import Details from "@/components/HotelReservation/EnterDetails/Details" import HistoryStateManager from "@/components/HotelReservation/EnterDetails/HistoryStateManager" import Payment from "@/components/HotelReservation/EnterDetails/Payment" import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion" -import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { getIntl } from "@/i18n" -import { StepEnum } from "@/types/components/enterDetails/step" +import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" +import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import type { LangParams, PageArgs } from "@/types/params" export function preload() { @@ -32,35 +35,56 @@ function isValidStep(step: string): step is StepEnum { export default async function StepPage({ params, searchParams, -}: PageArgs) { - if (!searchParams.hotel) { - redirect(`/${params.lang}`) - } - void getBreakfastPackages(searchParams.hotel) - const stepParams = new URLSearchParams(searchParams) - const paramsObject = getHotelReservationQueryParams(stepParams) - void getRoomAvailability({ - hotelId: paramsObject.hotel, - adults: paramsObject.room[0].adults, - roomStayStartDate: paramsObject.fromDate, - roomStayEndDate: paramsObject.toDate, - }) - const intl = await getIntl() +}: PageArgs) { + const { lang } = params - const hotel = await getHotelData(searchParams.hotel, params.lang) + void getBreakfastPackages(searchParams.hotel) + + const intl = await getIntl() + const selectRoomParams = new URLSearchParams(searchParams) + const { + hotel: hotelId, + adults, + children, + roomTypeCode, + rateCode, + fromDate, + toDate, + } = getQueryParamsForEnterDetails(selectRoomParams) + + if (!rateCode || !roomTypeCode) { + return notFound() + } + + void getSelectedRoomAvailability({ + hotelId: parseInt(searchParams.hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) + + const hotelData = await getHotelData({ + hotelId, + language: lang, + include: [HotelIncludeEnum.RoomCategories], + }) + const roomAvailability = await getSelectedRoomAvailability({ + hotelId: parseInt(searchParams.hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) + const breakfastPackages = await getBreakfastPackages(searchParams.hotel) const user = await getProfileSafely() const savedCreditCards = await getCreditCardsSafely() - const breakfastPackages = await getBreakfastPackages(searchParams.hotel) - const roomAvailability = await getRoomAvailability({ - hotelId: paramsObject.hotel, - adults: paramsObject.room[0].adults, - roomStayStartDate: paramsObject.fromDate, - roomStayEndDate: paramsObject.toDate, - rateCode: paramsObject.room[0].ratecode, - }) - - if (!isValidStep(params.step) || !hotel || !roomAvailability) { + if (!isValidStep(params.step) || !hotelData || !roomAvailability) { return notFound() } @@ -79,16 +103,30 @@ export default async function StepPage({ id: "Select payment method", }) + const availableRoom = roomAvailability.selectedRoom?.roomType + const bedTypes = hotelData.included + ?.find((room) => room.name === availableRoom) + ?.roomTypes.map((room) => ({ + description: room.mainBed.description, + size: room.mainBed.widthRange, + value: room.code, + })) + return (
- - - + + {/* TODO: How to handle no beds found? */} + {bedTypes ? ( + + + + ) : null} + {isCalculated - ? formatter.format(awardPoints) + ? formatNumber(awardPoints) : intl.formatMessage({ id: "Points being calculated" })} ) diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 6a594199e..2acb8a397 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -11,7 +11,6 @@ import { CloseLargeIcon } from "@/components/Icons" import { debounce } from "@/utils/debounce" import { getFormattedUrlQueryParams } from "@/utils/url" -import getHotelReservationQueryParams from "../HotelReservation/SelectRate/RoomSelection/utils" import MobileToggleButton from "./MobileToggleButton" import styles from "./bookingWidget.module.css" @@ -41,10 +40,10 @@ export default function BookingWidgetClient({ const bookingWidgetSearchData: BookingWidgetSearchParams | undefined = searchParams ? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), { - adults: "number", - age: "number", - bed: "number", - }) as BookingWidgetSearchParams) + adults: "number", + age: "number", + bed: "number", + }) as BookingWidgetSearchParams) : undefined const getLocationObj = (destination: string): Location | undefined => { @@ -70,9 +69,9 @@ export default function BookingWidgetClient({ const selectedLocation = bookingWidgetSearchData ? getLocationObj( - (bookingWidgetSearchData.city ?? - bookingWidgetSearchData.hotel) as string - ) + (bookingWidgetSearchData.city ?? + bookingWidgetSearchData.hotel) as string + ) : undefined const methods = useForm({ diff --git a/components/Footer/Navigation/SecondaryNav/index.tsx b/components/Footer/Navigation/SecondaryNav/index.tsx index 42266f915..78d4f544c 100644 --- a/components/Footer/Navigation/SecondaryNav/index.tsx +++ b/components/Footer/Navigation/SecondaryNav/index.tsx @@ -18,7 +18,7 @@ export default function FooterSecondaryNav({
{appDownloads && (