Merged in feat/SW-1353 (pull request #1513)
feat: add multiroom tracking to booking flow Approved-by: Linus Flood
This commit is contained in:
@@ -18,11 +18,14 @@ import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/D
|
||||
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
||||
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
import { getIntl } from "@/i18n"
|
||||
import RoomProvider from "@/providers/Details/RoomProvider"
|
||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||
import { convertSearchParamsToObj } from "@/utils/url"
|
||||
|
||||
import { getTracking } from "./tracking"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
@@ -57,34 +60,30 @@ export default async function DetailsPage({
|
||||
const childrenAsString =
|
||||
room.childrenInRoom && generateChildrenString(room.childrenInRoom)
|
||||
|
||||
const selectedRoomAvailabilityInput = {
|
||||
const packages = room.packages
|
||||
? await getPackages({
|
||||
adults: room.adults,
|
||||
children: room.childrenInRoom?.length,
|
||||
endDate: booking.toDate,
|
||||
hotelId: booking.hotelId,
|
||||
packageCodes: room.packages,
|
||||
startDate: booking.fromDate,
|
||||
lang,
|
||||
})
|
||||
: null
|
||||
|
||||
const roomAvailability = await getSelectedRoomAvailability({
|
||||
adults: room.adults,
|
||||
bookingCode: booking.bookingCode,
|
||||
children: childrenAsString,
|
||||
counterRateCode: room.counterRateCode,
|
||||
hotelId: booking.hotelId,
|
||||
packageCodes: room.packages,
|
||||
rateCode: room.rateCode,
|
||||
roomStayStartDate: booking.fromDate,
|
||||
roomStayEndDate: booking.toDate,
|
||||
roomStayStartDate: booking.fromDate,
|
||||
roomTypeCode: room.roomTypeCode,
|
||||
counterRateCode: room.counterRateCode,
|
||||
bookingCode: booking.bookingCode,
|
||||
}
|
||||
|
||||
const packages = room.packages
|
||||
? await getPackages({
|
||||
adults: room.adults,
|
||||
children: room.childrenInRoom?.length,
|
||||
endDate: booking.toDate,
|
||||
hotelId: booking.hotelId,
|
||||
packageCodes: room.packages,
|
||||
startDate: booking.fromDate,
|
||||
lang,
|
||||
})
|
||||
: null
|
||||
|
||||
const roomAvailability = await getSelectedRoomAvailability(
|
||||
selectedRoomAvailabilityInput
|
||||
)
|
||||
})
|
||||
|
||||
if (!roomAvailability) {
|
||||
// redirect back to select-rate if availability call fails
|
||||
@@ -98,8 +97,11 @@ export default async function DetailsPage({
|
||||
mustBeGuaranteed: roomAvailability.mustBeGuaranteed,
|
||||
memberMustBeGuaranteed: roomAvailability.memberMustBeGuaranteed,
|
||||
packages,
|
||||
rateTitle: roomAvailability.rateTitle,
|
||||
rate: roomAvailability.rate,
|
||||
rateDefinitionTitle: roomAvailability.rateDefinitionTitle,
|
||||
rateDetails: roomAvailability.rateDetails ?? [],
|
||||
rateTitle: roomAvailability.rateTitle,
|
||||
rateType: roomAvailability.rateType,
|
||||
roomType: roomAvailability.selectedRoom.roomType,
|
||||
roomTypeCode: roomAvailability.selectedRoom.roomTypeCode,
|
||||
roomRate: {
|
||||
@@ -122,41 +124,28 @@ export default async function DetailsPage({
|
||||
language: lang,
|
||||
})
|
||||
const user = await getProfileSafely()
|
||||
// const userTrackingData = await getUserTracking()
|
||||
|
||||
if (!hotelData || !rooms) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
// const arrivalDate = new Date(booking.fromDate)
|
||||
// const departureDate = new Date(booking.toDate)
|
||||
const { hotel } = hotelData
|
||||
|
||||
// TODO: add tracking
|
||||
// const initialHotelsTrackingData: TrackingSDKHotelInfo = {
|
||||
// searchTerm: searchParams.city,
|
||||
// arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
||||
// departureDate: format(departureDate, "yyyy-MM-dd"),
|
||||
// noOfAdults: adults,
|
||||
// noOfChildren: childrenInRoom?.length,
|
||||
// ageOfChildren: childrenInRoom?.map((c) => c.age).join(","),
|
||||
// childBedPreference: childrenInRoom
|
||||
// ?.map((c) => ChildBedMapEnum[c.bed])
|
||||
// .join("|"),
|
||||
// noOfRooms: 1, // // TODO: Handle multiple rooms
|
||||
// duration: differenceInCalendarDays(departureDate, arrivalDate),
|
||||
// leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
||||
// searchType: "hotel",
|
||||
// bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
||||
// country: hotel?.address.country,
|
||||
// hotelID: hotel?.operaId,
|
||||
// region: hotel?.address.city,
|
||||
// }
|
||||
const { hotelsTrackingData, pageTrackingData } = getTracking(
|
||||
booking,
|
||||
hotel,
|
||||
rooms,
|
||||
!!breakfastPackages?.length,
|
||||
searchParams.city,
|
||||
!!user,
|
||||
lang
|
||||
)
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
const firstRoom = rooms[0]
|
||||
const multirooms = rooms.slice(1)
|
||||
|
||||
const isRoomNotAvailable = rooms.some((room) => !room.isAvailable)
|
||||
return (
|
||||
<EnterDetailsProvider
|
||||
@@ -214,6 +203,7 @@ export default async function DetailsPage({
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
<TrackingSDK hotelInfo={hotelsTrackingData} pageData={pageTrackingData} />
|
||||
</EnterDetailsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||
|
||||
import { getSpecialRoomType } from "@/utils/specialRoomType"
|
||||
|
||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKHotelInfo,
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
import type { Room } from "@/types/providers/details/room"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type { SelectHotelParams } from "@/utils/url"
|
||||
|
||||
export function getTracking(
|
||||
booking: SelectHotelParams<SelectRateSearchParams>,
|
||||
hotel: Hotel,
|
||||
rooms: Room[],
|
||||
offersBreakfast: boolean,
|
||||
city: string | undefined,
|
||||
isMember: boolean,
|
||||
lang: Lang
|
||||
) {
|
||||
const arrivalDate = new Date(booking.fromDate)
|
||||
const departureDate = new Date(booking.toDate)
|
||||
|
||||
const pageTrackingData: TrackingSDKPageData = {
|
||||
channel: TrackingChannelEnum.hotelreservation,
|
||||
domainLanguage: lang,
|
||||
pageId: "details",
|
||||
pageName: "hotelreservation|details",
|
||||
pageType: "bookingroomsandratespage",
|
||||
siteSections: "hotelreservation|details",
|
||||
siteVersion: "new-web",
|
||||
}
|
||||
|
||||
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
||||
ageOfChildren: booking.rooms
|
||||
.map(
|
||||
(room) => room.childrenInRoom?.map((kid) => kid.age).join(",") ?? "-"
|
||||
)
|
||||
.join("|"),
|
||||
analyticsRateCode: rooms.map((room) => room.rate).join("|"),
|
||||
arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
||||
bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
||||
breakfastOption: rooms
|
||||
.map(() => (offersBreakfast ? "breakfast buffet" : "no breakfast"))
|
||||
.join(","),
|
||||
childBedPreference: booking.rooms
|
||||
.map(
|
||||
(room) =>
|
||||
room.childrenInRoom
|
||||
?.map((kid) => ChildBedMapEnum[kid.bed])
|
||||
.join(",") ?? "-"
|
||||
)
|
||||
.join("|"),
|
||||
country: hotel?.address.country,
|
||||
departureDate: format(departureDate, "yyyy-MM-dd"),
|
||||
duration: differenceInCalendarDays(departureDate, arrivalDate),
|
||||
hotelID: hotel?.operaId,
|
||||
leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
||||
noOfAdults: booking.rooms.map((room) => room.adults).join(","),
|
||||
noOfChildren: booking.rooms
|
||||
.map((room) => room.childrenInRoom?.length ?? 0)
|
||||
.join(","),
|
||||
noOfRooms: booking.rooms.length,
|
||||
rateCode: rooms
|
||||
.map((room, idx) => {
|
||||
if (idx === 0 && isMember && room.roomRate.memberRate) {
|
||||
return room.roomRate.memberRate?.rateCode
|
||||
}
|
||||
return room.roomRate.publicRate?.rateCode
|
||||
})
|
||||
.join("|"),
|
||||
rateCodeCancellationRule: rooms
|
||||
.map((room) => room.cancellationText.toLowerCase())
|
||||
.join(","),
|
||||
rateCodeName: rooms.map((room) => room.rateDefinitionTitle).join(","),
|
||||
rateCodeType: rooms.map((room) => room.rateType.toLowerCase()).join(","),
|
||||
region: hotel?.address.city,
|
||||
revenueCurrencyCode: rooms
|
||||
.map(
|
||||
(room) =>
|
||||
room.roomRate.publicRate?.localPrice.currency ??
|
||||
room.roomRate.memberRate?.localPrice.currency
|
||||
)
|
||||
.join(","),
|
||||
searchTerm: city,
|
||||
searchType: "hotel",
|
||||
specialRoomType: rooms
|
||||
.map((room) => getSpecialRoomType(room.packages))
|
||||
.join(","),
|
||||
}
|
||||
|
||||
return {
|
||||
hotelsTrackingData,
|
||||
pageTrackingData,
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { beforeAll, describe, expect, it } from "@jest/globals"
|
||||
|
||||
import { getValidFromDate, getValidToDate } from "./getValidDates"
|
||||
|
||||
const NOW = new Date("2020-10-01T00:00:00Z")
|
||||
|
||||
describe("getValidFromDate", () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers({ now: NOW })
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
describe("getValidFromDate", () => {
|
||||
it("returns today when empty string is provided", () => {
|
||||
const actual = getValidFromDate("")
|
||||
expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns today when undefined is provided", () => {
|
||||
const actual = getValidFromDate(undefined)
|
||||
expect(actual.toISOString()).toBe("2020-10-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns given date in utc", () => {
|
||||
const actual = getValidFromDate("2024-01-01")
|
||||
expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getValidToDate", () => {
|
||||
it("returns day after fromDate when empty string is provided", () => {
|
||||
const actual = getValidToDate("", NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns day after fromDate when undefined is provided", () => {
|
||||
const actual = getValidToDate(undefined, NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("returns given date in utc", () => {
|
||||
const actual = getValidToDate("2024-01-01", NOW)
|
||||
expect(actual.toISOString()).toBe("2024-01-01T00:00:00.000Z")
|
||||
})
|
||||
|
||||
it("fallsback to day after fromDate when given date is before fromDate", () => {
|
||||
const actual = getValidToDate("2020-09-30", NOW)
|
||||
expect(actual.toISOString()).toBe("2020-10-02T00:00:00.000Z")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import type { Dayjs } from "dayjs"
|
||||
|
||||
/**
|
||||
* Get valid dates from stringFromDate and stringToDate making sure that they are not in the past and chronologically correct
|
||||
* @example const { fromDate, toDate} = getValidDates("2021-01-01", "2021-01-02")
|
||||
*/
|
||||
export function getValidDates(
|
||||
stringFromDate: string | undefined,
|
||||
stringToDate: string | undefined
|
||||
): { fromDate: Dayjs; toDate: Dayjs } {
|
||||
const fromDate = getValidFromDate(stringFromDate)
|
||||
const toDate = getValidToDate(stringToDate, fromDate)
|
||||
|
||||
return { fromDate, toDate }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid fromDate from stringFromDate making sure that it is not in the past
|
||||
*/
|
||||
export function getValidFromDate(stringFromDate: string | undefined): Dayjs {
|
||||
const now = dt().utc()
|
||||
if (!stringFromDate) {
|
||||
return now
|
||||
}
|
||||
const toDate = dt(stringFromDate)
|
||||
|
||||
const yesterday = now.subtract(1, "day")
|
||||
if (!toDate.isAfter(yesterday)) {
|
||||
return now
|
||||
}
|
||||
|
||||
return toDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid toDate from stringToDate making sure that it is after fromDate
|
||||
*/
|
||||
export function getValidToDate(
|
||||
stringToDate: string | undefined,
|
||||
fromDate: Dayjs | Date
|
||||
): Dayjs {
|
||||
const tomorrow = dt().utc().add(1, "day")
|
||||
if (!stringToDate) {
|
||||
return tomorrow
|
||||
}
|
||||
|
||||
const toDate = dt(stringToDate)
|
||||
if (toDate.isAfter(fromDate)) {
|
||||
return toDate
|
||||
}
|
||||
|
||||
return tomorrow
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { REDEMPTION } from "@/constants/booking"
|
||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
||||
import { safeTry } from "@/utils/safeTry"
|
||||
import { convertSearchParamsToObj, type SelectHotelParams } from "@/utils/url"
|
||||
|
||||
import type {
|
||||
AlternativeHotelsSearchParams,
|
||||
SelectHotelSearchParams,
|
||||
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||
import type {
|
||||
Child,
|
||||
SelectRateSearchParams,
|
||||
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import {
|
||||
type HotelLocation,
|
||||
isHotelLocation,
|
||||
type Location,
|
||||
} from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
interface HotelSearchDetails<T> {
|
||||
city: Location | null
|
||||
hotel: HotelLocation | null
|
||||
selectHotelParams: SelectHotelParams<T> & { city: string | undefined }
|
||||
adultsInRoom: number[]
|
||||
childrenInRoomString?: string
|
||||
childrenInRoom?: Child[]
|
||||
bookingCode?: string
|
||||
redemption?: boolean
|
||||
}
|
||||
|
||||
export async function getHotelSearchDetails<
|
||||
T extends
|
||||
| SelectHotelSearchParams
|
||||
| SelectRateSearchParams
|
||||
| AlternativeHotelsSearchParams,
|
||||
>(
|
||||
{
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: T & {
|
||||
[key: string]: string
|
||||
}
|
||||
},
|
||||
isAlternativeHotels?: boolean
|
||||
): Promise<HotelSearchDetails<T> | null> {
|
||||
const selectHotelParams = convertSearchParamsToObj<T>(searchParams)
|
||||
|
||||
const [locations, error] = await safeTry(getLocations())
|
||||
if (!locations || error) {
|
||||
return null
|
||||
}
|
||||
|
||||
const hotel =
|
||||
("hotelId" in selectHotelParams &&
|
||||
(locations.find(
|
||||
(location) =>
|
||||
isHotelLocation(location) &&
|
||||
"operaId" in location &&
|
||||
location.operaId === selectHotelParams.hotelId
|
||||
) as HotelLocation | undefined)) ||
|
||||
null
|
||||
|
||||
if (isAlternativeHotels && !hotel) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const cityName = isAlternativeHotels
|
||||
? hotel?.relationships.city.name
|
||||
: "city" in selectHotelParams
|
||||
? (selectHotelParams.city as string | undefined)
|
||||
: undefined
|
||||
|
||||
const city =
|
||||
(typeof cityName === "string" &&
|
||||
locations.find(
|
||||
(location) => location.name.toLowerCase() === cityName.toLowerCase()
|
||||
)) ||
|
||||
null
|
||||
|
||||
if (!city && !hotel) return notFound()
|
||||
if (isAlternativeHotels && (!city || !hotel)) return notFound()
|
||||
|
||||
let adultsInRoom: number[] = []
|
||||
let childrenInRoomString: HotelSearchDetails<T>["childrenInRoomString"] =
|
||||
undefined
|
||||
let childrenInRoom: HotelSearchDetails<T>["childrenInRoom"] = undefined
|
||||
|
||||
const { rooms } = selectHotelParams
|
||||
|
||||
if (rooms && rooms.length > 0) {
|
||||
adultsInRoom = rooms.map((room) => room.adults ?? 0)
|
||||
childrenInRoomString = rooms[0].childrenInRoom
|
||||
? generateChildrenString(rooms[0].childrenInRoom)
|
||||
: undefined // TODO: Handle multiple rooms
|
||||
childrenInRoom = rooms[0].childrenInRoom // TODO: Handle multiple rooms
|
||||
}
|
||||
|
||||
return {
|
||||
city,
|
||||
hotel,
|
||||
selectHotelParams: { city: cityName, ...selectHotelParams },
|
||||
adultsInRoom,
|
||||
childrenInRoomString,
|
||||
childrenInRoom,
|
||||
bookingCode: selectHotelParams.bookingCode ?? undefined,
|
||||
redemption: selectHotelParams.searchType === REDEMPTION,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user