Merged in feat/sw-2873-move-selecthotel-to-booking-flow (pull request #2727)
feat(SW-2873): Move select-hotel to booking flow * crude setup of select-hotel in partner-sas * wip * Fix linting * restructure tracking files * Remove dependency on trpc in tracking hooks * Move pageview tracking to common * Fix some lint and import issues * Add AlternativeHotelsPage * Add SelectHotelMapPage * Add AlternativeHotelsMapPage * remove next dependency in tracking store * Remove dependency on react in tracking hooks * move isSameBooking to booking-flow * Inject searchParamsComparator into tracking store * Move useTrackHardNavigation to common * Move useTrackSoftNavigation to common * Add TrackingSDK to partner-sas * call serverclient in layout * Remove unused css * Update types * Move HotelPin type * Fix todos * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Fix component Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function AlternativeHotelsMapPage(
|
||||||
|
props: PageArgs<LangParams>
|
||||||
|
) {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
|
const lang = await getLang()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AlternativeHotelsMapPagePrimitive
|
||||||
|
lang={lang}
|
||||||
|
searchParams={searchParams}
|
||||||
|
renderTracking={(props) => (
|
||||||
|
<TrackingSDK
|
||||||
|
hotelInfo={props.hotelsTrackingData}
|
||||||
|
pageData={props.pageTrackingData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import { type LangParams, type PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function AlternativeHotelsPage(
|
||||||
|
props: PageArgs<LangParams>
|
||||||
|
) {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
|
const lang = await getLang()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlternativeHotelsPagePrimitive
|
||||||
|
lang={lang}
|
||||||
|
searchParams={searchParams}
|
||||||
|
renderTracking={(props) => (
|
||||||
|
<TrackingSDK
|
||||||
|
hotelInfo={props.hotelsTrackingData}
|
||||||
|
pageData={props.pageTrackingData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function SelectHotelMapPage(props: PageArgs<LangParams>) {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
|
const lang = await getLang()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectHotelMapPagePrimitive
|
||||||
|
lang={lang}
|
||||||
|
searchParams={searchParams}
|
||||||
|
renderTracking={(props) => (
|
||||||
|
<TrackingSDK
|
||||||
|
hotelInfo={props.hotelsTrackingData}
|
||||||
|
pageData={props.pageTrackingData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
export default async function SelectHotelPage() {
|
import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage"
|
||||||
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
|
||||||
return <div>select-hotel</div>
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function SelectHotelPage(props: PageArgs<LangParams>) {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
|
const lang = await getLang()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectHotelPagePrimitive
|
||||||
|
lang={lang}
|
||||||
|
searchParams={searchParams}
|
||||||
|
renderTracking={(props) => (
|
||||||
|
<TrackingSDK
|
||||||
|
hotelInfo={props.hotelsTrackingData}
|
||||||
|
pageData={props.pageTrackingData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import "@scandic-hotels/design-system/normalize.css"
|
|||||||
import "@scandic-hotels/design-system/design-system-new-deprecated.css"
|
import "@scandic-hotels/design-system/design-system-new-deprecated.css"
|
||||||
import "../../globals.css"
|
import "../../globals.css"
|
||||||
|
|
||||||
|
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
||||||
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
import { TrpcProvider } from "@scandic-hotels/trpc/Provider"
|
import { TrpcProvider } from "@scandic-hotels/trpc/Provider"
|
||||||
|
|
||||||
|
import { serverClient } from "@/lib/trpc"
|
||||||
|
|
||||||
import { getMessages } from "@/i18n"
|
import { getMessages } from "@/i18n"
|
||||||
import ClientIntlProvider from "@/i18n/Provider"
|
import ClientIntlProvider from "@/i18n/Provider"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
@@ -15,6 +18,7 @@ import { setLang } from "@/i18n/serverContext"
|
|||||||
import {
|
import {
|
||||||
trackAccordionItemOpen,
|
trackAccordionItemOpen,
|
||||||
trackBookingSearchClick,
|
trackBookingSearchClick,
|
||||||
|
trackGenericEvent,
|
||||||
trackOpenSidePeek,
|
trackOpenSidePeek,
|
||||||
} from "../utils/tracking"
|
} from "../utils/tracking"
|
||||||
|
|
||||||
@@ -43,6 +47,10 @@ export default async function RootLayout(props: RootLayoutProps) {
|
|||||||
setLang(lang)
|
setLang(lang)
|
||||||
const messages = await getMessages(lang)
|
const messages = await getMessages(lang)
|
||||||
|
|
||||||
|
// TODO we need this import right now to ensure configureServerClient is called,
|
||||||
|
// but check where we do this
|
||||||
|
const _caller = await serverClient()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>{/* TODO */}</head>
|
<head>{/* TODO */}</head>
|
||||||
@@ -55,28 +63,36 @@ export default async function RootLayout(props: RootLayoutProps) {
|
|||||||
>
|
>
|
||||||
{/* TODO handle onError */}
|
{/* TODO handle onError */}
|
||||||
<TrpcProvider>
|
<TrpcProvider>
|
||||||
<BookingFlowTrackingProvider
|
<BookingFlowContextProvider
|
||||||
trackingFunctions={{
|
data={{
|
||||||
trackBookingSearchClick,
|
// TODO
|
||||||
trackAccordionItemOpen,
|
isLoggedIn: false,
|
||||||
trackOpenSidePeek,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<header
|
<BookingFlowTrackingProvider
|
||||||
style={{
|
trackingFunctions={{
|
||||||
height: 64,
|
trackBookingSearchClick,
|
||||||
backgroundColor: "dodgerblue",
|
trackAccordionItemOpen,
|
||||||
color: "white",
|
trackOpenSidePeek,
|
||||||
display: "flex",
|
trackGenericEvent,
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
<header
|
||||||
<h1>SAS</h1>
|
style={{
|
||||||
</header>
|
height: 64,
|
||||||
<main>{children}</main>
|
backgroundColor: "dodgerblue",
|
||||||
</BookingFlowTrackingProvider>
|
color: "white",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
<h1>SAS</h1>
|
||||||
|
</header>
|
||||||
|
<main>{children}</main>
|
||||||
|
</BookingFlowTrackingProvider>
|
||||||
|
</BookingFlowContextProvider>
|
||||||
</TrpcProvider>
|
</TrpcProvider>
|
||||||
</ClientIntlProvider>
|
</ClientIntlProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { BookingWidget } from "@scandic-hotels/booking-flow/BookingWidget"
|
import { BookingWidget } from "@scandic-hotels/booking-flow/BookingWidget"
|
||||||
import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||||
|
|
||||||
import { serverClient } from "@/lib/trpc"
|
|
||||||
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
@@ -14,9 +12,6 @@ type SearchParams<S = {}> = {
|
|||||||
export default async function Home(props: SearchParams<{ lang: Lang }>) {
|
export default async function Home(props: SearchParams<{ lang: Lang }>) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
|
|
||||||
// TODO we need this import right now to ensure configureServerClient is called,
|
|
||||||
// but we should ensure it's called in a layout instead.
|
|
||||||
const _caller = await serverClient()
|
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
|
|
||||||
const booking = parseBookingWidgetSearchParams(searchParams)
|
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ export function trackOpenSidePeek(input: {
|
|||||||
}) {
|
}) {
|
||||||
console.warn("TODO: Implement trackOpenSidePeek", { input })
|
console.warn("TODO: Implement trackOpenSidePeek", { input })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function trackGenericEvent(data: any) {
|
||||||
|
console.warn("TODO: Implement trackGenericEvent", { data })
|
||||||
|
}
|
||||||
|
|||||||
55
apps/partner-sas/components/TrackingSDK.tsx
Normal file
55
apps/partner-sas/components/TrackingSDK.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
import { useTrackHardNavigation } from "@scandic-hotels/common/tracking/useTrackHardNavigation"
|
||||||
|
import { useTrackSoftNavigation } from "@scandic-hotels/common/tracking/useTrackSoftNavigation"
|
||||||
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
TrackingSDKAncillaries,
|
||||||
|
TrackingSDKHotelInfo,
|
||||||
|
TrackingSDKPageData,
|
||||||
|
TrackingSDKPaymentInfo,
|
||||||
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
|
export default function TrackingSDK({
|
||||||
|
pageData,
|
||||||
|
hotelInfo,
|
||||||
|
paymentInfo,
|
||||||
|
ancillaries,
|
||||||
|
}: {
|
||||||
|
pageData: TrackingSDKPageData
|
||||||
|
hotelInfo?: TrackingSDKHotelInfo
|
||||||
|
paymentInfo?: TrackingSDKPaymentInfo
|
||||||
|
ancillaries?: TrackingSDKAncillaries
|
||||||
|
}) {
|
||||||
|
const lang = useLang()
|
||||||
|
const pathName = usePathname()
|
||||||
|
const { data, isError } = trpc.user.userTrackingInfo.useQuery({
|
||||||
|
lang,
|
||||||
|
})
|
||||||
|
|
||||||
|
const userData = isError ? ({ loginStatus: "Error" } as const) : data
|
||||||
|
|
||||||
|
useTrackHardNavigation({
|
||||||
|
pageData,
|
||||||
|
hotelInfo,
|
||||||
|
paymentInfo,
|
||||||
|
ancillaries,
|
||||||
|
userData,
|
||||||
|
pathName,
|
||||||
|
})
|
||||||
|
useTrackSoftNavigation({
|
||||||
|
pageData,
|
||||||
|
hotelInfo,
|
||||||
|
paymentInfo,
|
||||||
|
ancillaries,
|
||||||
|
userData,
|
||||||
|
pathName,
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
17
apps/partner-sas/hooks/useLang.ts
Normal file
17
apps/partner-sas/hooks/useLang.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"use client"
|
||||||
|
import { useParams } from "next/navigation"
|
||||||
|
|
||||||
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
import { languageSchema } from "@scandic-hotels/common/utils/languages"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook to get the current lang from the URL
|
||||||
|
*/
|
||||||
|
export default function useLang() {
|
||||||
|
const { lang } = useParams<{
|
||||||
|
lang: Lang
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const parsedLang = languageSchema.safeParse(lang)
|
||||||
|
return parsedLang.success ? parsedLang.data : Lang.en
|
||||||
|
}
|
||||||
19
apps/partner-sas/types/params.ts
Normal file
19
apps/partner-sas/types/params.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
|
||||||
|
type NextSearchParams = { [key: string]: string | string[] | undefined }
|
||||||
|
|
||||||
|
type SearchParams = {
|
||||||
|
searchParams: Promise<NextSearchParams>
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params<P = {}> = {
|
||||||
|
params: Promise<P>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LangParams = {
|
||||||
|
lang: Lang
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LayoutArgs<P = undefined> = P extends undefined ? {} : Params<P>
|
||||||
|
|
||||||
|
export type PageArgs<P = undefined> = LayoutArgs<P> & SearchParams
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton"
|
import { SelectHotelSkeleton } from "@scandic-hotels/booking-flow/components/SelectHotel"
|
||||||
|
|
||||||
export default function AlternativeHotelsLoading() {
|
export default function AlternativeHotelsLoading() {
|
||||||
return <SelectHotelSkeleton />
|
return <SelectHotelSkeleton />
|
||||||
|
|||||||
@@ -1,35 +1,30 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage"
|
||||||
import { Suspense } from "react"
|
|
||||||
|
|
||||||
import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
|
||||||
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
|
||||||
import { MapContainer } from "@/components/MapContainer"
|
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
export default async function SelectHotelMapPage(
|
export default async function AlternativeHotelsMapPage(
|
||||||
props: PageArgs<LangParams, NextSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
|
const lang = await getLang()
|
||||||
const booking = parseSelectHotelSearchParams(searchParams)
|
|
||||||
|
|
||||||
if (!booking) return notFound()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
<MapContainer>
|
<AlternativeHotelsMapPagePrimitive
|
||||||
<Suspense
|
lang={lang}
|
||||||
key={searchParams.hotel}
|
searchParams={searchParams}
|
||||||
fallback={<SelectHotelMapContainerSkeleton />}
|
renderTracking={(props) => (
|
||||||
>
|
<TrackingSDK
|
||||||
<SelectHotelMapContainer booking={booking} isAlternativeHotels />
|
hotelInfo={props.hotelsTrackingData}
|
||||||
</Suspense>
|
pageData={props.pageTrackingData}
|
||||||
</MapContainer>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import stringify from "json-stable-stringify-without-jsonify"
|
import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage"
|
||||||
import { cookies } from "next/headers"
|
|
||||||
import { notFound } from "next/navigation"
|
|
||||||
import { Suspense } from "react"
|
|
||||||
|
|
||||||
import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
|
||||||
import { alternativeHotelsMap } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
|
||||||
|
|
||||||
import { FamilyAndFriendsCodes } from "@/constants/booking"
|
|
||||||
|
|
||||||
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
|
|
||||||
import SelectHotel from "@/components/HotelReservation/SelectHotel"
|
|
||||||
import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
import { getSelectHotelTracking } from "@/components/HotelReservation/SelectHotel/tracking"
|
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { getIntl } from "@/i18n"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type LangParams,
|
type LangParams,
|
||||||
@@ -26,112 +13,18 @@ export default async function AlternativeHotelsPage(
|
|||||||
props: PageArgs<LangParams, NextSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const params = await props.params
|
const lang = await getLang()
|
||||||
|
|
||||||
const booking = parseSelectHotelSearchParams(searchParams)
|
|
||||||
|
|
||||||
if (!booking) return notFound()
|
|
||||||
|
|
||||||
const searchDetails = await getHotelSearchDetails(booking, true)
|
|
||||||
|
|
||||||
if (!searchDetails || !searchDetails.hotel || !searchDetails.city) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
booking.bookingCode &&
|
|
||||||
FamilyAndFriendsCodes.includes(booking.bookingCode)
|
|
||||||
) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const isInvalidFNF = cookieStore.get("sc")?.value !== "1"
|
|
||||||
|
|
||||||
if (isInvalidFNF) {
|
|
||||||
return <FnFNotAllowedAlert />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This needs to be refactored into its
|
|
||||||
// own functions
|
|
||||||
const hotels = await getHotels({
|
|
||||||
fromDate: booking.fromDate,
|
|
||||||
toDate: booking.toDate,
|
|
||||||
rooms: booking.rooms,
|
|
||||||
isAlternativeFor: searchDetails.hotel,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
city: searchDetails.city,
|
|
||||||
redemption: !!searchDetails.redemption,
|
|
||||||
})
|
|
||||||
|
|
||||||
const arrivalDate = new Date(booking.fromDate)
|
|
||||||
const departureDate = new Date(booking.toDate)
|
|
||||||
|
|
||||||
const isRedemptionAvailability = searchDetails.redemption
|
|
||||||
? hotels.some(
|
|
||||||
(hotel) => hotel.availability.productType?.redemptions?.length
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const isBookingCodeRateAvailable = booking.bookingCode
|
|
||||||
? hotels.some(
|
|
||||||
(hotel) =>
|
|
||||||
hotel.availability.bookingCode &&
|
|
||||||
hotel.availability.status === "Available"
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({
|
|
||||||
lang: params.lang,
|
|
||||||
pageId: searchDetails.hotel ? "alternative-hotels" : "select-hotel",
|
|
||||||
pageName: searchDetails.hotel
|
|
||||||
? "hotelreservation|alternative-hotels"
|
|
||||||
: "hotelreservation|select-hotel",
|
|
||||||
siteSections: searchDetails.hotel
|
|
||||||
? "hotelreservation|alternative-hotels"
|
|
||||||
: "hotelreservation|select-hotel",
|
|
||||||
arrivalDate,
|
|
||||||
departureDate,
|
|
||||||
rooms: booking.rooms,
|
|
||||||
hotelsResult: hotels?.length ?? 0,
|
|
||||||
searchTerm: searchDetails.hotel
|
|
||||||
? booking.hotelId
|
|
||||||
: searchDetails.cityIdentifier,
|
|
||||||
country: hotels?.[0]?.hotel.address.country,
|
|
||||||
hotelCity: hotels?.[0]?.hotel.address.city,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
isBookingCodeRateAvailable,
|
|
||||||
isRedemption: searchDetails.redemption,
|
|
||||||
isRedemptionAvailable: isRedemptionAvailability,
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapHref = alternativeHotelsMap(params.lang)
|
|
||||||
|
|
||||||
const intl = await getIntl()
|
|
||||||
const title = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Alternatives for {value}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: searchDetails.hotel.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const suspenseKey = stringify(searchParams)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AlternativeHotelsPagePrimitive
|
||||||
<SelectHotel
|
lang={lang}
|
||||||
bookingCode={booking.bookingCode}
|
searchParams={searchParams}
|
||||||
city={searchDetails.city}
|
renderTracking={(props) => (
|
||||||
hotels={hotels}
|
|
||||||
isAlternative={!!searchDetails.hotel}
|
|
||||||
isBookingCodeRateAvailable={isBookingCodeRateAvailable}
|
|
||||||
mapHref={mapHref}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
<Suspense key={`${suspenseKey}-tracking`} fallback={null}>
|
|
||||||
<TrackingSDK
|
<TrackingSDK
|
||||||
pageData={pageTrackingData}
|
hotelInfo={props.hotelsTrackingData}
|
||||||
hotelInfo={hotelsTrackingData}
|
pageData={props.pageTrackingData}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
)}
|
||||||
</>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { cookies } from "next/headers"
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
|
||||||
|
import FnFNotAllowedAlert from "@scandic-hotels/booking-flow/components/FnFNotAllowedAlert"
|
||||||
import { parseDetailsSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
import { parseDetailsSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||||
|
import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends"
|
||||||
|
|
||||||
import { FamilyAndFriendsCodes } from "@/constants/booking"
|
|
||||||
import {
|
import {
|
||||||
getBreakfastPackages,
|
getBreakfastPackages,
|
||||||
getHotel,
|
getHotel,
|
||||||
@@ -19,7 +20,6 @@ import RoomOne from "@/components/HotelReservation/EnterDetails/Room/One"
|
|||||||
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
|
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
|
||||||
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
||||||
import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking"
|
import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking"
|
||||||
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
|
|
||||||
import RoomProvider from "@/providers/Details/RoomProvider"
|
import RoomProvider from "@/providers/Details/RoomProvider"
|
||||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
export default async function HotelReservationPage(
|
export default async function HotelReservationPage(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton"
|
import { SelectHotelSkeleton } from "@scandic-hotels/booking-flow/components/SelectHotel"
|
||||||
|
|
||||||
export default function SelectHotelLoading() {
|
export default function SelectHotelLoading() {
|
||||||
return <SelectHotelSkeleton />
|
return <SelectHotelSkeleton />
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import stringify from "json-stable-stringify-without-jsonify"
|
import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage"
|
||||||
import { notFound } from "next/navigation"
|
|
||||||
import { Suspense } from "react"
|
|
||||||
|
|
||||||
import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer"
|
|
||||||
import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton"
|
|
||||||
import { MapContainer } from "@/components/MapContainer"
|
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
@@ -16,22 +11,20 @@ export default async function SelectHotelMapPage(
|
|||||||
props: PageArgs<LangParams, NextSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const suspenseKey = stringify(searchParams)
|
const lang = await getLang()
|
||||||
|
|
||||||
const booking = parseSelectHotelSearchParams(searchParams)
|
|
||||||
|
|
||||||
if (!booking) return notFound()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
<MapContainer>
|
<SelectHotelMapPagePrimitive
|
||||||
<Suspense
|
lang={lang}
|
||||||
key={suspenseKey}
|
searchParams={searchParams}
|
||||||
fallback={<SelectHotelMapContainerSkeleton />}
|
renderTracking={(props) => (
|
||||||
>
|
<TrackingSDK
|
||||||
<SelectHotelMapContainer booking={booking} />
|
hotelInfo={props.hotelsTrackingData}
|
||||||
</Suspense>
|
pageData={props.pageTrackingData}
|
||||||
</MapContainer>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
import stringify from "json-stable-stringify-without-jsonify"
|
import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage"
|
||||||
import { cookies } from "next/headers"
|
|
||||||
import { notFound } from "next/navigation"
|
|
||||||
import { Suspense } from "react"
|
|
||||||
|
|
||||||
import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
|
||||||
import { selectHotelMap } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
|
||||||
|
|
||||||
import { FamilyAndFriendsCodes } from "@/constants/booking"
|
|
||||||
|
|
||||||
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
|
|
||||||
import SelectHotel from "@/components/HotelReservation/SelectHotel"
|
|
||||||
import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
import { getSelectHotelTracking } from "@/components/HotelReservation/SelectHotel/tracking"
|
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
@@ -21,93 +9,18 @@ export default async function SelectHotelPage(
|
|||||||
props: PageArgs<LangParams, NextSearchParams>
|
props: PageArgs<LangParams, NextSearchParams>
|
||||||
) {
|
) {
|
||||||
const searchParams = await props.searchParams
|
const searchParams = await props.searchParams
|
||||||
const params = await props.params
|
const lang = await getLang()
|
||||||
|
|
||||||
const booking = parseSelectHotelSearchParams(searchParams)
|
|
||||||
|
|
||||||
if (!booking) return notFound()
|
|
||||||
|
|
||||||
const searchDetails = await getHotelSearchDetails(booking)
|
|
||||||
|
|
||||||
if (!searchDetails || !searchDetails.city) return notFound()
|
|
||||||
|
|
||||||
if (
|
|
||||||
booking.bookingCode &&
|
|
||||||
FamilyAndFriendsCodes.includes(booking.bookingCode)
|
|
||||||
) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const isInvalidFNF = cookieStore.get("sc")?.value !== "1"
|
|
||||||
|
|
||||||
if (isInvalidFNF) {
|
|
||||||
return <FnFNotAllowedAlert />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { city, redemption } = searchDetails
|
|
||||||
|
|
||||||
const hotels = await getHotels({
|
|
||||||
fromDate: booking.fromDate,
|
|
||||||
toDate: booking.toDate,
|
|
||||||
rooms: booking.rooms,
|
|
||||||
isAlternativeFor: null,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
city: city,
|
|
||||||
redemption: !!redemption,
|
|
||||||
})
|
|
||||||
|
|
||||||
const isRedemptionAvailability = redemption
|
|
||||||
? hotels.some(
|
|
||||||
(hotel) => hotel.availability.productType?.redemptions?.length
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const isBookingCodeRateAvailable = booking.bookingCode
|
|
||||||
? hotels.some(
|
|
||||||
(hotel) =>
|
|
||||||
hotel.availability.bookingCode &&
|
|
||||||
hotel.availability.status === "Available"
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const arrivalDate = new Date(booking.fromDate)
|
|
||||||
const departureDate = new Date(booking.toDate)
|
|
||||||
|
|
||||||
const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({
|
|
||||||
rooms: booking.rooms,
|
|
||||||
lang: params.lang,
|
|
||||||
pageId: "select-hotel",
|
|
||||||
pageName: "hotelreservation|select-hotel",
|
|
||||||
siteSections: "hotelreservation|select-hotel",
|
|
||||||
arrivalDate,
|
|
||||||
departureDate,
|
|
||||||
hotelsResult: hotels?.length ?? 0,
|
|
||||||
searchTerm: booking.hotelId,
|
|
||||||
country: hotels?.[0]?.hotel.address.country,
|
|
||||||
hotelCity: hotels?.[0]?.hotel.address.city,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
isBookingCodeRateAvailable,
|
|
||||||
isRedemption: redemption,
|
|
||||||
isRedemptionAvailable: isRedemptionAvailability,
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapHref = selectHotelMap(params.lang)
|
|
||||||
const suspenseKey = stringify(searchParams)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SelectHotelPagePrimitive
|
||||||
<SelectHotel
|
lang={lang}
|
||||||
bookingCode={booking.bookingCode}
|
searchParams={searchParams}
|
||||||
isBookingCodeRateAvailable={isBookingCodeRateAvailable}
|
renderTracking={(props) => (
|
||||||
city={city}
|
|
||||||
hotels={hotels}
|
|
||||||
mapHref={mapHref}
|
|
||||||
title={city.name}
|
|
||||||
/>
|
|
||||||
<Suspense key={`${suspenseKey}-tracking`} fallback={null}>
|
|
||||||
<TrackingSDK
|
<TrackingSDK
|
||||||
pageData={pageTrackingData}
|
hotelInfo={props.hotelsTrackingData}
|
||||||
hotelInfo={hotelsTrackingData}
|
pageData={props.pageTrackingData}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
)}
|
||||||
</>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKPageData,
|
type TrackingSDKPageData,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
export default async function Tracking() {
|
export default async function Tracking() {
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import Script from "next/script"
|
|||||||
import { SessionProvider } from "next-auth/react"
|
import { SessionProvider } from "next-auth/react"
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app"
|
import { NuqsAdapter } from "nuqs/adapters/next/app"
|
||||||
|
|
||||||
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
|
||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
|
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
|
||||||
|
|
||||||
import TrpcProvider from "@/lib/trpc/Provider"
|
import TrpcProvider from "@/lib/trpc/Provider"
|
||||||
|
|
||||||
import { SessionRefresher } from "@/components/Auth/TokenRefresher"
|
import { SessionRefresher } from "@/components/Auth/TokenRefresher"
|
||||||
|
import { BookingFlowProviders } from "@/components/BookingFlowProviders"
|
||||||
import CookieBotConsent from "@/components/CookieBot"
|
import CookieBotConsent from "@/components/CookieBot"
|
||||||
import Footer from "@/components/Footer"
|
import Footer from "@/components/Footer"
|
||||||
import Header from "@/components/Header"
|
import Header from "@/components/Header"
|
||||||
@@ -30,8 +30,6 @@ import { FontPreload } from "@/fonts/font-preloading"
|
|||||||
import { getMessages } from "@/i18n"
|
import { getMessages } from "@/i18n"
|
||||||
import ClientIntlProvider from "@/i18n/Provider"
|
import ClientIntlProvider from "@/i18n/Provider"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
import { trackAccordionClick, trackOpenSidePeekEvent } from "@/utils/tracking"
|
|
||||||
import { trackBookingSearchClick } from "@/utils/tracking/booking"
|
|
||||||
|
|
||||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|
||||||
@@ -72,13 +70,7 @@ export default async function RootLayout(
|
|||||||
<NuqsAdapter>
|
<NuqsAdapter>
|
||||||
<TrpcProvider>
|
<TrpcProvider>
|
||||||
<RACRouterProvider>
|
<RACRouterProvider>
|
||||||
<BookingFlowTrackingProvider
|
<BookingFlowProviders>
|
||||||
trackingFunctions={{
|
|
||||||
trackBookingSearchClick,
|
|
||||||
trackAccordionItemOpen: trackAccordionClick,
|
|
||||||
trackOpenSidePeek: trackOpenSidePeekEvent,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RouteChange />
|
<RouteChange />
|
||||||
<SitewideAlert />
|
<SitewideAlert />
|
||||||
<Header />
|
<Header />
|
||||||
@@ -91,7 +83,7 @@ export default async function RootLayout(
|
|||||||
<CookieBotConsent />
|
<CookieBotConsent />
|
||||||
<UserExists />
|
<UserExists />
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</BookingFlowTrackingProvider>
|
</BookingFlowProviders>
|
||||||
</RACRouterProvider>
|
</RACRouterProvider>
|
||||||
</TrpcProvider>
|
</TrpcProvider>
|
||||||
</NuqsAdapter>
|
</NuqsAdapter>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKPageData,
|
type TrackingSDKPageData,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
export default async function Tracking() {
|
export default async function Tracking() {
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import NextAuth, { type NextAuthConfig, type User } from "next-auth"
|
import NextAuth, { type NextAuthConfig, type User } from "next-auth"
|
||||||
|
|
||||||
|
import { LoginTypeEnum } from "@scandic-hotels/common/constants/loginType"
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
import { LoginTypeEnum } from "@scandic-hotels/trpc/types/loginType"
|
|
||||||
|
|
||||||
import { PRE_REFRESH_TIME_IN_SECONDS } from "@/constants/auth"
|
import { PRE_REFRESH_TIME_IN_SECONDS } from "@/constants/auth"
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|||||||
30
apps/scandic-web/components/BookingFlowProviders.tsx
Normal file
30
apps/scandic-web/components/BookingFlowProviders.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
||||||
|
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
||||||
|
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
||||||
|
|
||||||
|
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
||||||
|
import { trackAccordionClick, trackOpenSidePeekEvent } from "@/utils/tracking"
|
||||||
|
import { trackBookingSearchClick } from "@/utils/tracking/booking"
|
||||||
|
|
||||||
|
import type { ReactNode } from "react"
|
||||||
|
|
||||||
|
export function BookingFlowProviders({ children }: { children: ReactNode }) {
|
||||||
|
const isLoggedIn = useIsUserLoggedIn()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BookingFlowContextProvider data={{ isLoggedIn }}>
|
||||||
|
<BookingFlowTrackingProvider
|
||||||
|
trackingFunctions={{
|
||||||
|
trackBookingSearchClick,
|
||||||
|
trackAccordionItemOpen: trackAccordionClick,
|
||||||
|
trackOpenSidePeek: trackOpenSidePeekEvent,
|
||||||
|
trackGenericEvent: trackEvent,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</BookingFlowTrackingProvider>
|
||||||
|
</BookingFlowContextProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { useEffect } from "react"
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
import { trackOpenMapView } from "@/utils/tracking/destinationPage"
|
import { trackOpenMapView } from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
import type { TrackingSDKPageData } from "@/types/components/tracking"
|
import type { TrackingSDKPageData } from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
interface DestinationTrackingProps {
|
interface DestinationTrackingProps {
|
||||||
pageData: TrackingSDKPageData
|
pageData: TrackingSDKPageData
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ import {
|
|||||||
} from "react"
|
} from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useHotelsMapStore } from "@scandic-hotels/booking-flow/stores/hotels-map"
|
||||||
import { debounce } from "@scandic-hotels/common/utils/debounce"
|
import { debounce } from "@scandic-hotels/common/utils/debounce"
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { InteractiveMap } from "@scandic-hotels/design-system/Map/InteractiveMap"
|
import { InteractiveMap } from "@scandic-hotels/design-system/Map/InteractiveMap"
|
||||||
|
|
||||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
|
||||||
|
|
||||||
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKHotelInfo,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
import { PointOfInterestGroupEnum } from "@scandic-hotels/trpc/enums/pointOfInterest"
|
import { PointOfInterestGroupEnum } from "@scandic-hotels/trpc/enums/pointOfInterest"
|
||||||
|
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
@@ -15,11 +20,6 @@ import type {
|
|||||||
HotelPageSectionHeadings,
|
HotelPageSectionHeadings,
|
||||||
HotelPageSections,
|
HotelPageSections,
|
||||||
} from "@/types/components/hotelPage/sections"
|
} from "@/types/components/hotelPage/sections"
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKHotelInfo,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import { HotelHashValues } from "@/types/enums/hotelPage"
|
import { HotelHashValues } from "@/types/enums/hotelPage"
|
||||||
|
|
||||||
export function getRoomNameAsParam(roomName: string) {
|
export function getRoomNameAsParam(roomName: string) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import type { TrackingSDKPageData } from "@scandic-hotels/common/tracking/types"
|
||||||
import type { CollectionPage } from "@scandic-hotels/trpc/types/collectionPage"
|
import type { CollectionPage } from "@scandic-hotels/trpc/types/collectionPage"
|
||||||
import type { ContentPage } from "@scandic-hotels/trpc/types/contentPage"
|
import type { ContentPage } from "@scandic-hotels/trpc/types/contentPage"
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import type { TrackingSDKPageData } from "@/types/components/tracking"
|
|
||||||
import type { staticPageVariants } from "./variants"
|
import type { staticPageVariants } from "./variants"
|
||||||
|
|
||||||
export interface StaticPageProps
|
export interface StaticPageProps
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {
|
|||||||
SiteSectionObject,
|
SiteSectionObject,
|
||||||
TrackingData,
|
TrackingData,
|
||||||
TrackingProps,
|
TrackingProps,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
function createPageObject(trackingData: TrackingData) {
|
function createPageObject(trackingData: TrackingData) {
|
||||||
const englishSegments = trackingData.englishUrl
|
const englishSegments = trackingData.englishUrl
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
|
|
||||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||||
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
||||||
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
import { useFormTracking } from "@/components/TrackingSDK/useFormTracking"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { getFormattedCountryList } from "@/utils/countries"
|
import { getFormattedCountryList } from "@/utils/countries"
|
||||||
import { getErrorMessage } from "@/utils/getErrorMessage"
|
import { getErrorMessage } from "@/utils/getErrorMessage"
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { createHash } from "crypto"
|
|||||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKAncillaries,
|
||||||
|
type TrackingSDKHotelInfo,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
type TrackingSDKPaymentInfo,
|
||||||
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
|
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
|
||||||
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||||
|
|
||||||
import { CancellationRuleEnum } from "@/constants/booking"
|
import { CancellationRuleEnum } from "@/constants/booking"
|
||||||
@@ -17,13 +24,6 @@ import type { Lang } from "@scandic-hotels/common/constants/language"
|
|||||||
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
||||||
import type { RateDefinition } from "@scandic-hotels/trpc/types/roomAvailability"
|
import type { RateDefinition } from "@scandic-hotels/trpc/types/roomAvailability"
|
||||||
|
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKAncillaries,
|
|
||||||
type TrackingSDKHotelInfo,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
type TrackingSDKPaymentInfo,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import type { Room } from "@/types/stores/booking-confirmation"
|
import type { Room } from "@/types/stores/booking-confirmation"
|
||||||
|
|
||||||
function getRate(cancellationRule: RateDefinition["cancellationRule"] | null) {
|
function getRate(cancellationRule: RateDefinition["cancellationRule"] | null) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
|
|||||||
|
|
||||||
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
|
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
|
||||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||||
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
import { useFormTracking } from "@/components/TrackingSDK/useFormTracking"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
|
|||||||
|
|
||||||
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
|
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
|
||||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||||
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
import { useFormTracking } from "@/components/TrackingSDK/useFormTracking"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { HotelDetailsSidePeek } from "@scandic-hotels/booking-flow/components/HotelDetailsSidePeek"
|
||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
import Title from "@scandic-hotels/design-system/Title"
|
import Title from "@scandic-hotels/design-system/Title"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./header.module.css"
|
import styles from "./header.module.css"
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKAncillaries,
|
||||||
|
type TrackingSDKHotelInfo,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
||||||
import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages"
|
import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages"
|
||||||
@@ -22,12 +28,6 @@ import type {
|
|||||||
DetailsBooking,
|
DetailsBooking,
|
||||||
RoomRate,
|
RoomRate,
|
||||||
} from "@/types/components/hotelReservation/enterDetails/details"
|
} from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKAncillaries,
|
|
||||||
type TrackingSDKHotelInfo,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import type { RoomState } from "@/types/stores/enter-details"
|
import type { RoomState } from "@/types/stores/enter-details"
|
||||||
|
|
||||||
export function getTracking(
|
export function getTracking(
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKPageData,
|
type TrackingSDKPageData,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
export default async function Tracking() {
|
export default async function Tracking() {
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats"
|
import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats"
|
||||||
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||||
@@ -11,7 +12,6 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
import Modal from "@scandic-hotels/design-system/Modal"
|
import Modal from "@scandic-hotels/design-system/Modal"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
|
||||||
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||||
|
|
||||||
import { CancellationRuleEnum } from "@/constants/booking"
|
import { CancellationRuleEnum } from "@/constants/booking"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import Modal from "@scandic-hotels/design-system/Modal"
|
import Modal from "@scandic-hotels/design-system/Modal"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
|
||||||
|
|
||||||
import { CancellationRuleEnum } from "@/constants/booking"
|
import { CancellationRuleEnum } from "@/constants/booking"
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { convertToChildType } from "../../utils/convertToChildType"
|
|||||||
import { getPriceType } from "../../utils/getPriceType"
|
import { getPriceType } from "../../utils/getPriceType"
|
||||||
import { formatChildBedPreferences } from "../utils"
|
import { formatChildBedPreferences } from "../utils"
|
||||||
|
|
||||||
import type { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
import type { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
||||||
import type { Room } from "@scandic-hotels/trpc/types/hotel"
|
import type { Room } from "@scandic-hotels/trpc/types/hotel"
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { notFound } from "next/navigation"
|
|
||||||
|
|
||||||
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
import { getCityCoordinates } from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
|
|
||||||
|
|
||||||
import { getHotelPins } from "../../HotelCardDialogListing/utils"
|
|
||||||
import { getFiltersFromHotels, getHotels } from "../helpers"
|
|
||||||
import { getSelectHotelTracking } from "../tracking"
|
|
||||||
import SelectHotelMap from "."
|
|
||||||
|
|
||||||
import type { SelectHotelMapContainerProps } from "@/types/components/hotelReservation/selectHotel/map"
|
|
||||||
|
|
||||||
export async function SelectHotelMapContainer({
|
|
||||||
booking,
|
|
||||||
isAlternativeHotels,
|
|
||||||
}: SelectHotelMapContainerProps) {
|
|
||||||
const lang = await getLang()
|
|
||||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
|
||||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
|
||||||
const getHotelSearchDetailsPromise = safeTry(
|
|
||||||
getHotelSearchDetails(booking, isAlternativeHotels)
|
|
||||||
)
|
|
||||||
|
|
||||||
const [searchDetails] = await getHotelSearchDetailsPromise
|
|
||||||
|
|
||||||
if (!searchDetails) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
city,
|
|
||||||
cityIdentifier,
|
|
||||||
hotel: isAlternativeFor,
|
|
||||||
redemption,
|
|
||||||
} = searchDetails
|
|
||||||
|
|
||||||
if (!city) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
const hotels = await getHotels({
|
|
||||||
fromDate: booking.fromDate,
|
|
||||||
toDate: booking.toDate,
|
|
||||||
rooms: booking.rooms,
|
|
||||||
isAlternativeFor,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
city,
|
|
||||||
redemption: !!redemption,
|
|
||||||
})
|
|
||||||
|
|
||||||
const hotelPins = getHotelPins(hotels)
|
|
||||||
const filterList = getFiltersFromHotels(hotels)
|
|
||||||
const cityCoordinates = await getCityCoordinates({
|
|
||||||
city: city.name,
|
|
||||||
hotel: { address: hotels?.[0]?.hotel?.address.streetAddress },
|
|
||||||
})
|
|
||||||
|
|
||||||
const arrivalDate = new Date(booking.fromDate)
|
|
||||||
const departureDate = new Date(booking.toDate)
|
|
||||||
const isRedemptionAvailability = redemption
|
|
||||||
? hotels.some(
|
|
||||||
(hotel) => hotel.availability.productType?.redemptions?.length
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const isBookingCodeRateAvailable = booking.bookingCode
|
|
||||||
? hotels?.some((hotel) => hotel.availability.bookingCode)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({
|
|
||||||
lang,
|
|
||||||
pageId: isAlternativeFor ? "alternative-hotels" : "select-hotel",
|
|
||||||
pageName: isAlternativeHotels
|
|
||||||
? "hotelreservation|alternative-hotels|mapview"
|
|
||||||
: "hotelreservation|select-hotel|mapview",
|
|
||||||
siteSections: isAlternativeHotels
|
|
||||||
? "hotelreservation|altervative-hotels|mapview"
|
|
||||||
: "hotelreservation|select-hotel|mapview",
|
|
||||||
arrivalDate,
|
|
||||||
departureDate,
|
|
||||||
rooms: booking.rooms,
|
|
||||||
hotelsResult: hotels.length,
|
|
||||||
searchTerm: isAlternativeFor ? booking.hotelId : cityIdentifier,
|
|
||||||
country: hotels?.[0]?.hotel.address.country,
|
|
||||||
hotelCity: hotels?.[0]?.hotel.address.city,
|
|
||||||
bookingCode: booking.bookingCode,
|
|
||||||
isBookingCodeRateAvailable,
|
|
||||||
isRedemption: redemption,
|
|
||||||
isRedemptionAvailable: isRedemptionAvailability,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SelectHotelMap
|
|
||||||
apiKey={googleMapsApiKey}
|
|
||||||
hotelPins={hotelPins}
|
|
||||||
mapId={googleMapId}
|
|
||||||
hotels={hotels}
|
|
||||||
filterList={filterList}
|
|
||||||
cityCoordinates={cityCoordinates}
|
|
||||||
bookingCode={booking.bookingCode}
|
|
||||||
isBookingCodeRateAvailable={isBookingCodeRateAvailable}
|
|
||||||
isAlternativeHotels={isAlternativeHotels}
|
|
||||||
/>
|
|
||||||
<TrackingSDK pageData={pageTrackingData} hotelInfo={hotelsTrackingData} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Body from "@scandic-hotels/design-system/Body"
|
import Body from "@scandic-hotels/design-system/Body"
|
||||||
import Caption from "@scandic-hotels/design-system/Caption"
|
import Caption from "@scandic-hotels/design-system/Caption"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
import Footnote from "@scandic-hotels/design-system/Footnote"
|
||||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||||
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
|
||||||
|
|
||||||
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
|
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
|
||||||
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
import Body from "@scandic-hotels/design-system/Body"
|
import Body from "@scandic-hotels/design-system/Body"
|
||||||
import Caption from "@scandic-hotels/design-system/Caption"
|
import Caption from "@scandic-hotels/design-system/Caption"
|
||||||
@@ -10,7 +11,6 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||||
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
|
||||||
|
|
||||||
import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext"
|
import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext"
|
||||||
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton"
|
import { RoomCardSkeleton } from "@scandic-hotels/booking-flow/components/RoomCardSkeleton"
|
||||||
|
|
||||||
import styles from "./roomsListSkeleton.module.css"
|
import styles from "./roomsListSkeleton.module.css"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton"
|
import { RoomCardSkeleton } from "@scandic-hotels/booking-flow/components/RoomCardSkeleton"
|
||||||
|
|
||||||
import styles from "./RoomsContainerSkeleton.module.css"
|
import styles from "./RoomsContainerSkeleton.module.css"
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||||
|
|
||||||
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
|
||||||
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
|
||||||
|
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
|
||||||
|
|
||||||
import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKHotelInfo,
|
type TrackingSDKHotelInfo,
|
||||||
type TrackingSDKPageData,
|
type TrackingSDKPageData,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
import type { ChildrenInRoom } from "@/utils/hotelSearchDetails"
|
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
||||||
|
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||||
|
|
||||||
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
import type { Child } from "@scandic-hotels/trpc/types/child"
|
||||||
|
|
||||||
|
import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
|
||||||
|
type ChildrenInRoom = (Child[] | null)[] | null
|
||||||
|
|
||||||
type SelectRateTrackingInput = {
|
type SelectRateTrackingInput = {
|
||||||
lang: Lang
|
lang: Lang
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
|
|
||||||
|
import FnFNotAllowedAlert from "@scandic-hotels/booking-flow/components/FnFNotAllowedAlert"
|
||||||
|
import { HotelDetailsSidePeek } from "@scandic-hotels/booking-flow/components/HotelDetailsSidePeek"
|
||||||
|
import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { HotelInfoCard } from "@scandic-hotels/design-system/HotelInfoCard"
|
import { HotelInfoCard } from "@scandic-hotels/design-system/HotelInfoCard"
|
||||||
|
|
||||||
import { FamilyAndFriendsCodes } from "@/constants/booking"
|
|
||||||
|
|
||||||
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer"
|
import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer"
|
||||||
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
||||||
|
|
||||||
import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert"
|
|
||||||
import { hasOverlappingDates } from "../utils"
|
import { hasOverlappingDates } from "../utils"
|
||||||
import AvailabilityError from "./AvailabilityError"
|
import AvailabilityError from "./AvailabilityError"
|
||||||
import Tracking from "./Tracking"
|
import Tracking from "./Tracking"
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import useLang from "@/hooks/useLang"
|
|||||||
import { useLazyPathname } from "@/hooks/useLazyPathname"
|
import { useLazyPathname } from "@/hooks/useLazyPathname"
|
||||||
import { trackLoginClick } from "@/utils/tracking"
|
import { trackLoginClick } from "@/utils/tracking"
|
||||||
|
|
||||||
|
import type { TrackingPosition } from "@scandic-hotels/common/tracking/types"
|
||||||
import type { PropsWithChildren } from "react"
|
import type { PropsWithChildren } from "react"
|
||||||
|
|
||||||
import type { TrackingPosition } from "@/types/components/tracking"
|
|
||||||
|
|
||||||
export default function LoginButton({
|
export default function LoginButton({
|
||||||
position,
|
position,
|
||||||
trackingId,
|
trackingId,
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
import { usePathname, useSearchParams } from "next/navigation"
|
import { usePathname, useSearchParams } from "next/navigation"
|
||||||
import { startTransition, useEffect } from "react"
|
import { startTransition, useEffect } from "react"
|
||||||
|
|
||||||
import useRouterTransitionStore from "@/stores/router-transition"
|
import { isSameBookingWidgetParams } from "@scandic-hotels/booking-flow/utils/isSameBooking"
|
||||||
import useTrackingStore from "@/stores/tracking"
|
import useRouterTransitionStore from "@scandic-hotels/common/stores/router-transition"
|
||||||
|
import useTrackingStore from "@scandic-hotels/common/stores/tracking"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { trackPageViewStart } from "@/utils/tracking"
|
import { trackPageViewStart } from "@/utils/tracking"
|
||||||
@@ -34,7 +35,10 @@ export default function RouteChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRouteInfo(pathName, currentLang, searchParams)
|
updateRouteInfo(pathName, currentLang, searchParams)
|
||||||
if (hasPathOrLangChanged() || hasBookingFlowParamsChanged()) {
|
if (
|
||||||
|
hasPathOrLangChanged() ||
|
||||||
|
hasBookingFlowParamsChanged(isSameBookingWidgetParams)
|
||||||
|
) {
|
||||||
setInitialPageLoadTime(Date.now())
|
setInitialPageLoadTime(Date.now())
|
||||||
trackPageViewStart()
|
trackPageViewStart()
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
|||||||
@@ -1,345 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { usePathname } from "next/navigation"
|
|
||||||
import {
|
|
||||||
startTransition,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react"
|
|
||||||
import {
|
|
||||||
type Control,
|
|
||||||
type FieldValues,
|
|
||||||
useFormState,
|
|
||||||
type UseFromSubscribe,
|
|
||||||
} from "react-hook-form"
|
|
||||||
|
|
||||||
import { useSessionId } from "@scandic-hotels/common/hooks/useSessionId"
|
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
|
||||||
|
|
||||||
import useRouterTransitionStore from "@/stores/router-transition"
|
|
||||||
import useTrackingStore from "@/stores/tracking"
|
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
|
||||||
import { promiseWithTimeout } from "@/utils/promiseWithTimeout"
|
|
||||||
import { createSDKPageObject, trackPageView } from "@/utils/tracking"
|
|
||||||
import {
|
|
||||||
type FormType,
|
|
||||||
trackFormAbandonment,
|
|
||||||
trackFormCompletion,
|
|
||||||
trackFormInputStarted,
|
|
||||||
} from "@/utils/tracking/form"
|
|
||||||
|
|
||||||
import type {
|
|
||||||
TrackingSDKProps,
|
|
||||||
TrackingSDKUserData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
|
|
||||||
enum TransitionStatusEnum {
|
|
||||||
NotRun = "NotRun",
|
|
||||||
Running = "Running",
|
|
||||||
Done = "Done",
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransitionStatus = keyof typeof TransitionStatusEnum
|
|
||||||
|
|
||||||
let hasTrackedHardNavigation = false
|
|
||||||
export const useTrackHardNavigation = ({
|
|
||||||
pageData,
|
|
||||||
hotelInfo,
|
|
||||||
paymentInfo,
|
|
||||||
ancillaries,
|
|
||||||
}: TrackingSDKProps) => {
|
|
||||||
const lang = useLang()
|
|
||||||
const {
|
|
||||||
data: userTrackingData,
|
|
||||||
isPending,
|
|
||||||
isError,
|
|
||||||
} = trpc.user.userTrackingInfo.useQuery({ lang })
|
|
||||||
|
|
||||||
const sessionId = useSessionId()
|
|
||||||
const pathName = usePathname()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isPending) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const userData: TrackingSDKUserData = isError
|
|
||||||
? { loginStatus: "Error" }
|
|
||||||
: userTrackingData
|
|
||||||
|
|
||||||
if (hasTrackedHardNavigation) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hasTrackedHardNavigation = true
|
|
||||||
|
|
||||||
const track = () => {
|
|
||||||
trackPerformance({
|
|
||||||
pathName,
|
|
||||||
sessionId,
|
|
||||||
paymentInfo,
|
|
||||||
hotelInfo,
|
|
||||||
userData,
|
|
||||||
pageData,
|
|
||||||
ancillaries,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === "complete") {
|
|
||||||
track()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", track)
|
|
||||||
return () => window.removeEventListener("load", track)
|
|
||||||
}, [
|
|
||||||
isError,
|
|
||||||
pathName,
|
|
||||||
hotelInfo,
|
|
||||||
userTrackingData,
|
|
||||||
pageData,
|
|
||||||
sessionId,
|
|
||||||
paymentInfo,
|
|
||||||
isPending,
|
|
||||||
ancillaries,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTrackSoftNavigation = ({
|
|
||||||
pageData,
|
|
||||||
hotelInfo,
|
|
||||||
paymentInfo,
|
|
||||||
ancillaries,
|
|
||||||
}: TrackingSDKProps) => {
|
|
||||||
const lang = useLang()
|
|
||||||
const {
|
|
||||||
data: userTrackingData,
|
|
||||||
isPending,
|
|
||||||
isError,
|
|
||||||
} = trpc.user.userTrackingInfo.useQuery({ lang })
|
|
||||||
|
|
||||||
const [status, setStatus] = useState<TransitionStatus>(
|
|
||||||
TransitionStatusEnum.NotRun
|
|
||||||
)
|
|
||||||
const { getPageLoadTime } = useTrackingStore()
|
|
||||||
|
|
||||||
const sessionId = useSessionId()
|
|
||||||
const pathName = usePathname()
|
|
||||||
const { isTransitioning, stopRouterTransition } = useRouterTransitionStore()
|
|
||||||
|
|
||||||
const previousPathname = useRef<string | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isPending) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTransitioning && status === TransitionStatusEnum.NotRun) {
|
|
||||||
startTransition(() => {
|
|
||||||
setStatus(TransitionStatusEnum.Running)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTransitioning && status === TransitionStatusEnum.Running) {
|
|
||||||
setStatus(TransitionStatusEnum.Done)
|
|
||||||
stopRouterTransition()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTransitioning && status === TransitionStatusEnum.Done) {
|
|
||||||
const pageLoadTime = getPageLoadTime()
|
|
||||||
const trackingData = {
|
|
||||||
...pageData,
|
|
||||||
sessionId,
|
|
||||||
pathName,
|
|
||||||
pageLoadTime: pageLoadTime,
|
|
||||||
}
|
|
||||||
const pageObject = createSDKPageObject(trackingData)
|
|
||||||
const userData: TrackingSDKUserData = isError
|
|
||||||
? { loginStatus: "Error" }
|
|
||||||
: userTrackingData
|
|
||||||
|
|
||||||
trackPageView({
|
|
||||||
event: "pageView",
|
|
||||||
pageInfo: pageObject,
|
|
||||||
userInfo: userData,
|
|
||||||
hotelInfo: hotelInfo,
|
|
||||||
paymentInfo,
|
|
||||||
ancillaries,
|
|
||||||
})
|
|
||||||
|
|
||||||
setStatus(TransitionStatusEnum.NotRun) // Reset status
|
|
||||||
previousPathname.current = pathName // Update for next render
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
isError,
|
|
||||||
isPending,
|
|
||||||
isTransitioning,
|
|
||||||
status,
|
|
||||||
stopRouterTransition,
|
|
||||||
pageData,
|
|
||||||
pathName,
|
|
||||||
hotelInfo,
|
|
||||||
getPageLoadTime,
|
|
||||||
sessionId,
|
|
||||||
paymentInfo,
|
|
||||||
userTrackingData,
|
|
||||||
ancillaries,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackPerformance = async ({
|
|
||||||
pathName,
|
|
||||||
sessionId,
|
|
||||||
paymentInfo,
|
|
||||||
hotelInfo,
|
|
||||||
userData,
|
|
||||||
pageData,
|
|
||||||
ancillaries,
|
|
||||||
}: {
|
|
||||||
pathName: string
|
|
||||||
sessionId: string | null
|
|
||||||
paymentInfo: TrackingSDKProps["paymentInfo"]
|
|
||||||
hotelInfo: TrackingSDKProps["hotelInfo"]
|
|
||||||
userData: TrackingSDKUserData
|
|
||||||
pageData: TrackingSDKProps["pageData"]
|
|
||||||
ancillaries: TrackingSDKProps["ancillaries"]
|
|
||||||
}) => {
|
|
||||||
let pageLoadTime: number | undefined = undefined
|
|
||||||
let lcpTime: number | undefined = undefined
|
|
||||||
|
|
||||||
try {
|
|
||||||
pageLoadTime = await promiseWithTimeout(getPageLoadTimeEntry(), 3000)
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error obtaining pageLoadTime:", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
lcpTime = await promiseWithTimeout(getLCPTimeEntry(), 3000)
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error obtaining lcpTime:", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackingData = {
|
|
||||||
...pageData,
|
|
||||||
sessionId,
|
|
||||||
pathName,
|
|
||||||
pageLoadTime,
|
|
||||||
lcpTime,
|
|
||||||
}
|
|
||||||
const pageObject = createSDKPageObject(trackingData)
|
|
||||||
|
|
||||||
trackPageView({
|
|
||||||
event: "pageView",
|
|
||||||
pageInfo: pageObject,
|
|
||||||
userInfo: userData,
|
|
||||||
hotelInfo,
|
|
||||||
paymentInfo,
|
|
||||||
ancillaries,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLCPTimeEntry = () => {
|
|
||||||
return new Promise<number | undefined>((resolve) => {
|
|
||||||
const observer = new PerformanceObserver((entries) => {
|
|
||||||
const lastEntry = entries.getEntries().at(-1)
|
|
||||||
if (lastEntry) {
|
|
||||||
observer.disconnect()
|
|
||||||
resolve(lastEntry.startTime / 1000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const lcpSupported = PerformanceObserver.supportedEntryTypes?.includes(
|
|
||||||
"largest-contentful-paint"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (lcpSupported) {
|
|
||||||
observer.observe({
|
|
||||||
type: "largest-contentful-paint",
|
|
||||||
buffered: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
resolve(undefined)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPageLoadTimeEntry = () => {
|
|
||||||
return new Promise<number>((resolve) => {
|
|
||||||
const observer = new PerformanceObserver((entries) => {
|
|
||||||
const navEntry = entries.getEntriesByType("navigation")[0]
|
|
||||||
if (navEntry) {
|
|
||||||
observer.disconnect()
|
|
||||||
resolve(navEntry.duration / 1000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
observer.observe({ type: "navigation", buffered: true })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFormTracking<T extends FieldValues>(
|
|
||||||
formType: FormType,
|
|
||||||
subscribe: UseFromSubscribe<T>,
|
|
||||||
control: Control<T>,
|
|
||||||
nameSuffix: string = ""
|
|
||||||
) {
|
|
||||||
const [formStarted, setFormStarted] = useState(false)
|
|
||||||
const lastAccessedField = useRef<string | undefined>(undefined)
|
|
||||||
const formState = useFormState({ control })
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = subscribe({
|
|
||||||
formState: { dirtyFields: true },
|
|
||||||
callback: (data) => {
|
|
||||||
if ("name" in data) {
|
|
||||||
lastAccessedField.current = data.name as string
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formStarted) {
|
|
||||||
trackFormInputStarted(formType, nameSuffix)
|
|
||||||
setFormStarted(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return () => unsubscribe()
|
|
||||||
}, [subscribe, formType, nameSuffix, formStarted])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!formStarted || !lastAccessedField.current || formState.isValid) return
|
|
||||||
|
|
||||||
const lastField = lastAccessedField.current
|
|
||||||
|
|
||||||
function handleBeforeUnload() {
|
|
||||||
trackFormAbandonment(formType, lastField, nameSuffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleVisibilityChange() {
|
|
||||||
if (document.visibilityState === "hidden") {
|
|
||||||
trackFormAbandonment(formType, lastField, nameSuffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload)
|
|
||||||
window.addEventListener("visibilitychange", handleVisibilityChange)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
|
||||||
window.removeEventListener("visibilitychange", handleVisibilityChange)
|
|
||||||
}
|
|
||||||
}, [formStarted, formType, nameSuffix, formState.isValid])
|
|
||||||
|
|
||||||
const trackFormSubmit = useCallback(() => {
|
|
||||||
if (formState.isValid) {
|
|
||||||
trackFormCompletion(formType, nameSuffix)
|
|
||||||
}
|
|
||||||
}, [formType, nameSuffix, formState.isValid])
|
|
||||||
|
|
||||||
return {
|
|
||||||
trackFormSubmit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import { usePathname } from "next/navigation"
|
||||||
useTrackHardNavigation,
|
|
||||||
useTrackSoftNavigation,
|
import { useTrackHardNavigation } from "@scandic-hotels/common/tracking/useTrackHardNavigation"
|
||||||
} from "@/components/TrackingSDK/hooks"
|
import { useTrackSoftNavigation } from "@scandic-hotels/common/tracking/useTrackSoftNavigation"
|
||||||
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
TrackingSDKAncillaries,
|
TrackingSDKAncillaries,
|
||||||
TrackingSDKHotelInfo,
|
TrackingSDKHotelInfo,
|
||||||
TrackingSDKPageData,
|
TrackingSDKPageData,
|
||||||
TrackingSDKPaymentInfo,
|
TrackingSDKPaymentInfo,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
export default function TrackingSDK({
|
export default function TrackingSDK({
|
||||||
pageData,
|
pageData,
|
||||||
@@ -23,8 +26,30 @@ export default function TrackingSDK({
|
|||||||
paymentInfo?: TrackingSDKPaymentInfo
|
paymentInfo?: TrackingSDKPaymentInfo
|
||||||
ancillaries?: TrackingSDKAncillaries
|
ancillaries?: TrackingSDKAncillaries
|
||||||
}) {
|
}) {
|
||||||
useTrackHardNavigation({ pageData, hotelInfo, paymentInfo, ancillaries })
|
const lang = useLang()
|
||||||
useTrackSoftNavigation({ pageData, hotelInfo, paymentInfo, ancillaries })
|
const pathName = usePathname()
|
||||||
|
const { data, isError } = trpc.user.userTrackingInfo.useQuery({
|
||||||
|
lang,
|
||||||
|
})
|
||||||
|
|
||||||
|
const userData = isError ? ({ loginStatus: "Error" } as const) : data
|
||||||
|
|
||||||
|
useTrackHardNavigation({
|
||||||
|
pageData,
|
||||||
|
hotelInfo,
|
||||||
|
paymentInfo,
|
||||||
|
ancillaries,
|
||||||
|
userData,
|
||||||
|
pathName,
|
||||||
|
})
|
||||||
|
useTrackSoftNavigation({
|
||||||
|
pageData,
|
||||||
|
hotelInfo,
|
||||||
|
paymentInfo,
|
||||||
|
ancillaries,
|
||||||
|
userData,
|
||||||
|
pathName,
|
||||||
|
})
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
78
apps/scandic-web/components/TrackingSDK/useFormTracking.ts
Normal file
78
apps/scandic-web/components/TrackingSDK/useFormTracking.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
import {
|
||||||
|
type Control,
|
||||||
|
type FieldValues,
|
||||||
|
useFormState,
|
||||||
|
type UseFromSubscribe,
|
||||||
|
} from "react-hook-form"
|
||||||
|
|
||||||
|
import {
|
||||||
|
type FormType,
|
||||||
|
trackFormAbandonment,
|
||||||
|
trackFormCompletion,
|
||||||
|
trackFormInputStarted,
|
||||||
|
} from "@/utils/tracking/form"
|
||||||
|
|
||||||
|
export function useFormTracking<T extends FieldValues>(
|
||||||
|
formType: FormType,
|
||||||
|
subscribe: UseFromSubscribe<T>,
|
||||||
|
control: Control<T>,
|
||||||
|
nameSuffix: string = ""
|
||||||
|
) {
|
||||||
|
const [formStarted, setFormStarted] = useState(false)
|
||||||
|
const lastAccessedField = useRef<string | undefined>(undefined)
|
||||||
|
const formState = useFormState({ control })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = subscribe({
|
||||||
|
formState: { dirtyFields: true },
|
||||||
|
callback: (data) => {
|
||||||
|
if ("name" in data) {
|
||||||
|
lastAccessedField.current = data.name as string
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formStarted) {
|
||||||
|
trackFormInputStarted(formType, nameSuffix)
|
||||||
|
setFormStarted(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return () => unsubscribe()
|
||||||
|
}, [subscribe, formType, nameSuffix, formStarted])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!formStarted || !lastAccessedField.current || formState.isValid) return
|
||||||
|
|
||||||
|
const lastField = lastAccessedField.current
|
||||||
|
|
||||||
|
function handleBeforeUnload() {
|
||||||
|
trackFormAbandonment(formType, lastField, nameSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVisibilityChange() {
|
||||||
|
if (document.visibilityState === "hidden") {
|
||||||
|
trackFormAbandonment(formType, lastField, nameSuffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload)
|
||||||
|
window.addEventListener("visibilitychange", handleVisibilityChange)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||||||
|
window.removeEventListener("visibilitychange", handleVisibilityChange)
|
||||||
|
}
|
||||||
|
}, [formStarted, formType, nameSuffix, formState.isValid])
|
||||||
|
|
||||||
|
const trackFormSubmit = useCallback(() => {
|
||||||
|
if (formState.isValid) {
|
||||||
|
trackFormCompletion(formType, nameSuffix)
|
||||||
|
}
|
||||||
|
}, [formType, nameSuffix, formState.isValid])
|
||||||
|
|
||||||
|
return {
|
||||||
|
trackFormSubmit,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
|
import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
|
||||||
|
|
||||||
export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"]
|
|
||||||
|
|
||||||
export const SEARCHTYPE = "searchtype"
|
export const SEARCHTYPE = "searchtype"
|
||||||
|
|
||||||
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
|
|
||||||
import { isRateSelected } from "./isRateSelected"
|
import { isRateSelected } from "./isRateSelected"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
import type { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
|
|
||||||
import type { Rate } from "./types"
|
import type { Rate } from "./types"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
import { isDefined } from "@scandic-hotels/common/utils/isDefined"
|
|
||||||
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
import { cache } from "@/utils/cache"
|
import { cache } from "@/utils/cache"
|
||||||
|
|
||||||
import { serverClient } from "../server"
|
import { serverClient } from "../server"
|
||||||
@@ -12,22 +9,13 @@ import type { GetHotelsByCSFilterInput } from "@scandic-hotels/trpc/routers/hote
|
|||||||
import type { GetSavedPaymentCardsInput } from "@scandic-hotels/trpc/routers/user/input"
|
import type { GetSavedPaymentCardsInput } from "@scandic-hotels/trpc/routers/user/input"
|
||||||
import type { RoomsAvailabilityExtendedInputSchema } from "@scandic-hotels/trpc/types/availability"
|
import type { RoomsAvailabilityExtendedInputSchema } from "@scandic-hotels/trpc/types/availability"
|
||||||
import type { Country } from "@scandic-hotels/trpc/types/country"
|
import type { Country } from "@scandic-hotels/trpc/types/country"
|
||||||
import type {
|
import type { HotelInput } from "@scandic-hotels/trpc/types/hotel"
|
||||||
CityCoordinatesInput,
|
|
||||||
HotelInput,
|
|
||||||
} from "@scandic-hotels/trpc/types/hotel"
|
|
||||||
import type {
|
import type {
|
||||||
AncillaryPackagesInput,
|
AncillaryPackagesInput,
|
||||||
BreackfastPackagesInput,
|
BreackfastPackagesInput,
|
||||||
PackagesInput,
|
PackagesInput,
|
||||||
} from "@scandic-hotels/trpc/types/packages"
|
} from "@scandic-hotels/trpc/types/packages"
|
||||||
|
|
||||||
export const getLocations = cache(async function getMemoizedLocations() {
|
|
||||||
const lang = await getLang()
|
|
||||||
const caller = await serverClient()
|
|
||||||
return caller.hotel.locations.get({ lang })
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getProfile = cache(async function getMemoizedProfile() {
|
export const getProfile = cache(async function getMemoizedProfile() {
|
||||||
const caller = await serverClient()
|
const caller = await serverClient()
|
||||||
return caller.user.get()
|
return caller.user.get()
|
||||||
@@ -187,13 +175,6 @@ export const getLinkedReservations = cache(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getCityCoordinates = cache(
|
|
||||||
async function getMemoizedCityCoordinates(input: CityCoordinatesInput) {
|
|
||||||
const caller = await serverClient()
|
|
||||||
return caller.hotel.map.city(input)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getCurrentRewards = cache(
|
export const getCurrentRewards = cache(
|
||||||
async function getMemoizedCurrentRewards() {
|
async function getMemoizedCurrentRewards() {
|
||||||
const caller = await serverClient()
|
const caller = await serverClient()
|
||||||
@@ -294,83 +275,6 @@ export const getJobylonFeed = cache(async function getMemoizedJobylonFeed() {
|
|||||||
return caller.partner.jobylon.feed.get()
|
return caller.partner.jobylon.feed.get()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getJumpToData = cache(async function getMemoizedJumpToData() {
|
|
||||||
const lang = await getLang()
|
|
||||||
const caller = await serverClient()
|
|
||||||
const [locationsResults, urlsResults] = await Promise.allSettled([
|
|
||||||
getLocations(),
|
|
||||||
caller.hotel.locations.urls({ lang }),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (
|
|
||||||
locationsResults.status === "fulfilled" &&
|
|
||||||
urlsResults.status === "fulfilled"
|
|
||||||
) {
|
|
||||||
const locations = locationsResults.value
|
|
||||||
const urls = urlsResults.value
|
|
||||||
|
|
||||||
if (!locations || !urls) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return locations
|
|
||||||
.map((location) => {
|
|
||||||
const { id, name, type } = location
|
|
||||||
|
|
||||||
const isCity = type === "cities"
|
|
||||||
const isHotel = type === "hotels"
|
|
||||||
|
|
||||||
let url: string | undefined
|
|
||||||
|
|
||||||
if (isCity) {
|
|
||||||
url = urls.cities.find(
|
|
||||||
(c) =>
|
|
||||||
c.city &&
|
|
||||||
location.cityIdentifier &&
|
|
||||||
c.city === location.cityIdentifier
|
|
||||||
)?.url
|
|
||||||
} else if (isHotel) {
|
|
||||||
url = urls.hotels.find(
|
|
||||||
(h) => h.hotelId && location.id && h.hotelId === location.id
|
|
||||||
)?.url
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = ""
|
|
||||||
if (isCity) {
|
|
||||||
description = location.country
|
|
||||||
} else if (isHotel) {
|
|
||||||
description = location.relationships.city.name
|
|
||||||
}
|
|
||||||
|
|
||||||
const rankingNames: string[] = [location.name]
|
|
||||||
if (isCity) {
|
|
||||||
if (location.cityIdentifier) {
|
|
||||||
rankingNames.push(location.cityIdentifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rankingKeywords = location.keyWords || []
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
displayName: name,
|
|
||||||
type,
|
|
||||||
description,
|
|
||||||
url,
|
|
||||||
rankingNames: rankingNames.map((v) => v.toLowerCase()),
|
|
||||||
rankingKeywords: rankingKeywords.map((v) => v.toLowerCase()),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(isDefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getSelectedRoomsAvailabilityEnterDetails = cache(
|
export const getSelectedRoomsAvailabilityEnterDetails = cache(
|
||||||
async function getMemoizedSelectedRoomsAvailability(
|
async function getMemoizedSelectedRoomsAvailability(
|
||||||
input: RoomsAvailabilityExtendedInputSchema
|
input: RoomsAvailabilityExtendedInputSchema
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import deepmerge from "deepmerge"
|
import deepmerge from "deepmerge"
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
|
||||||
|
import { isSameBooking } from "@scandic-hotels/booking-flow/utils/isSameBooking"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
|
|
||||||
import { createDetailsStore } from "@/stores/enter-details"
|
import { createDetailsStore } from "@/stores/enter-details"
|
||||||
import {
|
import {
|
||||||
checkIsSameBooking as checkIsSameBooking,
|
|
||||||
clearSessionStorage,
|
clearSessionStorage,
|
||||||
getTotalPrice,
|
getTotalPrice,
|
||||||
readFromSessionStorage,
|
readFromSessionStorage,
|
||||||
@@ -89,8 +89,8 @@ export default function EnterDetailsProvider({
|
|||||||
setHasInitializedStore(true)
|
setHasInitializedStore(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const isSameBooking = checkIsSameBooking(storedValues.booking, booking)
|
|
||||||
if (!isSameBooking) {
|
if (!isSameBooking(storedValues.booking, booking)) {
|
||||||
clearSessionStorage()
|
clearSessionStorage()
|
||||||
setHasInitializedStore(true)
|
setHasInitializedStore(true)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -48,33 +48,6 @@ export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkIsSameBooking(
|
|
||||||
prev: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string },
|
|
||||||
next: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string }
|
|
||||||
) {
|
|
||||||
const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev
|
|
||||||
|
|
||||||
const prevRoomsWithoutRateCodes = prevRooms?.map(
|
|
||||||
({ adults, childrenInRoom }) => ({ adults, childrenInRoom })
|
|
||||||
)
|
|
||||||
const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next
|
|
||||||
|
|
||||||
const nextRoomsWithoutRateCodes = nextRooms?.map(
|
|
||||||
({ adults, childrenInRoom }) => ({ adults, childrenInRoom })
|
|
||||||
)
|
|
||||||
|
|
||||||
return isEqual(
|
|
||||||
{
|
|
||||||
...prevBooking,
|
|
||||||
rooms: prevRoomsWithoutRateCodes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...nextBooking,
|
|
||||||
rooms: nextRoomsWithoutRateCodes,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function add(...nums: (number | string | undefined)[]) {
|
export function add(...nums: (number | string | undefined)[]) {
|
||||||
return nums.reduce((total: number, num) => {
|
return nums.reduce((total: number, num) => {
|
||||||
if (typeof num === "undefined") {
|
if (typeof num === "undefined") {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
export type AvailabilityInput = {
|
|
||||||
cityId: string
|
|
||||||
roomStayStartDate: string
|
|
||||||
roomStayEndDate: string
|
|
||||||
adults: number
|
|
||||||
children?: string
|
|
||||||
bookingCode?: string
|
|
||||||
redemption?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AlternativeHotelsAvailabilityInput = {
|
|
||||||
roomStayStartDate: string
|
|
||||||
roomStayEndDate: string
|
|
||||||
adults: number
|
|
||||||
children?: string
|
|
||||||
bookingCode?: string
|
|
||||||
redemption?: boolean
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import type { CategorizedHotelFilters } from "./hotelFilters"
|
|
||||||
|
|
||||||
export type FilterAndSortModalProps = {
|
|
||||||
filters: CategorizedHotelFilters
|
|
||||||
setShowSkeleton?: (showSkeleton: boolean) => void
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export type FilterCheckboxProps = {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
isDisabled?: boolean
|
|
||||||
isSelected: boolean
|
|
||||||
onChange: (filterId: string) => void
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import type { ProductType } from "@scandic-hotels/trpc/types/availability"
|
|
||||||
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
|
|
||||||
|
|
||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
|
|
||||||
export enum HotelCardListingTypeEnum {
|
|
||||||
MapListing = "mapListing",
|
|
||||||
PageListing = "pageListing",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HotelData = {
|
|
||||||
hotelData: Hotel
|
|
||||||
price: ProductType
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HotelCardListingProps = {
|
|
||||||
hotelData: HotelResponse[]
|
|
||||||
unfilteredHotelCount: number
|
|
||||||
type?: HotelCardListingTypeEnum
|
|
||||||
isAlternative?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NullableHotelData extends Omit<HotelData, "hotelData"> {
|
|
||||||
hotelData: HotelData["hotelData"] | null
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
import type { HotelCardListingTypeEnum } from "./hotelCardListingProps"
|
|
||||||
|
|
||||||
export type HotelCardProps = {
|
|
||||||
hotelData: HotelResponse
|
|
||||||
isUserLoggedIn: boolean
|
|
||||||
type?: HotelCardListingTypeEnum
|
|
||||||
state?: "default" | "active"
|
|
||||||
bookingCode?: string | null
|
|
||||||
isAlternative?: boolean
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ProductType } from "@scandic-hotels/trpc/types/availability"
|
||||||
|
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
|
||||||
|
|
||||||
|
export type HotelData = {
|
||||||
|
hotelData: Hotel
|
||||||
|
price: ProductType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NullableHotelData extends Omit<HotelData, "hotelData"> {
|
||||||
|
hotelData: HotelData["hotelData"] | null
|
||||||
|
}
|
||||||
@@ -4,26 +4,6 @@ import type { Amenities } from "@scandic-hotels/trpc/types/hotel"
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
import type { Coordinates } from "@/types/components/maps/coordinates"
|
import type { Coordinates } from "@/types/components/maps/coordinates"
|
||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
import type { CategorizedHotelFilters } from "./hotelFilters"
|
|
||||||
import type { SelectHotelBooking } from "./selectHotel"
|
|
||||||
|
|
||||||
export interface HotelListingProps {
|
|
||||||
hotels: HotelResponse[]
|
|
||||||
unfilteredHotelCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectHotelMapProps {
|
|
||||||
apiKey: string
|
|
||||||
hotelPins: HotelPin[]
|
|
||||||
mapId: string
|
|
||||||
hotels: HotelResponse[]
|
|
||||||
filterList: CategorizedHotelFilters
|
|
||||||
cityCoordinates: Coordinates
|
|
||||||
bookingCode: string | undefined
|
|
||||||
isBookingCodeRateAvailable?: boolean
|
|
||||||
isAlternativeHotels?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImageSizes = z.infer<typeof imageSchema>["imageSizes"]
|
type ImageSizes = z.infer<typeof imageSchema>["imageSizes"]
|
||||||
type ImageMetaData = z.infer<typeof imageSchema>["metaData"]
|
type ImageMetaData = z.infer<typeof imageSchema>["metaData"]
|
||||||
@@ -56,13 +36,3 @@ export interface HotelCardDialogProps {
|
|||||||
data: HotelPin
|
data: HotelPin
|
||||||
handleClose: (event: { stopPropagation: () => void }) => void
|
handleClose: (event: { stopPropagation: () => void }) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HotelCardDialogListingProps {
|
|
||||||
hotels: HotelResponse[]
|
|
||||||
unfilteredHotelCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SelectHotelMapContainerProps = {
|
|
||||||
booking: SelectHotelBooking
|
|
||||||
isAlternativeHotels?: boolean
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
|
|
||||||
|
|
||||||
export type NoAvailabilityAlertProp = {
|
|
||||||
hotelsLength: number
|
|
||||||
bookingCode?: string
|
|
||||||
isAllUnavailable: boolean
|
|
||||||
isAlternative?: boolean
|
|
||||||
isBookingCodeRateNotAvailable?: boolean
|
|
||||||
operaId: Hotel["operaId"]
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { BookingSearchType } from "@scandic-hotels/booking-flow/searchType"
|
import type { BookingSearchType } from "@scandic-hotels/booking-flow/searchType"
|
||||||
import type { RateEnum } from "@scandic-hotels/trpc/enums/rate"
|
import type { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
import type { Child } from "@scandic-hotels/trpc/types/child"
|
import type { Child } from "@scandic-hotels/trpc/types/child"
|
||||||
import type { PackageEnum, Packages } from "@scandic-hotels/trpc/types/packages"
|
import type { PackageEnum, Packages } from "@scandic-hotels/trpc/types/packages"
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
||||||
|
|
||||||
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
|
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
|
||||||
import type { LowestRoomPriceEvent } from "@/types/components/tracking"
|
import type { LowestRoomPriceEvent } from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
export function trackLowestRoomPrice(event: LowestRoomPriceEvent) {
|
export function trackLowestRoomPrice(event: LowestRoomPriceEvent) {
|
||||||
trackEvent({
|
trackEvent({
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ export {
|
|||||||
trackLoginClick,
|
trackLoginClick,
|
||||||
trackSocialMediaClick,
|
trackSocialMediaClick,
|
||||||
} from "./navigation"
|
} from "./navigation"
|
||||||
|
export { trackPaymentEvent, trackUpdatePaymentMethod } from "./payment"
|
||||||
|
export { trackClick } from "@scandic-hotels/common/tracking/base"
|
||||||
export {
|
export {
|
||||||
createSDKPageObject,
|
createSDKPageObject,
|
||||||
trackPageView,
|
trackPageView,
|
||||||
trackPageViewStart,
|
trackPageViewStart,
|
||||||
} from "./pageview"
|
} from "@scandic-hotels/common/tracking/pageview"
|
||||||
export { trackPaymentEvent, trackUpdatePaymentMethod } from "./payment"
|
|
||||||
export { trackClick } from "@scandic-hotels/common/tracking/base"
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
||||||
|
|
||||||
import type { TrackingPosition } from "@/types/components/tracking"
|
import type { TrackingPosition } from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
export function trackFooterClick(group: string, name: string) {
|
export function trackFooterClick(group: string, name: string) {
|
||||||
trackEvent({
|
trackEvent({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
|||||||
import type {
|
import type {
|
||||||
PaymentEvent,
|
PaymentEvent,
|
||||||
PaymentFailEvent,
|
PaymentFailEvent,
|
||||||
} from "@/types/components/tracking"
|
} from "@scandic-hotels/common/tracking/types"
|
||||||
|
|
||||||
function isPaymentFailEvent(event: PaymentEvent): event is PaymentFailEvent {
|
function isPaymentFailEvent(event: PaymentEvent): event is PaymentFailEvent {
|
||||||
return "errorMessage" in event
|
return "errorMessage" in event
|
||||||
|
|||||||
9
packages/booking-flow/env/server.ts
vendored
9
packages/booking-flow/env/server.ts
vendored
@@ -9,10 +9,15 @@ export const env = createEnv({
|
|||||||
*/
|
*/
|
||||||
isServer: typeof window === "undefined" || "Deno" in window,
|
isServer: typeof window === "undefined" || "Deno" in window,
|
||||||
server: {
|
server: {
|
||||||
FOO: z.string().optional(),
|
GOOGLE_STATIC_MAP_KEY: z.string(),
|
||||||
|
GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string(),
|
||||||
|
GOOGLE_DYNAMIC_MAP_ID: z.string(),
|
||||||
},
|
},
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
FOO: process.env.FOO,
|
GOOGLE_STATIC_MAP_KEY: process.env.GOOGLE_STATIC_MAP_KEY,
|
||||||
|
GOOGLE_STATIC_MAP_SIGNATURE_SECRET:
|
||||||
|
process.env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET,
|
||||||
|
GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
23
packages/booking-flow/lib/bookingFlowContext.tsx
Normal file
23
packages/booking-flow/lib/bookingFlowContext.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { createContext, useContext } from "react"
|
||||||
|
|
||||||
|
export type BookingFlowContextData = {
|
||||||
|
isLoggedIn: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BookingFlowContext = createContext<
|
||||||
|
BookingFlowContextData | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
|
export const useBookingFlowContext = (): BookingFlowContextData => {
|
||||||
|
const context = useContext(BookingFlowContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useBookingFlowContext must be used within a BookingFlowContextProvider. Did you forget to use the provider in the consuming app?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
export function AlternativeHotelsPageTitle({
|
||||||
|
hotelName,
|
||||||
|
}: {
|
||||||
|
hotelName: string
|
||||||
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const title = intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Alternatives for {value}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: hotelName,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return <>{title}</>
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
BookingFlowContext,
|
||||||
|
type BookingFlowContextData,
|
||||||
|
} from "../bookingFlowContext"
|
||||||
|
|
||||||
|
import type { ReactNode } from "react"
|
||||||
|
|
||||||
|
export function BookingFlowContextProvider(props: {
|
||||||
|
children: ReactNode
|
||||||
|
data: BookingFlowContextData
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<BookingFlowContext value={props.data}>{props.children}</BookingFlowContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
||||||
import { Alert } from "@scandic-hotels/design-system/Alert"
|
import { Alert } from "@scandic-hotels/design-system/Alert"
|
||||||
|
|
||||||
import { getIntl } from "@/i18n"
|
|
||||||
|
|
||||||
import styles from "./FnFNotAllowedAlert.module.css"
|
import styles from "./FnFNotAllowedAlert.module.css"
|
||||||
|
|
||||||
export default async function FnFNotAllowedAlert() {
|
export default function FnFNotAllowedAlert() {
|
||||||
const intl = await getIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.fnfMain}>
|
<div className={styles.fnfMain}>
|
||||||
@@ -3,15 +3,19 @@
|
|||||||
import { useCallback, useEffect, useRef } from "react"
|
import { useCallback, useEffect, useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useHotelFilterStore } from "../../stores/hotel-filters"
|
||||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
import { useHotelsMapStore } from "../../stores/hotels-map"
|
||||||
|
import ListingHotelCardDialog from "../ListingHotelCardDialog"
|
||||||
import ListingHotelCardDialog from "../HotelCardDialog/ListingHotelCardDialog"
|
|
||||||
import { getHotelPins } from "./utils"
|
import { getHotelPins } from "./utils"
|
||||||
|
|
||||||
import styles from "./hotelCardDialogListing.module.css"
|
import styles from "./hotelCardDialogListing.module.css"
|
||||||
|
|
||||||
import type { HotelCardDialogListingProps } from "@/types/components/hotelReservation/selectHotel/map"
|
import type { HotelResponse } from "../SelectHotel/helpers"
|
||||||
|
|
||||||
|
interface HotelCardDialogListingProps {
|
||||||
|
hotels: HotelResponse[]
|
||||||
|
unfilteredHotelCount: number
|
||||||
|
}
|
||||||
|
|
||||||
export default function HotelCardDialogListing({
|
export default function HotelCardDialogListing({
|
||||||
hotels,
|
hotels,
|
||||||
@@ -1,5 +1,39 @@
|
|||||||
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
import type { imageSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/image"
|
||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
import type { ProductTypeCheque } from "@scandic-hotels/trpc/types/availability"
|
||||||
|
import type { Amenities } from "@scandic-hotels/trpc/types/hotel"
|
||||||
|
import type { z } from "zod"
|
||||||
|
|
||||||
|
import type { HotelResponse } from "../SelectHotel/helpers"
|
||||||
|
|
||||||
|
type ImageSizes = z.infer<typeof imageSchema>["imageSizes"]
|
||||||
|
type ImageMetaData = z.infer<typeof imageSchema>["metaData"]
|
||||||
|
|
||||||
|
interface Coordinates {
|
||||||
|
lat: number
|
||||||
|
lng: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HotelPin = {
|
||||||
|
bookingCode?: string | null
|
||||||
|
name: string
|
||||||
|
coordinates: Coordinates
|
||||||
|
chequePrice: ProductTypeCheque["localPrice"] | null
|
||||||
|
publicPrice: number | null
|
||||||
|
memberPrice: number | null
|
||||||
|
redemptionPrice: number | null
|
||||||
|
voucherPrice: number | null
|
||||||
|
rateType: string | null
|
||||||
|
currency: string
|
||||||
|
images: {
|
||||||
|
imageSizes: ImageSizes
|
||||||
|
metaData: ImageMetaData
|
||||||
|
}[]
|
||||||
|
amenities: Amenities
|
||||||
|
ratings: number | null
|
||||||
|
operaId: string
|
||||||
|
facilityIds: number[]
|
||||||
|
hasEnoughPoints: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function getHotelPins(
|
export function getHotelPins(
|
||||||
hotels: HotelResponse[],
|
hotels: HotelResponse[],
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useSession } from "next-auth/react"
|
|
||||||
import { useEffect, useMemo, useRef } from "react"
|
import { useEffect, useMemo, useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import {
|
|
||||||
BookingCodeFilterEnum,
|
|
||||||
useBookingCodeFilterStore,
|
|
||||||
} from "@scandic-hotels/booking-flow/stores/bookingCode-filter"
|
|
||||||
import {
|
import {
|
||||||
alternativeHotelsMap,
|
alternativeHotelsMap,
|
||||||
selectHotelMap,
|
selectHotelMap,
|
||||||
@@ -17,14 +12,16 @@ import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop"
|
|||||||
import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton"
|
import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton"
|
||||||
import { HotelCard } from "@scandic-hotels/design-system/HotelCard"
|
import { HotelCard } from "@scandic-hotels/design-system/HotelCard"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
|
||||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
import useLang from "../../hooks/useLang"
|
||||||
|
import { mapApiImagesToGalleryImages } from "../../misc/imageGallery"
|
||||||
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
|
import {
|
||||||
import useLang from "@/hooks/useLang"
|
BookingCodeFilterEnum,
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
useBookingCodeFilterStore,
|
||||||
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
} from "../../stores/bookingCode-filter"
|
||||||
|
import { useHotelFilterStore } from "../../stores/hotel-filters"
|
||||||
|
import { useHotelsMapStore } from "../../stores/hotels-map"
|
||||||
|
import { HotelDetailsSidePeek } from "../HotelDetailsSidePeek"
|
||||||
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
|
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
|
||||||
import { getSortedHotels } from "./utils"
|
import { getSortedHotels } from "./utils"
|
||||||
|
|
||||||
@@ -32,10 +29,19 @@ import styles from "./hotelCardListing.module.css"
|
|||||||
|
|
||||||
import type { HotelType } from "@scandic-hotels/common/constants/hotelType"
|
import type { HotelType } from "@scandic-hotels/common/constants/hotelType"
|
||||||
|
|
||||||
import {
|
import type { HotelResponse } from "../SelectHotel/helpers"
|
||||||
type HotelCardListingProps,
|
|
||||||
HotelCardListingTypeEnum,
|
export enum HotelCardListingTypeEnum {
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
MapListing = "mapListing",
|
||||||
|
PageListing = "pageListing",
|
||||||
|
}
|
||||||
|
|
||||||
|
type HotelCardListingProps = {
|
||||||
|
hotelData: HotelResponse[]
|
||||||
|
unfilteredHotelCount: number
|
||||||
|
type?: HotelCardListingTypeEnum
|
||||||
|
isAlternative?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export default function HotelCardListing({
|
export default function HotelCardListing({
|
||||||
hotelData,
|
hotelData,
|
||||||
@@ -46,8 +52,7 @@ export default function HotelCardListing({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { data: session } = useSession()
|
const isUserLoggedIn = useIsLoggedIn()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||||
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
import { SortOrder } from "../../misc/sortOrder"
|
||||||
import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers"
|
|
||||||
|
import type { HotelResponse } from "../SelectHotel/helpers"
|
||||||
|
|
||||||
function getPricePerNight(hotel: HotelResponse): number {
|
function getPricePerNight(hotel: HotelResponse): number {
|
||||||
return (
|
return (
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import AdditionalAmenities from "@scandic-hotels/booking-flow/components/AdditionalAmenities"
|
|
||||||
import Contact from "@scandic-hotels/booking-flow/components/Contact"
|
|
||||||
import BreakfastAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/BreakfastAccordionItem"
|
|
||||||
import CheckInCheckOutAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/CheckInCheckOutAccordionItem"
|
|
||||||
import ParkingAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/ParkingAccordionItem"
|
|
||||||
import Accordion from "@scandic-hotels/design-system/Accordion"
|
import Accordion from "@scandic-hotels/design-system/Accordion"
|
||||||
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
|
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
|
||||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||||
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
|
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { trackAccordionClick } from "@/utils/tracking"
|
import { useTrackingContext } from "../../../trackingContext"
|
||||||
|
import AdditionalAmenities from "../../AdditionalAmenities"
|
||||||
|
import Contact from "../../Contact"
|
||||||
|
import BreakfastAccordionItem from "../../SidePeekAccordions/BreakfastAccordionItem"
|
||||||
|
import CheckInCheckOutAccordionItem from "../../SidePeekAccordions/CheckInCheckOutAccordionItem"
|
||||||
|
import ParkingAccordionItem from "../../SidePeekAccordions/ParkingAccordionItem"
|
||||||
|
|
||||||
import styles from "./hotelSidePeek.module.css"
|
import styles from "./hotelSidePeek.module.css"
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@ function AccessibilityAccordionItem({
|
|||||||
elevatorPitch,
|
elevatorPitch,
|
||||||
}: AccessibilityAccordionItemProps) {
|
}: AccessibilityAccordionItemProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const tracking = useTrackingContext()
|
||||||
|
|
||||||
if (!elevatorPitch) {
|
if (!elevatorPitch) {
|
||||||
return null
|
return null
|
||||||
@@ -95,7 +96,7 @@ function AccessibilityAccordionItem({
|
|||||||
iconName={IconName.Accessibility}
|
iconName={IconName.Accessibility}
|
||||||
className={styles.accordionItem}
|
className={styles.accordionItem}
|
||||||
variant="sidepeek"
|
variant="sidepeek"
|
||||||
onOpen={() => trackAccordionClick("amenities:accessibility")}
|
onOpen={() => tracking.trackAccordionItemOpen("amenities:accessibility")}
|
||||||
>
|
>
|
||||||
<div className={styles.accessibilityContent}>
|
<div className={styles.accessibilityContent}>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
@@ -6,8 +6,7 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
|||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import SidePeekSelfControlled from "@scandic-hotels/design-system/SidePeekSelfControlled"
|
import SidePeekSelfControlled from "@scandic-hotels/design-system/SidePeekSelfControlled"
|
||||||
|
|
||||||
import { trackOpenSidePeekEvent } from "@/utils/tracking"
|
import { useTrackingContext } from "../../trackingContext"
|
||||||
|
|
||||||
import { HotelSidePeekContent } from "./HotelSidePeekContent"
|
import { HotelSidePeekContent } from "./HotelSidePeekContent"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -16,7 +15,9 @@ import type {
|
|||||||
Restaurant,
|
Restaurant,
|
||||||
} from "@scandic-hotels/trpc/types/hotel"
|
} from "@scandic-hotels/trpc/types/hotel"
|
||||||
|
|
||||||
import { SidePeekEnum } from "@/types/sidepeek"
|
enum SidePeekEnum {
|
||||||
|
hotelDetails = "hotel-detail-side-peek",
|
||||||
|
}
|
||||||
|
|
||||||
interface HotelDetailsSidePeekProps {
|
interface HotelDetailsSidePeekProps {
|
||||||
hotel: Hotel & { url: string | null }
|
hotel: Hotel & { url: string | null }
|
||||||
@@ -48,7 +49,7 @@ const buttonPropsMap: Record<
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HotelDetailsSidePeek({
|
export function HotelDetailsSidePeek({
|
||||||
hotel,
|
hotel,
|
||||||
restaurants,
|
restaurants,
|
||||||
additionalHotelData,
|
additionalHotelData,
|
||||||
@@ -56,6 +57,7 @@ export default function HotelDetailsSidePeek({
|
|||||||
wrapping = true,
|
wrapping = true,
|
||||||
buttonVariant,
|
buttonVariant,
|
||||||
}: HotelDetailsSidePeekProps) {
|
}: HotelDetailsSidePeekProps) {
|
||||||
|
const tracking = useTrackingContext()
|
||||||
const buttonProps = buttonPropsMap[buttonVariant]
|
const buttonProps = buttonPropsMap[buttonVariant]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,7 +66,7 @@ export default function HotelDetailsSidePeek({
|
|||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
wrapping={wrapping}
|
wrapping={wrapping}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
trackOpenSidePeekEvent({
|
tracking.trackOpenSidePeek({
|
||||||
name: SidePeekEnum.hotelDetails,
|
name: SidePeekEnum.hotelDetails,
|
||||||
hotelId: hotel.operaId,
|
hotelId: hotel.operaId,
|
||||||
includePathname: true,
|
includePathname: true,
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useSession } from "next-auth/react"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -17,12 +16,12 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton
|
|||||||
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
import Subtitle from "@scandic-hotels/design-system/Subtitle"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
import useLang from "../../hooks/useLang"
|
||||||
|
|
||||||
import styles from "./listingHotelCardDialog.module.css"
|
import styles from "./listingHotelCardDialog.module.css"
|
||||||
|
|
||||||
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
import type { HotelPin } from "../HotelCardDialogListing/utils"
|
||||||
|
|
||||||
interface ListingHotelCardProps {
|
interface ListingHotelCardProps {
|
||||||
data: HotelPin
|
data: HotelPin
|
||||||
@@ -38,8 +37,7 @@ export default function ListingHotelCardDialog({
|
|||||||
|
|
||||||
const [imageError, setImageError] = useState(false)
|
const [imageError, setImageError] = useState(false)
|
||||||
|
|
||||||
const { data: session } = useSession()
|
const isUserLoggedIn = useIsLoggedIn()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
|
||||||
const {
|
const {
|
||||||
bookingCode,
|
bookingCode,
|
||||||
name,
|
name,
|
||||||
@@ -130,7 +128,6 @@ export default function ListingHotelCardDialog({
|
|||||||
<Subtitle type="two">
|
<Subtitle type="two">
|
||||||
{publicPrice} {currency}
|
{publicPrice} {currency}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
{memberPrice && <Caption>/</Caption>}
|
{memberPrice && <Caption>/</Caption>}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -169,8 +166,7 @@ export default function ListingHotelCardDialog({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
{chequePrice.additionalPricePerStay > 0
|
{chequePrice.additionalPricePerStay > 0
|
||||||
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
? " + " +
|
||||||
" + " +
|
|
||||||
intl.formatMessage(
|
intl.formatMessage(
|
||||||
{
|
{
|
||||||
defaultMessage: "{price} {currency}",
|
defaultMessage: "{price} {currency}",
|
||||||
@@ -182,7 +178,6 @@ export default function ListingHotelCardDialog({
|
|||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
<span>
|
<span>
|
||||||
/
|
/
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
@@ -204,7 +199,6 @@ export default function ListingHotelCardDialog({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
||||||
<span>
|
<span>
|
||||||
/
|
/
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
@@ -20,20 +20,25 @@ import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
|||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import useInitializeFiltersFromUrl from "../../../../hooks/useInitializeFiltersFromUrl"
|
||||||
|
import { SortOrder } from "../../../../misc/sortOrder"
|
||||||
import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl"
|
import { useHotelFilterStore } from "../../../../stores/hotel-filters"
|
||||||
|
|
||||||
import { DEFAULT_SORT } from "../../HotelSorter"
|
import { DEFAULT_SORT } from "../../HotelSorter"
|
||||||
import FilterContent from "../FilterContent"
|
import FilterContent from "../FilterContent"
|
||||||
|
|
||||||
import styles from "./filterAndSortModal.module.css"
|
import styles from "./filterAndSortModal.module.css"
|
||||||
|
|
||||||
import type { FilterAndSortModalProps } from "@/types/components/hotelReservation/selectHotel/filterAndSortModal"
|
import type { CategorizedHotelFilters } from "../../../../types"
|
||||||
import {
|
|
||||||
type SortItem,
|
type SortItem = {
|
||||||
SortOrder,
|
label: string
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterAndSortModalProps = {
|
||||||
|
filters: CategorizedHotelFilters
|
||||||
|
setShowSkeleton?: (showSkeleton: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
export default function FilterAndSortModal({
|
export default function FilterAndSortModal({
|
||||||
filters,
|
filters,
|
||||||
@@ -7,7 +7,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import styles from "./filterCheckbox.module.css"
|
import styles from "./filterCheckbox.module.css"
|
||||||
|
|
||||||
import type { FilterCheckboxProps } from "@/types/components/hotelReservation/selectHotel/filterCheckbox"
|
type FilterCheckboxProps = {
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isSelected: boolean
|
||||||
|
onChange: (filterId: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
export default function FilterCheckbox({
|
export default function FilterCheckbox({
|
||||||
isSelected,
|
isSelected,
|
||||||
@@ -8,10 +8,7 @@ import FilterCheckbox from "./FilterCheckbox"
|
|||||||
|
|
||||||
import styles from "./filterContent.module.css"
|
import styles from "./filterContent.module.css"
|
||||||
|
|
||||||
import type {
|
import type { CategorizedHotelFilters, HotelFilter } from "../../../../types"
|
||||||
CategorizedHotelFilters,
|
|
||||||
HotelFilter,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
|
||||||
|
|
||||||
interface FilterContentProps {
|
interface FilterContentProps {
|
||||||
filters: CategorizedHotelFilters
|
filters: CategorizedHotelFilters
|
||||||
@@ -83,7 +80,6 @@ export default function FilterContent({
|
|||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
{!isDisabled && (
|
{!isDisabled && (
|
||||||
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
|
||||||
<span>{`(${combinedFiltersCount > 0 ? combinedFiltersCount : filterCount})`}</span>
|
<span>{`(${combinedFiltersCount > 0 ? combinedFiltersCount : filterCount})`}</span>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
@@ -3,17 +3,20 @@
|
|||||||
import { usePathname, useSearchParams } from "next/navigation"
|
import { usePathname, useSearchParams } from "next/navigation"
|
||||||
import { useCallback, useEffect } from "react"
|
import { useCallback, useEffect } from "react"
|
||||||
|
|
||||||
import { trackEvent } from "@scandic-hotels/common/tracking/base"
|
import useInitializeFiltersFromUrl from "../../../../hooks/useInitializeFiltersFromUrl"
|
||||||
|
import { useHotelFilterStore } from "../../../../stores/hotel-filters"
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useTrackingContext } from "../../../../trackingContext"
|
||||||
|
|
||||||
import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl"
|
|
||||||
|
|
||||||
import FilterContent from "../FilterContent"
|
import FilterContent from "../FilterContent"
|
||||||
|
|
||||||
import type { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
import type { CategorizedHotelFilters } from "../../../../types"
|
||||||
|
|
||||||
|
type HotelFiltersProps = {
|
||||||
|
filters: CategorizedHotelFilters
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
||||||
|
const tracking = useTrackingContext()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
useInitializeFiltersFromUrl()
|
useInitializeFiltersFromUrl()
|
||||||
@@ -41,13 +44,18 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
|||||||
.map((id) => surroundingsMap.get(id))
|
.map((id) => surroundingsMap.get(id))
|
||||||
.join(",")
|
.join(",")
|
||||||
|
|
||||||
trackEvent({
|
tracking.trackGenericEvent({
|
||||||
event: "filterUsed",
|
event: "filterUsed",
|
||||||
filter: {
|
filter: {
|
||||||
filtersUsed: `Filters values - hotelfacilities:${hotelFacilitiesFilter}|hotelsurroundings:${hotelSurroundingsFilter}`,
|
filtersUsed: `Filters values - hotelfacilities:${hotelFacilitiesFilter}|hotelsurroundings:${hotelSurroundingsFilter}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [activeFilters, filters.facilityFilters, filters.surroundingsFilters])
|
}, [
|
||||||
|
tracking,
|
||||||
|
activeFilters,
|
||||||
|
filters.facilityFilters,
|
||||||
|
filters.surroundingsFilters,
|
||||||
|
])
|
||||||
|
|
||||||
// Update the URL when the filters changes
|
// Update the URL when the filters changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -4,7 +4,7 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useHotelFilterStore } from "../../../stores/hotel-filters"
|
||||||
|
|
||||||
export default function HotelCount() {
|
export default function HotelCount() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user