"use client" import deepmerge from "deepmerge" import { useEffect, useRef, useState } from "react" import { dt } from "@scandic-hotels/common/dt" import { createDetailsStore } from "@/stores/enter-details" import { checkIsSameBooking as checkIsSameBooking, clearSessionStorage, getTotalPrice, readFromSessionStorage, writeToSessionStorage, } from "@/stores/enter-details/helpers" import { getMultiroomDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema" import { guestDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema" import LoadingSpinner from "@/components/LoadingSpinner" import { DetailsContext } from "@/contexts/Details" import type { DetailsStore } from "@/types/contexts/enter-details" import { StepEnum } from "@/types/enums/step" import type { DetailsProviderProps } from "@/types/providers/enter-details" import type { InitialState, RoomState } from "@/types/stores/enter-details" export default function EnterDetailsProvider({ booking, breakfastPackages, children, lang, rooms, searchParamsStr, user, vat, }: DetailsProviderProps) { // This state is needed to be able to use defaultValues for // react-hook-form since values needs to be there on mount // and since we read from SessionStorage we need to delay // rendering the form until that has been done. const [hasInitializedStore, setHasInitializedStore] = useState(false) const storeRef = useRef(undefined) if (!storeRef.current) { const initialData: InitialState = { booking, rooms: rooms .filter((r) => r.bedTypes?.length) // TODO: how to handle room without bedtypes? .map((room) => ({ isAvailable: room.isAvailable, breakfastIncluded: room.breakfastIncluded, cancellationText: room.cancellationText, cancellationRule: room.cancellationRule, rateDetails: room.rateDetails, memberRateDetails: room.memberRateDetails, rateTitle: room.rateTitle, roomFeatures: room.packages, roomRate: room.roomRate, roomType: room.roomType, roomTypeCode: room.roomTypeCode, bedTypes: room.bedTypes, bedType: room.bedTypes?.length === 1 ? { roomTypeCode: room.bedTypes[0].value, description: room.bedTypes[0].description, type: room.bedTypes[0].type, } : undefined, mustBeGuaranteed: room.mustBeGuaranteed, memberMustBeGuaranteed: room.memberMustBeGuaranteed, isFlexRate: room.isFlexRate, })), vat, } storeRef.current = createDetailsStore( initialData, searchParamsStr, user, breakfastPackages, lang ) } useEffect(() => { const storedValues = readFromSessionStorage() if (!storedValues) { setHasInitializedStore(true) return } const isSameBooking = checkIsSameBooking(storedValues.booking, booking) if (!isSameBooking) { clearSessionStorage() setHasInitializedStore(true) return } const store = storeRef.current?.getState() if (!store) { setHasInitializedStore(true) return } const updatedRooms = storedValues.rooms.map((storedRoom, idx) => { const room = store.rooms[idx] if (!room) { return null } // Need to create a deep new copy // since store is readonly const currentRoom = deepmerge({}, room) if (!currentRoom.room.isAvailable) { return currentRoom } if (!currentRoom.room.bedType && storedRoom.room.bedType) { const sameBed = currentRoom.room.bedTypes.find( (bedType) => bedType.value === storedRoom.room.bedType?.roomTypeCode ) if (sameBed) { currentRoom.room.bedType = { description: sameBed.description, roomTypeCode: sameBed.value, type: sameBed.type, } currentRoom.steps[StepEnum.selectBed].isValid = true } } if ( currentRoom.steps[StepEnum.breakfast] && currentRoom.room.breakfast === undefined && (storedRoom.room.breakfast || storedRoom.room.breakfast === false) ) { currentRoom.room.breakfast = storedRoom.room.breakfast currentRoom.steps[StepEnum.breakfast].isValid = true } // User is already added for main room if (!user || (user && idx > 0)) { currentRoom.room.guest = deepmerge( currentRoom.room.guest, storedRoom.room.guest ) } if ( !currentRoom.room.specialRequest.comment && storedRoom.room.specialRequest.comment ) { currentRoom.room.specialRequest.comment = storedRoom.room.specialRequest.comment } const validGuest = idx > 0 ? getMultiroomDetailsSchema().safeParse(currentRoom.room.guest) : guestDetailsSchema.safeParse(currentRoom.room.guest) if (validGuest.success) { currentRoom.steps[StepEnum.details].isValid = true } const invalidStep = Object.values(currentRoom.steps).find( (step) => !step.isValid ) currentRoom.isComplete = !invalidStep return currentRoom }) const canProceedToPayment = updatedRooms.every( (room) => room?.isComplete && room?.room.isAvailable ) const filteredOutMissingRooms = updatedRooms.filter( (room): room is RoomState => !!room ) const nights = dt(booking.toDate).diff(booking.fromDate, "days") const totalPrice = getTotalPrice( filteredOutMissingRooms.map((r) => r.room), !!user, nights ) // Need to create a deep new copy since store is readonly const availableBeds = deepmerge({}, store.availableBeds) for (const filteredOutMissingRoom of filteredOutMissingRooms) { if (filteredOutMissingRoom.room.bedType) { const roomTypeCode = filteredOutMissingRoom.room.bedType.roomTypeCode availableBeds[roomTypeCode] = Math.max( availableBeds[roomTypeCode] - 1, 0 ) } } writeToSessionStorage({ booking, rooms: filteredOutMissingRooms, }) storeRef.current?.setState({ availableBeds, canProceedToPayment, rooms: filteredOutMissingRooms, totalPrice, }) setHasInitializedStore(true) }, [booking, rooms, user]) return ( {hasInitializedStore ? children : } ) }