import { produce } from "immer" import { ReadonlyURLSearchParams } from "next/navigation" import { useContext } from "react" import { create, useStore } from "zustand" import { RatesContext } from "@/contexts/Rates" import { findProductInRoom, findSelectedRate, isRoomPackageCode, } from "./helpers" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter" import { RateTypeEnum } from "@/types/enums/rateType" import type { InitialState, RatesState } from "@/types/stores/rates" import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability" export function createRatesStore({ booking, hotelType, isUserLoggedIn, labels, packages, pathname, roomCategories, roomsAvailability, searchParams, vat, }: InitialState) { const filterOptions = [ { code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM, description: labels.accessibilityRoom, itemCode: packages.find( (pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM )?.itemCode, }, { code: RoomPackageCodeEnum.ALLERGY_ROOM, description: labels.allergyRoom, itemCode: packages.find( (pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM )?.itemCode, }, { code: RoomPackageCodeEnum.PET_ROOM, description: labels.petRoom, itemCode: packages.find( (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM )?.itemCode, }, ] let roomConfigurations: RatesState["roomConfigurations"] = [] if (roomsAvailability) { for (const availability of roomsAvailability) { if ("error" in availability) { // Availability request failed, default to empty array roomConfigurations.push([]) } else { roomConfigurations.push(availability.roomConfigurations) } } } 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.roomTypeCode, roomConfiguration ) if (!selectedRoom) { continue } const product = findProductInRoom(room.rateCode, selectedRoom) if (product) { rateSummary[idx] = { features: selectedRoom.features, product, rate: product.rate, roomType: selectedRoom.roomType, roomTypeCode: selectedRoom.roomTypeCode, package: room.packages?.[0], } } } } let activeRoom = rateSummary.length if (searchParams.has("modifyRateIndex")) { activeRoom = Number(searchParams.get("modifyRateIndex")) } else if (rateSummary.length === booking.rooms.length) { // Since all rooms has selections, all sections should be // closed on load activeRoom = -1 } return create()((set) => { return { activeRoom, booking, filterOptions, hotelType, isUserLoggedIn, packages, pathname, petRoomPackage: packages.find( (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM ), rateSummary, roomConfigurations, rooms: booking.rooms.map((room, idx) => { const roomConfiguration = roomConfigurations[idx] const selectedRate = findSelectedRate( room.rateCode, room.roomTypeCode, roomConfiguration ) ?? null let product = null if (selectedRate) { product = findProductInRoom(room.rateCode, selectedRate) } // Since features are fetched async based on query string, we need to read from query string to apply correct filtering const packagesParam = searchParams.get(`room[${idx}].packages`) const selectedPackage = isRoomPackageCode(packagesParam) ? packagesParam : undefined let rooms: RoomConfiguration[] = roomConfiguration if (selectedPackage) { rooms = roomConfiguration.filter((r) => r.features.find((f) => f.code === selectedPackage) ) } return { actions: { appendRegularRates(roomConfigurations) { return set( produce((state: RatesState) => { 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) { return { ...currentRoom, campaign: [ ...currentRoom.campaign, ...incomingRoom.campaign, ], products: [ ...currentRoom.products, ...incomingRoom.products, ], regular: incomingRoom.regular, } } return currentRoom }) state.rooms[idx].rooms = updatedRooms }) ) }, 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 }) ) }, selectFilter(filter) { return set( produce((state: RatesState) => { state.rooms[idx].selectedFilter = filter }) ) }, selectPackage(code) { return set( produce((state: RatesState) => { state.rooms[idx].selectedPackage = code const roomConfiguration = state.roomConfigurations[idx] if (roomConfiguration) { const searchParams = new URLSearchParams(state.searchParams) if (code) { state.rooms[idx].rooms = roomConfiguration.filter( (room) => room.features.find((feat) => feat.code === code) ) searchParams.set(`room[${idx}].packages`, code) if (state.rateSummary[idx]) { state.rateSummary[idx].package = code } } else { state.rooms[idx].rooms = roomConfiguration searchParams.delete(`room[${idx}].packages`) if (state.rateSummary[idx]) { state.rateSummary[idx].package = undefined } } state.searchParams = new ReadonlyURLSearchParams( searchParams ) window.history.pushState( {}, "", `${state.pathname}?${searchParams}` ) } }) ) }, selectRate(selectedRate) { return set( produce((state: RatesState) => { if (!selectedRate.product) { return } state.rooms[idx].selectedRate = selectedRate state.rateSummary[idx] = { features: selectedRate.features, package: state.rooms[idx].selectedPackage, 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 } let isRegularRate = false if ( "public" in selectedRate.product && selectedRate.product.public ) { isRegularRate = selectedRate.product.public.rateType === RateTypeEnum.Regular 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 && isRegularRate const searchParams = new URLSearchParams(state.searchParams) const counterratecode = isMemberRate ? productRateCode : memberRateCode if (counterratecode) { searchParams.set( `room[${idx}].counterratecode`, counterratecode ) } const rateCode = isMemberRate ? memberRateCode : productRateCode if (rateCode) { searchParams.set(`room[${idx}].ratecode`, rateCode) } searchParams.set( `room[${idx}].roomtype`, selectedRate.roomTypeCode ) if (state.rateSummary.length === state.booking.rooms.length) { state.activeRoom = -1 } else { state.activeRoom = idx + 1 } state.searchParams = new ReadonlyURLSearchParams(searchParams) window.history.pushState( {}, "", `${state.pathname}?${searchParams}` ) }) ) }, }, bookingRoom: room, rooms, selectedFilter: booking.bookingCode ? BookingCodeFilterEnum.Discounted : BookingCodeFilterEnum.All, selectedPackage, selectedRate: selectedRate && product ? { features: selectedRate.features, product, roomType: selectedRate.roomType, roomTypeCode: selectedRate.roomTypeCode, } : null, } }), roomCategories, roomsAvailability, searchParams, vat, } }) } export function useRatesStore(selector: (store: RatesState) => T) { const store = useContext(RatesContext) if (!store) { throw new Error("useRatesStore must be used within RatesProvider") } return useStore(store, selector) }