Merge branch 'master' into feature/tracking
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function Loading() {
|
||||
return <LoadingSpinner />
|
||||
return <LoadingSpinner fullPage />
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { headers } from "next/headers"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { isSignupPage } from "@/constants/routes/signup"
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import HotelPage from "@/components/ContentType/HotelPage"
|
||||
@@ -22,17 +24,33 @@ export default function ContentTypePage({
|
||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||
setLang(params.lang)
|
||||
|
||||
const pathname = headers().get("x-pathname") || ""
|
||||
|
||||
switch (params.contentType) {
|
||||
case "collection-page":
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return notFound()
|
||||
}
|
||||
return <CollectionPage />
|
||||
case "content-page":
|
||||
case "content-page": {
|
||||
const isSignupRoute = isSignupPage(pathname)
|
||||
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return notFound()
|
||||
// Hide content pages for next release for non-signup routes.
|
||||
if (!isSignupRoute) {
|
||||
return notFound()
|
||||
}
|
||||
}
|
||||
|
||||
if (!env.SHOW_SIGNUP_FLOW) {
|
||||
// Hide content pages for signup routes when signup flow is disabled.
|
||||
if (isSignupRoute) {
|
||||
return notFound()
|
||||
}
|
||||
}
|
||||
|
||||
return <ContentPage />
|
||||
}
|
||||
case "loyalty-page":
|
||||
return <LoyaltyPage />
|
||||
case "hotel-page":
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
.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,25 +1,39 @@
|
||||
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 } = rooms[0] // TODO: Handle multiple rooms
|
||||
const {
|
||||
adults,
|
||||
children,
|
||||
roomTypeCode,
|
||||
rateCode,
|
||||
packages: packageCodes,
|
||||
} = rooms[0] // TODO: Handle multiple rooms
|
||||
|
||||
const availability = await getSelectedRoomAvailability({
|
||||
hotelId: hotel,
|
||||
@@ -29,49 +43,88 @@ export default async function SummaryPage({
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
const user = await getProfileSafely()
|
||||
|
||||
if (!availability) {
|
||||
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
|
||||
return null
|
||||
redirect(selectRate[params.lang])
|
||||
}
|
||||
|
||||
const prices =
|
||||
user && availability.memberRate
|
||||
? {
|
||||
local: {
|
||||
price: availability.memberRate?.localPrice.pricePerStay,
|
||||
currency: availability.memberRate?.localPrice.currency,
|
||||
price: availability.memberRate.localPrice.pricePerStay,
|
||||
currency: availability.memberRate.localPrice.currency,
|
||||
},
|
||||
euro: {
|
||||
price: availability.memberRate?.requestedPrice?.pricePerStay,
|
||||
currency: availability.memberRate?.requestedPrice?.currency,
|
||||
price: availability.memberRate.requestedPrice.pricePerStay,
|
||||
currency: availability.memberRate.requestedPrice.currency,
|
||||
},
|
||||
}
|
||||
: {
|
||||
local: {
|
||||
price: availability.publicRate?.localPrice.pricePerStay,
|
||||
currency: availability.publicRate?.localPrice.currency,
|
||||
price: availability.publicRate.localPrice.pricePerStay,
|
||||
currency: availability.publicRate.localPrice.currency,
|
||||
},
|
||||
euro: {
|
||||
price: availability.publicRate?.requestedPrice?.pricePerStay,
|
||||
currency: availability.publicRate?.requestedPrice?.currency,
|
||||
price: availability.publicRate.requestedPrice.pricePerStay,
|
||||
currency: availability.publicRate.requestedPrice.currency,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<Summary
|
||||
showMemberPrice={!!(user && availability.memberRate)}
|
||||
room={{
|
||||
roomType: availability.selectedRoom.roomType,
|
||||
localPrice: prices.local,
|
||||
euroPrice: prices.euro,
|
||||
adults,
|
||||
children,
|
||||
cancellationText: availability.cancellationText,
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<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,
|
||||
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,
|
||||
cancellationText: availability.cancellationText,
|
||||
packages,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.shadow} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,94 +8,38 @@
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
}
|
||||
|
||||
.enter-details-layout__content {
|
||||
.enter-details-layout__container {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3) var(--Spacing-x9);
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
.enter-details-layout__content {
|
||||
margin: var(--Spacing-x3) var(--Spacing-x2) 0;
|
||||
}
|
||||
|
||||
.enter-details-layout__summaryContainer {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1/-1;
|
||||
}
|
||||
|
||||
.enter-details-layout__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;
|
||||
}
|
||||
|
||||
.enter-details-layout__hider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.enter-details-layout__shadow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.enter-details-layout__summaryContainer {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
margin-top: calc(0px - var(--Spacing-x9));
|
||||
}
|
||||
|
||||
.enter-details-layout__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;
|
||||
}
|
||||
|
||||
.enter-details-layout__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;
|
||||
}
|
||||
|
||||
.enter-details-layout__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;
|
||||
}
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.enter-details-layout__summary {
|
||||
top: calc(
|
||||
var(--booking-widget-desktop-height) + var(--Spacing-x2) +
|
||||
var(--Spacing-x-half)
|
||||
.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__hider {
|
||||
top: calc(var(--booking-widget-desktop-height) - 6px);
|
||||
.enter-details-layout__summaryContainer {
|
||||
position: static;
|
||||
display: grid;
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/-1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,10 @@ export default async function StepLayout({
|
||||
<EnterDetailsProvider step={params.step} isMember={!!user}>
|
||||
<main className="enter-details-layout__layout">
|
||||
{hotelHeader}
|
||||
<div className={"enter-details-layout__content"}>
|
||||
{children}
|
||||
<aside className="enter-details-layout__summaryContainer">
|
||||
<div className="enter-details-layout__hider" />
|
||||
<div className="enter-details-layout__summary">{summary}</div>
|
||||
<div className="enter-details-layout__shadow" />
|
||||
<div className={"enter-details-layout__container"}>
|
||||
<div className={"enter-details-layout__content"}>{children}</div>
|
||||
<aside className={"enter-details-layout__summaryContainer"}>
|
||||
{summary}
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -46,7 +46,13 @@ export default async function StepPage({
|
||||
toDate,
|
||||
} = getQueryParamsForEnterDetails(selectRoomParams)
|
||||
|
||||
const { adults, children, roomTypeCode, rateCode } = rooms[0] // TODO: Handle multiple rooms
|
||||
const {
|
||||
adults,
|
||||
children,
|
||||
roomTypeCode,
|
||||
rateCode,
|
||||
packages: packageCodes,
|
||||
} = rooms[0] // TODO: Handle multiple rooms
|
||||
|
||||
const childrenAsString = children && generateChildrenString(children)
|
||||
|
||||
@@ -60,12 +66,9 @@ export default async function StepPage({
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
|
||||
const hotelData = await getHotelData({
|
||||
hotelId,
|
||||
language: lang,
|
||||
})
|
||||
const roomAvailability = await getSelectedRoomAvailability({
|
||||
hotelId,
|
||||
adults,
|
||||
@@ -74,6 +77,12 @@ export default async function StepPage({
|
||||
roomStayEndDate: toDate,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
})
|
||||
const hotelData = await getHotelData({
|
||||
hotelId,
|
||||
language: lang,
|
||||
isCardOnlyPayment: roomAvailability?.mustBeGuaranteed,
|
||||
})
|
||||
const breakfastPackages = await getBreakfastPackages(breakfastInput)
|
||||
const user = await getProfileSafely()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
|
||||
import { env } from "@/env/server"
|
||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
|
||||
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
|
||||
import {
|
||||
generateChildrenString,
|
||||
@@ -11,11 +12,7 @@ import {
|
||||
import { MapModal } from "@/components/MapModal"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import {
|
||||
fetchAvailableHotels,
|
||||
getCentralCoordinates,
|
||||
getHotelPins,
|
||||
} from "../../utils"
|
||||
import { fetchAvailableHotels } from "../../utils"
|
||||
|
||||
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
@@ -61,16 +58,12 @@ export default async function SelectHotelMapPage({
|
||||
|
||||
const hotelPins = getHotelPins(hotels)
|
||||
|
||||
const centralCoordinates = getCentralCoordinates(hotelPins)
|
||||
|
||||
return (
|
||||
<MapModal>
|
||||
<SelectHotelMap
|
||||
apiKey={googleMapsApiKey}
|
||||
coordinates={centralCoordinates}
|
||||
hotelPins={hotelPins}
|
||||
mapId={googleMapId}
|
||||
isModal={true}
|
||||
hotels={hotels}
|
||||
/>
|
||||
</MapModal>
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
.main {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x3);
|
||||
padding: var(--Spacing-x4) var(--Spacing-x4) 0 var(--Spacing-x4);
|
||||
padding: 0 var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x2);
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
min-height: 100dvh;
|
||||
flex-direction: column;
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
padding: var(--Spacing-x4) var(--Spacing-x5) var(--Spacing-x3)
|
||||
var(--Spacing-x5);
|
||||
justify-content: space-between;
|
||||
max-width: var(--max-width);
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2) 0 var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.cityInformation {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.sideBar {
|
||||
@@ -38,7 +40,31 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hotelList {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.main {
|
||||
padding: var(--Spacing-x5);
|
||||
}
|
||||
.header {
|
||||
display: block;
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
padding: var(--Spacing-x4) var(--Spacing-x5) var(--Spacing-x3)
|
||||
var(--Spacing-x5);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
max-width: var(--max-width-navigation);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.link {
|
||||
display: flex;
|
||||
padding-bottom: var(--Spacing-x6);
|
||||
@@ -50,13 +76,6 @@
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
}
|
||||
.mapLinkText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x0);
|
||||
}
|
||||
.main {
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x5);
|
||||
|
||||
@@ -19,7 +19,11 @@ import {
|
||||
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import { ChevronRightIcon } from "@/components/Icons"
|
||||
import StaticMap from "@/components/Maps/StaticMap"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
@@ -32,6 +36,7 @@ import {
|
||||
TrackingSDKHotelInfo,
|
||||
TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function SelectHotelPage({
|
||||
@@ -99,17 +104,44 @@ export default async function SelectHotelPage({
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<div>{city.name}</div>
|
||||
<HotelSorter />
|
||||
<div className={styles.title}>
|
||||
<div className={styles.cityInformation}>
|
||||
<Subtitle>{city.name}</Subtitle>
|
||||
<Preamble>{hotels.length} hotels</Preamble>
|
||||
</div>
|
||||
<HotelSorter />
|
||||
</div>
|
||||
<MobileMapButtonContainer city={searchParams.city} />
|
||||
</header>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.sideBar}>
|
||||
<Link
|
||||
className={styles.link}
|
||||
color="burgundy"
|
||||
href={selectHotelMap[params.lang]}
|
||||
keepSearchParams
|
||||
>
|
||||
{hotels.length > 0 ? ( // TODO: Temp fix until API returns hotels that are not available
|
||||
<Link
|
||||
className={styles.link}
|
||||
color="burgundy"
|
||||
href={selectHotelMap[params.lang]}
|
||||
keepSearchParams
|
||||
>
|
||||
<div className={styles.mapContainer}>
|
||||
<StaticMap
|
||||
city={searchParams.city}
|
||||
width={340}
|
||||
height={180}
|
||||
zoomLevel={11}
|
||||
mapType="roadmap"
|
||||
altText={`Map of ${searchParams.city} city center`}
|
||||
/>
|
||||
<Button wrapping size="medium" intent="text" theme="base">
|
||||
{intl.formatMessage({ id: "See map" })}
|
||||
<ChevronRightIcon
|
||||
color="baseButtonTextOnFillNormal"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div className={styles.mapContainer}>
|
||||
<StaticMap
|
||||
city={searchParams.city}
|
||||
@@ -119,16 +151,22 @@ export default async function SelectHotelPage({
|
||||
mapType="roadmap"
|
||||
altText={`Map of ${searchParams.city} city center`}
|
||||
/>
|
||||
<div className={styles.mapLinkText}>
|
||||
{intl.formatMessage({ id: "Show map" })}
|
||||
<ChevronRightIcon color="burgundy" width={20} height={20} />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<MobileMapButtonContainer city={searchParams.city} />
|
||||
)}
|
||||
<HotelFilter filters={filterList} />
|
||||
</div>
|
||||
<HotelCardListing hotelData={hotels} />
|
||||
<div className={styles.hotelList}>
|
||||
{!hotels.length && (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No availability" })}
|
||||
text={intl.formatMessage({
|
||||
id: "There are no rooms available that match your request.",
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<HotelCardListing hotelData={hotels} />
|
||||
</div>
|
||||
<TrackingSDK
|
||||
pageData={pageTrackingData}
|
||||
hotelInfo={hotelsTrackingData}
|
||||
|
||||
@@ -87,38 +87,3 @@ export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters {
|
||||
{ facilityFilters: [], surroundingsFilters: [] }
|
||||
)
|
||||
}
|
||||
|
||||
export function getHotelPins(hotels: HotelData[]): HotelPin[] {
|
||||
return hotels.map((hotel) => ({
|
||||
coordinates: {
|
||||
lat: hotel.hotelData.location.latitude,
|
||||
lng: hotel.hotelData.location.longitude,
|
||||
},
|
||||
name: hotel.hotelData.name,
|
||||
publicPrice: hotel.price?.regularAmount ?? null,
|
||||
memberPrice: hotel.price?.memberAmount ?? null,
|
||||
currency: hotel.price?.currency || null,
|
||||
images: [
|
||||
hotel.hotelData.hotelContent.images,
|
||||
...(hotel.hotelData.gallery?.heroImages ?? []),
|
||||
],
|
||||
amenities: hotel.hotelData.detailedFacilities.slice(0, 3),
|
||||
ratings: hotel.hotelData.ratings?.tripAdvisor.rating ?? null,
|
||||
}))
|
||||
}
|
||||
|
||||
export function getCentralCoordinates(hotels: HotelPin[]) {
|
||||
const centralCoordinates = hotels.reduce(
|
||||
(acc, pin) => {
|
||||
acc.lat += pin.coordinates.lat
|
||||
acc.lng += pin.coordinates.lng
|
||||
return acc
|
||||
},
|
||||
{ lat: 0, lng: 0 }
|
||||
)
|
||||
|
||||
centralCoordinates.lat /= hotels.length
|
||||
centralCoordinates.lng /= hotels.length
|
||||
|
||||
return centralCoordinates
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function SelectRatePage({
|
||||
params,
|
||||
@@ -49,11 +49,11 @@ export default async function SelectRatePage({
|
||||
searchParams.fromDate &&
|
||||
dt(searchParams.fromDate).isAfter(dt().subtract(1, "day"))
|
||||
? searchParams.fromDate
|
||||
: dt().utc().format("YYYY-MM-D")
|
||||
: 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-D")
|
||||
: 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
|
||||
@@ -94,9 +94,16 @@ export default async function SelectRatePage({
|
||||
|
||||
const roomCategories = hotelData?.included
|
||||
|
||||
const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce(
|
||||
(acc, room) => {
|
||||
return acc && room.status === "NotAvailable"
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<HotelInfoCard hotelData={hotelData} />
|
||||
<HotelInfoCard hotelData={hotelData} noAvailability={noRoomsAvailable} />
|
||||
<Rooms
|
||||
roomsAvailability={roomsAvailability}
|
||||
roomCategories={roomCategories ?? []}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function Loading() {
|
||||
return <LoadingSpinner />
|
||||
return <LoadingSpinner fullPage />
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
import styles from "./loading.module.css"
|
||||
import { BookingWidgetSkeleton } from "@/components/BookingWidget/Client"
|
||||
|
||||
export default function LoadingBookingWidget() {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
)
|
||||
return <BookingWidgetSkeleton />
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import CurrentLoadingSpinner from "@/components/Current/LoadingSpinner"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import { FooterDetailsSkeleton } from "@/components/Footer/Details"
|
||||
import { FooterNavigationSkeleton } from "@/components/Footer/Navigation"
|
||||
|
||||
export default function LoadingFooter() {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return <CurrentLoadingSpinner />
|
||||
}
|
||||
return <LoadingSpinner />
|
||||
return (
|
||||
<footer>
|
||||
<FooterNavigationSkeleton />
|
||||
<FooterDetailsSkeleton />
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function Loading() {
|
||||
return <LoadingSpinner />
|
||||
return <LoadingSpinner fullPage />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user