import { produce } from "immer" import { useContext } from "react" import { create, useStore } from "zustand" import { REDEMPTION } from "@/constants/booking" import { RatesContext } from "@/contexts/Rates" import { clearRoomSelectionFromUrl, findProductInRoom, findSelectedRate, } from "./helpers" import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter" import type { Package, Packages } from "@/types/requests/packages" import type { InitialState, RatesState } from "@/types/stores/rates" import type { PriceProduct } from "@/types/trpc/routers/hotel/roomAvailability" export function createRatesStore({ booking, hotelType, labels, pathname, roomCategories, roomsAvailability, searchParams, vat, }: InitialState) { 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[] = [] 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) { const updatedSearchParams = clearRoomSelectionFromUrl(idx, searchParams) searchParams = updatedSearchParams window.history.replaceState( {}, "", `${pathname}?${updatedSearchParams}` ) 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 (searchParams.has("modifyRateIndex")) { activeRoom = Number(searchParams.get("modifyRateIndex")) } 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 === REDEMPTION return create()((set) => { return { activeRoom, booking, packageOptions, hotelType, isRedemptionBooking, pathname, rateSummary, roomConfigurations, 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()) 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 } const searchParams = state.searchParams if (filteredSelectedPackages.length) { searchParams.set( `room[${idx}].packages`, filteredSelectedPackages.map((pkg) => pkg.code).join(",") ) } else { searchParams.delete(`room[${idx}].packages`) } state.searchParams = searchParams window.history.replaceState( {}, "", `${state.pathname}?${searchParams}` ) }) ) }, 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 } const searchParams = state.searchParams searchParams.delete(`room[${idx}].packages`) state.searchParams = searchParams window.history.replaceState( {}, "", `${state.pathname}?${searchParams}` ) }) ) }, 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 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) } if (selectedRate.product.bookingCode) { searchParams.set( `room[${idx}].bookingCode`, selectedRate.product.bookingCode ) } else { if (searchParams.has(`room[${idx}].bookingCode`)) { searchParams.delete(`room[${idx}].bookingCode`) } } 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 = searchParams window.history.replaceState( {}, "", `${state.pathname}?${searchParams}` ) }) ) }, 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 } const searchParams = state.searchParams if (selectedPackages.length) { searchParams.set( `room[${idx}].packages`, selectedPackages.join(",") ) } else { searchParams.delete(`room[${idx}].packages`) } state.searchParams = searchParams window.history.replaceState( {}, "", `${state.pathname}?${searchParams}` ) }) ) }, 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 { const searchParams = clearRoomSelectionFromUrl( idx, state.searchParams ) state.searchParams = searchParams state.rateSummary[idx] = null state.rooms[idx].selectedRate = null window.history.replaceState( {}, "", `${pathname}?${searchParams}` ) } } } else { state.rooms[idx].rooms = [] if (state.rateSummary[idx]) { const searchParams = clearRoomSelectionFromUrl( idx, state.searchParams ) state.searchParams = searchParams state.rateSummary[idx] = null state.rooms[idx].selectedRate = null window.history.replaceState( {}, "", `${pathname}?${searchParams}` ) } } }) ) }, }, 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, } }), roomCategories, roomsPackages, 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) }