Merged in feat/sw-2857-refactor-booking-flow-url-updates (pull request #2302)
feat(SW-2857): Refactor booking flow url updates * Add support for removing parameters when using initial values in serializeSearchParams * Don't manually write search params in rate store * Booking is already from live search params so no need * Fix input type in serializeBookingSearchParams Approved-by: Linus Flood
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
import { useState, useTransition } from "react"
|
import { useState, useTransition } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -32,7 +32,6 @@ export default function RateSummary() {
|
|||||||
isFetchingPackages,
|
isFetchingPackages,
|
||||||
rateSummary,
|
rateSummary,
|
||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
searchParams,
|
|
||||||
} = useRatesStore((state) => ({
|
} = useRatesStore((state) => ({
|
||||||
bookingCode: state.booking.bookingCode,
|
bookingCode: state.booking.bookingCode,
|
||||||
bookingRooms: state.booking.rooms,
|
bookingRooms: state.booking.rooms,
|
||||||
@@ -43,7 +42,6 @@ export default function RateSummary() {
|
|||||||
isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages),
|
isFetchingPackages: state.rooms.some((room) => room.isFetchingPackages),
|
||||||
rateSummary: state.rateSummary,
|
rateSummary: state.rateSummary,
|
||||||
roomsAvailability: state.roomsAvailability,
|
roomsAvailability: state.roomsAvailability,
|
||||||
searchParams: state.searchParams,
|
|
||||||
}))
|
}))
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
const isUserLoggedIn = isValidClientSession(session)
|
||||||
@@ -51,7 +49,7 @@ export default function RateSummary() {
|
|||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = new URLSearchParams(searchParams)
|
const params = useSearchParams()
|
||||||
const [_, startTransition] = useTransition()
|
const [_, startTransition] = useTransition()
|
||||||
|
|
||||||
if (!roomsAvailability) {
|
if (!roomsAvailability) {
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export default function Form({ close }: { close: () => void }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function getFilteredRates(packages: PackageEnum[]) {
|
async function getFilteredRates(packages: PackageEnum[]) {
|
||||||
|
const bookingCode = bookingRoom.rateCode
|
||||||
|
? bookingRoom.bookingCode
|
||||||
|
: booking.bookingCode
|
||||||
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
|
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
|
||||||
booking: {
|
booking: {
|
||||||
fromDate: booking.fromDate,
|
fromDate: booking.fromDate,
|
||||||
@@ -46,9 +49,7 @@ export default function Form({ close }: { close: () => void }) {
|
|||||||
toDate: booking.toDate,
|
toDate: booking.toDate,
|
||||||
room: {
|
room: {
|
||||||
...bookingRoom,
|
...bookingRoom,
|
||||||
bookingCode: bookingRoom.rateCode
|
bookingCode: bookingCode ?? undefined,
|
||||||
? bookingRoom.bookingCode
|
|
||||||
: booking.bookingCode,
|
|
||||||
packages,
|
packages,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export default function RoomPackageFilter() {
|
|||||||
|
|
||||||
async function deleteSelectedPackage(code: PackageEnum) {
|
async function deleteSelectedPackage(code: PackageEnum) {
|
||||||
removeSelectedPackage(code)
|
removeSelectedPackage(code)
|
||||||
|
const bookingCode = bookingRoom.rateCode
|
||||||
|
? bookingRoom.bookingCode
|
||||||
|
: booking.bookingCode
|
||||||
|
|
||||||
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
|
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
|
||||||
booking: {
|
booking: {
|
||||||
fromDate: booking.fromDate,
|
fromDate: booking.fromDate,
|
||||||
@@ -41,9 +45,7 @@ export default function RoomPackageFilter() {
|
|||||||
toDate: booking.toDate,
|
toDate: booking.toDate,
|
||||||
room: {
|
room: {
|
||||||
...bookingRoom,
|
...bookingRoom,
|
||||||
bookingCode: bookingRoom.rateCode
|
bookingCode: bookingCode ?? undefined,
|
||||||
? bookingRoom.bookingCode
|
|
||||||
: booking.bookingCode,
|
|
||||||
packages: selectedPackages
|
packages: selectedPackages
|
||||||
.filter((pkg) => pkg.code !== code)
|
.filter((pkg) => pkg.code !== code)
|
||||||
.map((pkg) => pkg.code),
|
.map((pkg) => pkg.code),
|
||||||
|
|||||||
@@ -22,39 +22,41 @@ export default function RatesProvider({
|
|||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const store = useMemo(
|
const modifyRateIndex = searchParams.has("modifyRateIndex")
|
||||||
() =>
|
? Number(searchParams.get("modifyRateIndex"))
|
||||||
createRatesStore({
|
: undefined
|
||||||
booking,
|
|
||||||
hotelType,
|
const store = useMemo(() => {
|
||||||
labels: {
|
return createRatesStore({
|
||||||
accessibilityRoom: intl.formatMessage({
|
|
||||||
defaultMessage: "Accessible room",
|
|
||||||
}),
|
|
||||||
allergyRoom: intl.formatMessage({
|
|
||||||
defaultMessage: "Allergy-friendly room",
|
|
||||||
}),
|
|
||||||
petRoom: intl.formatMessage({
|
|
||||||
defaultMessage: "Pet-friendly room",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
pathname,
|
|
||||||
roomCategories,
|
|
||||||
roomsAvailability,
|
|
||||||
searchParams: new URLSearchParams(searchParams),
|
|
||||||
vat,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
booking,
|
booking,
|
||||||
hotelType,
|
hotelType,
|
||||||
intl,
|
labels: {
|
||||||
|
accessibilityRoom: intl.formatMessage({
|
||||||
|
defaultMessage: "Accessible room",
|
||||||
|
}),
|
||||||
|
allergyRoom: intl.formatMessage({
|
||||||
|
defaultMessage: "Allergy-friendly room",
|
||||||
|
}),
|
||||||
|
petRoom: intl.formatMessage({
|
||||||
|
defaultMessage: "Pet-friendly room",
|
||||||
|
}),
|
||||||
|
},
|
||||||
pathname,
|
pathname,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
searchParams,
|
|
||||||
vat,
|
vat,
|
||||||
]
|
initialActiveRoom: modifyRateIndex,
|
||||||
)
|
})
|
||||||
|
}, [
|
||||||
|
booking,
|
||||||
|
hotelType,
|
||||||
|
intl,
|
||||||
|
pathname,
|
||||||
|
roomCategories,
|
||||||
|
roomsAvailability,
|
||||||
|
modifyRateIndex,
|
||||||
|
vat,
|
||||||
|
])
|
||||||
|
|
||||||
return <RatesContext.Provider value={store}>{children}</RatesContext.Provider>
|
return <RatesContext.Provider value={store}>{children}</RatesContext.Provider>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export function findProduct(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function findProductInRoom(
|
export function findProductInRoom(
|
||||||
rateCode: string | undefined,
|
rateCode: string | undefined | null,
|
||||||
room: RoomConfiguration,
|
room: RoomConfiguration,
|
||||||
counterRateCode = ""
|
counterRateCode: string | undefined | null
|
||||||
) {
|
) {
|
||||||
if (!rateCode) {
|
if (!rateCode) {
|
||||||
return null
|
return null
|
||||||
@@ -55,7 +55,7 @@ export function findProductInRoom(
|
|||||||
|
|
||||||
if (room.campaign.length) {
|
if (room.campaign.length) {
|
||||||
const campaignProduct = room.campaign.find((product) =>
|
const campaignProduct = room.campaign.find((product) =>
|
||||||
findProduct(rateCode, product, counterRateCode)
|
findProduct(rateCode, product, counterRateCode || "")
|
||||||
)
|
)
|
||||||
if (campaignProduct) {
|
if (campaignProduct) {
|
||||||
return campaignProduct
|
return campaignProduct
|
||||||
@@ -63,7 +63,7 @@ export function findProductInRoom(
|
|||||||
}
|
}
|
||||||
if (room.code.length) {
|
if (room.code.length) {
|
||||||
const codeProduct = room.code.find((product) =>
|
const codeProduct = room.code.find((product) =>
|
||||||
findProduct(rateCode, product, counterRateCode)
|
findProduct(rateCode, product, counterRateCode || "")
|
||||||
)
|
)
|
||||||
if (codeProduct) {
|
if (codeProduct) {
|
||||||
return codeProduct
|
return codeProduct
|
||||||
@@ -79,7 +79,7 @@ export function findProductInRoom(
|
|||||||
}
|
}
|
||||||
if (room.regular.length) {
|
if (room.regular.length) {
|
||||||
const regularProduct = room.regular.find((product) =>
|
const regularProduct = room.regular.find((product) =>
|
||||||
findProduct(rateCode, product, counterRateCode)
|
findProduct(rateCode, product, counterRateCode || "")
|
||||||
)
|
)
|
||||||
if (regularProduct) {
|
if (regularProduct) {
|
||||||
return regularProduct
|
return regularProduct
|
||||||
@@ -88,9 +88,9 @@ export function findProductInRoom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function findSelectedRate(
|
export function findSelectedRate(
|
||||||
rateCode: string | undefined,
|
rateCode: string | undefined | null,
|
||||||
counterRateCode: string | undefined,
|
counterRateCode: string | undefined | null,
|
||||||
roomTypeCode: string | undefined,
|
roomTypeCode: string | undefined | null,
|
||||||
rooms: RoomConfiguration[] | AvailabilityError
|
rooms: RoomConfiguration[] | AvailabilityError
|
||||||
) {
|
) {
|
||||||
if (!Array.isArray(rooms)) {
|
if (!Array.isArray(rooms)) {
|
||||||
@@ -109,17 +109,6 @@ export function findSelectedRate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearRoomSelectionFromUrl(
|
|
||||||
roomIdx: number,
|
|
||||||
searchParams: URLSearchParams
|
|
||||||
) {
|
|
||||||
searchParams.delete(`room[${roomIdx}].bookingCode`)
|
|
||||||
searchParams.delete(`room[${roomIdx}].counterratecode`)
|
|
||||||
searchParams.delete(`room[${roomIdx}].ratecode`)
|
|
||||||
searchParams.delete(`room[${roomIdx}].roomtype`)
|
|
||||||
return searchParams
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findDefaultCurrency(
|
export function findDefaultCurrency(
|
||||||
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { create, useStore } from "zustand"
|
|||||||
import { REDEMPTION } from "@/constants/booking"
|
import { REDEMPTION } from "@/constants/booking"
|
||||||
|
|
||||||
import { RatesContext } from "@/contexts/Rates"
|
import { RatesContext } from "@/contexts/Rates"
|
||||||
|
import { serializeBookingSearchParams } from "@/utils/url"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearRoomSelectionFromUrl,
|
|
||||||
findDefaultCurrency,
|
findDefaultCurrency,
|
||||||
findProductInRoom,
|
findProductInRoom,
|
||||||
findSelectedRate,
|
findSelectedRate,
|
||||||
@@ -27,9 +27,16 @@ export function createRatesStore({
|
|||||||
pathname,
|
pathname,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
searchParams,
|
initialActiveRoom,
|
||||||
vat,
|
vat,
|
||||||
}: InitialState) {
|
}: InitialState) {
|
||||||
|
function updateUrl(booking: RatesState["booking"]) {
|
||||||
|
const searchParams = serializeBookingSearchParams(booking, {
|
||||||
|
initialSearchParams: new URLSearchParams(window.location.search),
|
||||||
|
})
|
||||||
|
window.history.replaceState({}, "", `${pathname}?${searchParams}`)
|
||||||
|
}
|
||||||
|
|
||||||
const packageOptions = [
|
const packageOptions = [
|
||||||
{
|
{
|
||||||
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
||||||
@@ -73,13 +80,8 @@ export function createRatesStore({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!selectedRoom) {
|
if (!selectedRoom) {
|
||||||
const updatedSearchParams = clearRoomSelectionFromUrl(idx, searchParams)
|
booking.rooms[idx] = roomWithoutSelection(room)
|
||||||
searchParams = updatedSearchParams
|
updateUrl(booking)
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${pathname}?${updatedSearchParams}`
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +110,8 @@ export function createRatesStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let activeRoom = rateSummary.length
|
let activeRoom = rateSummary.length
|
||||||
if (searchParams.has("modifyRateIndex")) {
|
if (initialActiveRoom) {
|
||||||
activeRoom = Number(searchParams.get("modifyRateIndex"))
|
activeRoom = initialActiveRoom
|
||||||
} else if (rateSummary.length === booking.rooms.length) {
|
} else if (rateSummary.length === booking.rooms.length) {
|
||||||
// Finds the first unselected room and sets that to active
|
// Finds the first unselected room and sets that to active
|
||||||
// if no unselected rooms it will return -1 and close all rooms
|
// if no unselected rooms it will return -1 and close all rooms
|
||||||
@@ -128,13 +130,11 @@ export function createRatesStore({
|
|||||||
packageOptions,
|
packageOptions,
|
||||||
hotelType,
|
hotelType,
|
||||||
isRedemptionBooking,
|
isRedemptionBooking,
|
||||||
pathname,
|
|
||||||
rateSummary,
|
rateSummary,
|
||||||
roomConfigurations,
|
roomConfigurations,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
roomsPackages,
|
roomsPackages,
|
||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
searchParams,
|
|
||||||
vat,
|
vat,
|
||||||
defaultCurrency,
|
defaultCurrency,
|
||||||
rooms: booking.rooms.map((room, idx) => {
|
rooms: booking.rooms.map((room, idx) => {
|
||||||
@@ -266,23 +266,14 @@ export function createRatesStore({
|
|||||||
BookingCodeFilterEnum.Discounted
|
BookingCodeFilterEnum.Discounted
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = state.searchParams
|
|
||||||
if (filteredSelectedPackages.length) {
|
if (filteredSelectedPackages.length) {
|
||||||
searchParams.set(
|
state.booking.rooms[idx].packages =
|
||||||
`room[${idx}].packages`,
|
filteredSelectedPackages.map((pkg) => pkg.code)
|
||||||
filteredSelectedPackages.map((pkg) => pkg.code).join(",")
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete(`room[${idx}].packages`)
|
state.booking.rooms[idx].packages = null
|
||||||
}
|
}
|
||||||
|
|
||||||
state.searchParams = searchParams
|
updateUrl(state.booking)
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${state.pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -300,36 +291,8 @@ export function createRatesStore({
|
|||||||
BookingCodeFilterEnum.Discounted
|
BookingCodeFilterEnum.Discounted
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = state.searchParams
|
state.booking.rooms[idx].packages = null
|
||||||
searchParams.delete(`room[${idx}].packages`)
|
updateUrl(state.booking)
|
||||||
|
|
||||||
state.searchParams = searchParams
|
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${state.pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
removeSelectedRoom() {
|
|
||||||
return set(
|
|
||||||
produce((state: RatesState) => {
|
|
||||||
state.rateSummary[idx] = null
|
|
||||||
|
|
||||||
const searchParams = state.searchParams
|
|
||||||
searchParams.delete(`room[${idx}].counterratecode`)
|
|
||||||
searchParams.delete(`room[${idx}].ratecode`)
|
|
||||||
searchParams.delete(`room[${idx}].roomtype`)
|
|
||||||
|
|
||||||
state.searchParams = searchParams
|
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${state.pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -408,43 +371,33 @@ export function createRatesStore({
|
|||||||
state.rooms[idx].bookingRoom.bookingCode =
|
state.rooms[idx].bookingRoom.bookingCode =
|
||||||
selectedRate.product.bookingCode
|
selectedRate.product.bookingCode
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(state.searchParams)
|
|
||||||
const counterratecode = isMemberRate
|
const counterratecode = isMemberRate
|
||||||
? productRateCode
|
? productRateCode
|
||||||
: memberRateCode
|
: memberRateCode
|
||||||
if (counterratecode) {
|
if (counterratecode) {
|
||||||
searchParams.set(
|
state.booking.rooms[idx].counterRateCode = counterratecode
|
||||||
`room[${idx}].counterratecode`,
|
|
||||||
counterratecode
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
if (searchParams.has(`room[${idx}].counterratecode`)) {
|
state.booking.rooms[idx].counterRateCode = null
|
||||||
searchParams.delete(`room[${idx}].counterratecode`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rateCode = isMemberRate
|
const rateCode = isMemberRate
|
||||||
? memberRateCode
|
? memberRateCode
|
||||||
: productRateCode
|
: productRateCode
|
||||||
if (rateCode) {
|
if (rateCode) {
|
||||||
searchParams.set(`room[${idx}].ratecode`, rateCode)
|
state.booking.rooms[idx].rateCode = rateCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRate.product.bookingCode) {
|
if (selectedRate.product.bookingCode) {
|
||||||
searchParams.set(
|
state.booking.rooms[idx].bookingCode =
|
||||||
`room[${idx}].bookingCode`,
|
|
||||||
selectedRate.product.bookingCode
|
selectedRate.product.bookingCode
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
if (searchParams.has(`room[${idx}].bookingCode`)) {
|
if (state.booking.rooms[idx].bookingCode) {
|
||||||
searchParams.delete(`room[${idx}].bookingCode`)
|
state.booking.rooms[idx].bookingCode = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchParams.set(
|
state.booking.rooms[idx].roomTypeCode =
|
||||||
`room[${idx}].roomtype`,
|
|
||||||
selectedRate.roomTypeCode
|
selectedRate.roomTypeCode
|
||||||
)
|
|
||||||
|
|
||||||
if (state.rateSummary.length === state.booking.rooms.length) {
|
if (state.rateSummary.length === state.booking.rooms.length) {
|
||||||
state.activeRoom = -1
|
state.activeRoom = -1
|
||||||
@@ -452,13 +405,7 @@ export function createRatesStore({
|
|||||||
state.activeRoom = idx + 1
|
state.activeRoom = idx + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
state.searchParams = searchParams
|
updateUrl(state.booking)
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${state.pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -479,23 +426,13 @@ export function createRatesStore({
|
|||||||
BookingCodeFilterEnum.Discounted
|
BookingCodeFilterEnum.Discounted
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchParams = state.searchParams
|
|
||||||
if (selectedPackages.length) {
|
if (selectedPackages.length) {
|
||||||
searchParams.set(
|
state.booking.rooms[idx].packages = selectedPackages
|
||||||
`room[${idx}].packages`,
|
|
||||||
selectedPackages.join(",")
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete(`room[${idx}].packages`)
|
state.booking.rooms[idx].packages = null
|
||||||
}
|
}
|
||||||
|
|
||||||
state.searchParams = searchParams
|
updateUrl(state.booking)
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${state.pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -519,36 +456,27 @@ export function createRatesStore({
|
|||||||
rateSummaryRoom.packages =
|
rateSummaryRoom.packages =
|
||||||
state.rooms[idx].selectedPackages
|
state.rooms[idx].selectedPackages
|
||||||
} else {
|
} else {
|
||||||
const searchParams = clearRoomSelectionFromUrl(
|
state.booking.rooms[idx] = roomWithoutSelection(
|
||||||
idx,
|
state.booking.rooms[idx]
|
||||||
state.searchParams
|
|
||||||
)
|
)
|
||||||
state.searchParams = searchParams
|
|
||||||
state.rateSummary[idx] = null
|
state.rateSummary[idx] = null
|
||||||
state.rooms[idx].selectedRate = null
|
state.rooms[idx].selectedRate = null
|
||||||
|
|
||||||
window.history.replaceState(
|
updateUrl(state.booking)
|
||||||
{},
|
|
||||||
"",
|
|
||||||
`${pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.rooms[idx].rooms = []
|
state.rooms[idx].rooms = []
|
||||||
if (state.rateSummary[idx]) {
|
if (state.rateSummary[idx]) {
|
||||||
const searchParams = clearRoomSelectionFromUrl(
|
state.booking.rooms[idx] = roomWithoutSelection(
|
||||||
idx,
|
state.booking.rooms[idx]
|
||||||
state.searchParams
|
|
||||||
)
|
)
|
||||||
state.searchParams = searchParams
|
|
||||||
state.rateSummary[idx] = null
|
state.rateSummary[idx] = null
|
||||||
state.rooms[idx].selectedRate = null
|
state.rooms[idx].selectedRate = null
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
updateUrl(state.booking)
|
||||||
"",
|
|
||||||
`${pathname}?${searchParams}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -587,3 +515,15 @@ export function useRatesStore<T>(selector: (store: RatesState) => T) {
|
|||||||
|
|
||||||
return useStore(store, selector)
|
return useStore(store, selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function roomWithoutSelection(
|
||||||
|
room: RatesState["booking"]["rooms"][number]
|
||||||
|
): RatesState["booking"]["rooms"][number] {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
rateCode: null,
|
||||||
|
counterRateCode: null,
|
||||||
|
roomTypeCode: null,
|
||||||
|
bookingCode: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { Child, Room } from "../hotelReservation/selectRate/selectRate"
|
import type { Child } from "../hotelReservation/selectRate/selectRate"
|
||||||
|
|
||||||
export type ChildBed = {
|
export type ChildBed = {
|
||||||
label: string
|
label: string
|
||||||
value: number
|
value: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GuestsRoom = Required<Pick<Room, "adults" | "childrenInRoom">>
|
export type GuestsRoom = {
|
||||||
|
adults: number
|
||||||
|
childrenInRoom: Child[]
|
||||||
|
}
|
||||||
|
|
||||||
export type GuestsRoomPickerProps = {
|
export type GuestsRoomPickerProps = {
|
||||||
index: number
|
index: number
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ export interface Child {
|
|||||||
|
|
||||||
export interface Room {
|
export interface Room {
|
||||||
adults: number
|
adults: number
|
||||||
bookingCode?: string
|
|
||||||
childrenInRoom?: Child[]
|
childrenInRoom?: Child[]
|
||||||
counterRateCode?: string
|
bookingCode?: string | null
|
||||||
packages?: PackageEnum[]
|
counterRateCode?: string | null
|
||||||
rateCode?: string
|
packages?: PackageEnum[] | null
|
||||||
roomTypeCode?: string
|
rateCode?: string | null
|
||||||
|
roomTypeCode?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectRateBooking = {
|
export type SelectRateBooking = {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ interface Actions {
|
|||||||
modifyRate: () => void
|
modifyRate: () => void
|
||||||
removeSelectedPackage: (code: PackageEnum) => void
|
removeSelectedPackage: (code: PackageEnum) => void
|
||||||
removeSelectedPackages: () => void
|
removeSelectedPackages: () => void
|
||||||
removeSelectedRoom: () => void
|
|
||||||
selectFilter: (filter: BookingCodeFilterEnum) => void
|
selectFilter: (filter: BookingCodeFilterEnum) => void
|
||||||
selectPackages: (codes: PackageEnum[]) => void
|
selectPackages: (codes: PackageEnum[]) => void
|
||||||
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void
|
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void
|
||||||
@@ -58,14 +57,12 @@ export interface RatesState {
|
|||||||
hotelType: string | undefined
|
hotelType: string | undefined
|
||||||
isRedemptionBooking: boolean
|
isRedemptionBooking: boolean
|
||||||
packageOptions: DefaultFilterOptions[]
|
packageOptions: DefaultFilterOptions[]
|
||||||
pathname: string
|
|
||||||
rateSummary: Array<Rate | null>
|
rateSummary: Array<Rate | null>
|
||||||
rooms: SelectedRoom[]
|
rooms: SelectedRoom[]
|
||||||
roomCategories: Room[]
|
roomCategories: Room[]
|
||||||
roomConfigurations: RoomConfiguration[][]
|
roomConfigurations: RoomConfiguration[][]
|
||||||
roomsPackages: Package[][]
|
roomsPackages: Package[][]
|
||||||
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
||||||
searchParams: URLSearchParams
|
|
||||||
vat: number
|
vat: number
|
||||||
defaultCurrency: CurrencyEnum
|
defaultCurrency: CurrencyEnum
|
||||||
}
|
}
|
||||||
@@ -73,14 +70,10 @@ export interface RatesState {
|
|||||||
export interface InitialState
|
export interface InitialState
|
||||||
extends Pick<
|
extends Pick<
|
||||||
RatesState,
|
RatesState,
|
||||||
| "booking"
|
"booking" | "hotelType" | "roomCategories" | "roomsAvailability" | "vat"
|
||||||
| "hotelType"
|
|
||||||
| "pathname"
|
|
||||||
| "roomCategories"
|
|
||||||
| "roomsAvailability"
|
|
||||||
| "searchParams"
|
|
||||||
| "vat"
|
|
||||||
> {
|
> {
|
||||||
|
initialActiveRoom?: number
|
||||||
|
pathname: string
|
||||||
labels: {
|
labels: {
|
||||||
accessibilityRoom: string
|
accessibilityRoom: string
|
||||||
allergyRoom: string
|
allergyRoom: string
|
||||||
|
|||||||
@@ -450,6 +450,53 @@ describe("Serialize search params", () => {
|
|||||||
"city=stockholm&hotel=456&filter[0]=1831&filter[1]=1383&packages=ABC"
|
"city=stockholm&hotel=456&filter[0]=1831&filter[1]=1383&packages=ABC"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("with initial search params and removing existing parameter", () => {
|
||||||
|
const initialSearchParams = new URLSearchParams(
|
||||||
|
"city=stockholm&hotel=123&filters=123,456,789"
|
||||||
|
)
|
||||||
|
const obj = {
|
||||||
|
hotel: null,
|
||||||
|
filters: ["123", "789"],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
initialSearchParams,
|
||||||
|
typeHints: {
|
||||||
|
filters: "COMMA_SEPARATED_ARRAY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"city=stockholm&filters=123,789"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with initial search params and removing values in array", () => {
|
||||||
|
const initialSearchParams = new URLSearchParams(
|
||||||
|
"room[0].adults=1&room[0].rateCode=ABC&room[1].adults=2&room[2].adults=3&room[3].adults=4"
|
||||||
|
)
|
||||||
|
const obj = {
|
||||||
|
room: [
|
||||||
|
{
|
||||||
|
adults: 1,
|
||||||
|
rateCode: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adults: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = serializeSearchParams(obj, {
|
||||||
|
initialSearchParams,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decodeURIComponent(result.toString())).toEqual(
|
||||||
|
"room[0].adults=1&room[1].adults=3&room[2].adults=4"
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Parse serialized search params", () => {
|
describe("Parse serialized search params", () => {
|
||||||
|
|||||||
@@ -150,7 +150,11 @@ type SerializeOptions = {
|
|||||||
* @param obj - The object to serialize
|
* @param obj - The object to serialize
|
||||||
* @param options.keyRenameMap - Optional mapping of keys to rename, ie { "oldKey": "newKey" }
|
* @param options.keyRenameMap - Optional mapping of keys to rename, ie { "oldKey": "newKey" }
|
||||||
* @param options.typeHints - Optional type hints to force certain keys to be treated as comma separated arrays
|
* @param options.typeHints - Optional type hints to force certain keys to be treated as comma separated arrays
|
||||||
|
* @param options.initialSearchParams - Optional initial URL search parameters to merge with the serialized object
|
||||||
* @returns URLSearchParams - The serialized URL search parameters
|
* @returns URLSearchParams - The serialized URL search parameters
|
||||||
|
*
|
||||||
|
* To force a key to be removed when merging with initialSearchParams, set its value to `null` in the object.
|
||||||
|
* Arrays are not merged, they will always replace existing values.
|
||||||
*/
|
*/
|
||||||
export function serializeSearchParams(
|
export function serializeSearchParams(
|
||||||
obj: Record<string, any>,
|
obj: Record<string, any>,
|
||||||
@@ -173,23 +177,31 @@ export function serializeSearchParams(
|
|||||||
const value = obj[key]
|
const value = obj[key]
|
||||||
|
|
||||||
const renamedKey = keyRenameMap[key] || key
|
const renamedKey = keyRenameMap[key] || key
|
||||||
|
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
params.delete(paramKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (typeHints[key] === "COMMA_SEPARATED_ARRAY") {
|
if (typeHints[key] === "COMMA_SEPARATED_ARRAY") {
|
||||||
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
|
|
||||||
params.set(paramKey, value.join(","))
|
params.set(paramKey, value.join(","))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an array value already exists (from initialSearchParams),
|
||||||
|
// we need to first remove it since it can't be merged.
|
||||||
|
deleteAllKeysStartingWith(params, renamedKey)
|
||||||
value.forEach((item, index) => {
|
value.forEach((item, index) => {
|
||||||
const indexedKey = `${renamedKey}[${index}]`
|
const indexedKey = `${renamedKey}[${index}]`
|
||||||
const paramKey = prefix ? `${prefix}.${indexedKey}` : indexedKey
|
const arrayKey = prefix ? `${prefix}.${indexedKey}` : indexedKey
|
||||||
buildParams(item, paramKey)
|
|
||||||
|
buildParams(item, arrayKey)
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramKey = prefix ? `${prefix}.${renamedKey}` : renamedKey
|
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null) {
|
||||||
buildParams(value, paramKey)
|
buildParams(value, paramKey)
|
||||||
continue
|
continue
|
||||||
@@ -207,3 +219,12 @@ export function serializeSearchParams(
|
|||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return typeof value === "object" && value !== null
|
return typeof value === "object" && value !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteAllKeysStartingWith(searchParams: URLSearchParams, key: string) {
|
||||||
|
const keysToDelete = Array.from(searchParams.keys()).filter(
|
||||||
|
(k) => k.startsWith(key) || k === key
|
||||||
|
)
|
||||||
|
for (const k of keysToDelete) {
|
||||||
|
searchParams.delete(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -238,7 +238,11 @@ const reversedKeyRenameMap = Object.fromEntries(
|
|||||||
Object.entries(keyRenameMap).map(([key, value]) => [value, key])
|
Object.entries(keyRenameMap).map(([key, value]) => [value, key])
|
||||||
)
|
)
|
||||||
export function serializeBookingSearchParams(
|
export function serializeBookingSearchParams(
|
||||||
obj: { [key: string]: any },
|
obj:
|
||||||
|
| BookingWidgetSearchData
|
||||||
|
| SelectHotelBooking
|
||||||
|
| SelectRateBooking
|
||||||
|
| DetailsBooking,
|
||||||
{ initialSearchParams }: { initialSearchParams?: URLSearchParams } = {}
|
{ initialSearchParams }: { initialSearchParams?: URLSearchParams } = {}
|
||||||
) {
|
) {
|
||||||
return serializeSearchParams(obj, {
|
return serializeSearchParams(obj, {
|
||||||
|
|||||||
Reference in New Issue
Block a user