Files
web/apps/scandic-web/stores/select-rate/index.ts
Hrishikesh Vaipurkar 0a4bf40a15 Merged in chore/SW-3321-move-selectratecontext-to- (pull request #2729)
chore(SW-3321): Moved Select rate context to booking-flow package

* chore(SW-3321): Moved Select rate context to booking-flow package

* chore(SW-3321): Optimised code


Approved-by: Joakim Jäderberg
2025-09-02 07:40:01 +00:00

538 lines
18 KiB
TypeScript

import { produce } from "immer"
import { useContext } from "react"
import { create, useStore } from "zustand"
import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter"
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel"
import { RatesContext } from "@/contexts/Rates"
import {
findDefaultCurrency,
findProductInRoom,
findSelectedRate,
} from "./helpers"
import type {
InitialState,
RatesState,
} from "@scandic-hotels/booking-flow/types/stores/rates"
import type { Package, Packages } from "@scandic-hotels/trpc/types/packages"
import type { PriceProduct } from "@scandic-hotels/trpc/types/roomAvailability"
export function createRatesStore({
booking,
hotelType,
labels,
pathname,
roomCategories,
roomsAvailability,
initialActiveRoom,
vat,
}: InitialState) {
function updateUrl(booking: RatesState["booking"], activeRoom: number = -1) {
const searchParams = serializeBookingSearchParams(booking, {
initialSearchParams: new URLSearchParams(window.location.search),
})
if (activeRoom >= 0) {
searchParams.set("modifyRateIndex", activeRoom.toString())
} else {
searchParams.delete("modifyRateIndex")
}
window.history.replaceState({}, "", `${pathname}?${searchParams}`)
}
const packageOptions = [
{
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
description: labels.accessibilityRoom,
},
{
code: RoomPackageCodeEnum.ALLERGY_ROOM,
description: labels.allergyRoom,
},
{
code: RoomPackageCodeEnum.PET_ROOM,
description: labels.petRoom,
},
]
const roomsPackages: NonNullable<Packages>[] = []
const roomConfigurations: RatesState["roomConfigurations"] = []
if (roomsAvailability) {
for (const availability of roomsAvailability) {
if ("error" in availability) {
// Availability request failed, default to empty array
roomConfigurations.push([])
roomsPackages.push([])
} else {
roomConfigurations.push(availability.roomConfigurations)
roomsPackages.push(availability.packages)
}
}
}
const rateSummary: RatesState["rateSummary"] = []
for (const [idx, room] of booking.rooms.entries()) {
if (room.rateCode && room.roomTypeCode) {
const roomConfiguration = roomConfigurations?.[idx]
const selectedRoom = findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
roomConfiguration
)
if (!selectedRoom) {
booking.rooms[idx] = roomWithoutSelection(room)
updateUrl(booking)
continue
}
const product = findProductInRoom(
room.rateCode,
selectedRoom,
room.counterRateCode
)
if (product) {
const roomPackages = roomsPackages[idx].filter((pkg) =>
room.packages?.includes(pkg.code)
)
rateSummary[idx] = {
features: selectedRoom.features,
product,
packages: roomPackages,
rate: product.rate,
roomType: selectedRoom.roomType,
roomTypeCode: selectedRoom.roomTypeCode,
}
} else {
rateSummary[idx] = null
}
}
}
let activeRoom = rateSummary.length
if (initialActiveRoom !== undefined && initialActiveRoom >= 0) {
activeRoom = initialActiveRoom
} else if (rateSummary.length === booking.rooms.length) {
// Finds the first unselected room and sets that to active
// if no unselected rooms it will return -1 and close all rooms
const unselectedRoomIndex = rateSummary.findIndex((rate) => !rate)
activeRoom = unselectedRoomIndex
}
const isRedemptionBooking = booking.searchType === SEARCH_TYPE_REDEMPTION
const defaultCurrency = findDefaultCurrency(roomsAvailability)
return create<RatesState>()((set) => {
return {
activeRoom,
booking,
packageOptions,
hotelType,
isRedemptionBooking,
rateSummary,
roomConfigurations,
roomCategories,
roomsPackages,
roomsAvailability,
vat,
defaultCurrency,
rooms: booking.rooms.map((room, idx) => {
const roomConfiguration = roomConfigurations[idx]
const roomPackages = roomsPackages[idx]
const selectedPackages =
room.packages
?.map((code) => roomPackages.find((pkg) => pkg.code === code))
.filter((pkg): pkg is Package => Boolean(pkg)) ?? []
const selectedRate =
findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
roomConfiguration
) ?? null
let product = null
if (selectedRate) {
product = findProductInRoom(
room.rateCode,
selectedRate,
room.counterRateCode
)
}
const bookingCode = room.rateCode
? room.bookingCode
: booking.bookingCode
const selectedFilter =
bookingCode && !isRedemptionBooking
? BookingCodeFilterEnum.Discounted
: BookingCodeFilterEnum.All
return {
actions: {
appendRegularRates(roomConfigurations) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingAdditionalRate = false
if (roomConfigurations) {
const rooms = state.rooms[idx].rooms
const updatedRooms = rooms.map((currentRoom) => {
const incomingRoom = roomConfigurations.find(
(room) =>
room.roomType === currentRoom.roomType &&
room.roomTypeCode === currentRoom.roomTypeCode
)
if (incomingRoom) {
let campaign = currentRoom.campaign
if (incomingRoom.campaign.length) {
const newCampaign = [
...campaign,
...incomingRoom.campaign,
].reduce((cpns, cpn) => {
if (cpns.has(cpn.rateDefinition.rateCode)) {
return cpns
}
cpns.set(cpn.rateDefinition.rateCode, cpn)
return cpns
}, new Map<string, PriceProduct>())
campaign = Array.from(newCampaign.values())
}
const currentRoomAvailable =
currentRoom.status === AvailabilityEnum.Available
const incomingRoomAvailable =
incomingRoom.status === AvailabilityEnum.Available
let status = AvailabilityEnum.NotAvailable
if (currentRoomAvailable || incomingRoomAvailable) {
status = AvailabilityEnum.Available
}
return {
...currentRoom,
campaign,
products: [
...currentRoom.products,
...incomingRoom.products,
],
regular: incomingRoom.regular,
status,
}
}
return currentRoom
})
state.rooms[idx].rooms = updatedRooms
} else {
state.rooms[idx].rooms = []
}
})
)
},
closeSection() {
return set(
produce((state: RatesState) => {
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
})
)
},
modifyRate() {
return set(
produce((state: RatesState) => {
state.activeRoom = idx
})
)
},
removeSelectedPackage(code) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
const filteredSelectedPackages = state.rooms[
idx
].selectedPackages.filter((c) => c.code !== code)
state.rooms[idx].selectedPackages = filteredSelectedPackages
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
if (filteredSelectedPackages.length) {
state.booking.rooms[idx].packages =
filteredSelectedPackages.map((pkg) => pkg.code)
} else {
state.booking.rooms[idx].packages = null
}
updateUrl(state.booking, state.activeRoom)
})
)
},
removeSelectedPackages() {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
state.rooms[idx].selectedPackages = []
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
state.booking.rooms[idx].packages = null
updateUrl(state.booking, state.activeRoom)
})
)
},
selectFilter(filter) {
return set(
produce((state: RatesState) => {
state.rooms[idx].selectedFilter = filter
state.rooms[idx].isFetchingAdditionalRate =
filter === BookingCodeFilterEnum.All
})
)
},
selectRate(selectedRate, isUserLoggedIn) {
return set(
produce((state: RatesState) => {
if (!selectedRate.product) {
return
}
state.rooms[idx].selectedRate = selectedRate
state.rateSummary[idx] = {
features: selectedRate.features,
packages: state.rooms[idx].selectedPackages,
product: selectedRate.product,
rate: selectedRate.product.rate,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
const roomNr = idx + 1
const isMainRoom = roomNr === 1
let productRateCode = ""
if ("corporateCheque" in selectedRate.product) {
productRateCode =
selectedRate.product.corporateCheque.rateCode
}
if ("redemption" in selectedRate.product) {
productRateCode = selectedRate.product.redemption.rateCode
}
if ("voucher" in selectedRate.product) {
productRateCode = selectedRate.product.voucher.rateCode
}
if (
"public" in selectedRate.product &&
selectedRate.product.public
) {
productRateCode = selectedRate.product.public.rateCode
}
let hasMemberRate = false
let memberRateCode = ""
if (
"member" in selectedRate.product &&
selectedRate.product.member
) {
hasMemberRate = true
memberRateCode = selectedRate.product.member.rateCode
}
const isMemberRate =
isUserLoggedIn && isMainRoom && hasMemberRate
state.rooms[idx].bookingRoom.rateCode = isMemberRate
? memberRateCode
: productRateCode
if (!isMemberRate && hasMemberRate) {
state.rooms[idx].bookingRoom.counterRateCode =
memberRateCode
}
state.rooms[idx].bookingRoom.roomTypeCode =
selectedRate.roomTypeCode
state.rooms[idx].bookingRoom.bookingCode =
selectedRate.product.bookingCode
const counterratecode = isMemberRate
? productRateCode
: memberRateCode
if (counterratecode) {
state.booking.rooms[idx].counterRateCode = counterratecode
} else {
state.booking.rooms[idx].counterRateCode = null
}
const rateCode = isMemberRate
? memberRateCode
: productRateCode
if (rateCode) {
state.booking.rooms[idx].rateCode = rateCode
}
if (selectedRate.product.bookingCode) {
state.booking.rooms[idx].bookingCode =
selectedRate.product.bookingCode
} else {
if (state.booking.rooms[idx].bookingCode) {
state.booking.rooms[idx].bookingCode = null
}
}
state.booking.rooms[idx].roomTypeCode =
selectedRate.roomTypeCode
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
updateUrl(state.booking)
})
)
},
selectPackages(selectedPackages) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
const pkgs = state.roomsPackages[idx].filter((pkg) =>
selectedPackages.includes(pkg.code)
)
state.rooms[idx].selectedPackages = pkgs
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
if (selectedPackages.length) {
state.booking.rooms[idx].packages = selectedPackages
} else {
state.booking.rooms[idx].packages = null
}
updateUrl(state.booking, state.activeRoom)
})
)
},
updateRooms(rooms) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = false
if (rooms) {
state.rooms[idx].rooms = rooms
const rateSummaryRoom = state.rateSummary[idx]
if (rateSummaryRoom) {
const room = state.rooms[idx].bookingRoom
const selectedRoom = findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
rooms
)
if (selectedRoom) {
rateSummaryRoom.packages =
state.rooms[idx].selectedPackages
} else {
state.booking.rooms[idx] = roomWithoutSelection(
state.booking.rooms[idx]
)
state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null
updateUrl(state.booking)
}
}
} else {
state.rooms[idx].rooms = []
if (state.rateSummary[idx]) {
state.booking.rooms[idx] = roomWithoutSelection(
state.booking.rooms[idx]
)
state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null
updateUrl(state.booking)
}
}
})
)
},
},
bookingRoom: room,
isFetchingAdditionalRate: false,
isFetchingPackages: false,
rooms: roomConfiguration,
selectedFilter,
selectedPackages,
selectedRate:
selectedRate && product
? {
features: selectedRate.features,
packages: selectedPackages,
product,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
: null,
}
}),
}
})
}
export function useRatesStore<T>(selector: (store: RatesState) => T) {
const store = useContext(RatesContext)
if (!store) {
throw new Error("useRatesStore must be used within RatesProvider")
}
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,
}
}