"use client" import deepmerge from "deepmerge" import { createContext, useEffect, useRef, useState } from "react" import { dt } from "@scandic-hotels/common/dt" import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner" import { useGetPointsCurrency } from "../../bookingFlowConfig/bookingFlowConfigContext" import { getMultiroomDetailsSchema } from "../../components/EnterDetails/Details/Multiroom/schema" import { guestDetailsSchema } from "../../components/EnterDetails/Details/RoomOne/schema" import { createDetailsStore, type EnterDetailsStore, } from "../../stores/enter-details" import { EnterDetailsStepEnum } from "../../stores/enter-details/enterDetailsStep" import { clearSessionStorage, readFromSessionStorage, writeToSessionStorage, } from "../../stores/enter-details/helpers" import { getTotalPrice } from "../../stores/enter-details/priceCalculations" import { isSameBooking } from "../../utils/isSameBooking" import type { Lang } from "@scandic-hotels/common/constants/language" import type { BreakfastPackages } from "@scandic-hotels/trpc/routers/hotels/output" import type { RoomCategories } from "@scandic-hotels/trpc/types/hotel" import type { Room } from "@scandic-hotels/trpc/types/room" import type { User } from "@scandic-hotels/trpc/types/user" import type { InitialState, RoomState } from "../../stores/enter-details/types" import type { DetailsBooking } from "../../utils/url" export const EnterDetailsContext = createContext(null) type DetailsProviderProps = React.PropsWithChildren & { booking: DetailsBooking breakfastPackages: BreakfastPackages hotelOffersBreakfast: boolean lang: Lang rooms: Room[] searchParamsStr: string user: User | null vat: number hotelName: string roomCategories: RoomCategories } export default function EnterDetailsProvider({ booking, breakfastPackages, children, hotelOffersBreakfast, lang, rooms, searchParamsStr, user, vat, hotelName, roomCategories, }: 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) const pointsCurrency = useGetPointsCurrency() if (!storeRef.current) { const initialData: InitialState = { booking, hotelOffersBreakfast, 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, hotelName, roomCategories, } storeRef.current = createDetailsStore( initialData, searchParamsStr, user, breakfastPackages, lang, pointsCurrency ) } useEffect(() => { const storedValues = readFromSessionStorage() if (!storedValues) { setHasInitializedStore(true) return } if (!isSameBooking(storedValues.booking, booking)) { 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[EnterDetailsStepEnum.selectBed].isValid = true } } if ( currentRoom.steps[EnterDetailsStepEnum.breakfast] && currentRoom.room.breakfast === undefined && (storedRoom.room.breakfast || storedRoom.room.breakfast === false) ) { currentRoom.room.breakfast = storedRoom.room.breakfast currentRoom.steps[EnterDetailsStepEnum.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[EnterDetailsStepEnum.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 : } ) }