feat(SW-2873): Move select-hotel to booking flow * crude setup of select-hotel in partner-sas * wip * Fix linting * restructure tracking files * Remove dependency on trpc in tracking hooks * Move pageview tracking to common * Fix some lint and import issues * Add AlternativeHotelsPage * Add SelectHotelMapPage * Add AlternativeHotelsMapPage * remove next dependency in tracking store * Remove dependency on react in tracking hooks * move isSameBooking to booking-flow * Inject searchParamsComparator into tracking store * Move useTrackHardNavigation to common * Move useTrackSoftNavigation to common * Add TrackingSDK to partner-sas * call serverclient in layout * Remove unused css * Update types * Move HotelPin type * Fix todos * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Fix component Approved-by: Joakim Jäderberg
221 lines
6.6 KiB
TypeScript
221 lines
6.6 KiB
TypeScript
"use client"
|
|
import deepmerge from "deepmerge"
|
|
import { useEffect, useRef, useState } from "react"
|
|
|
|
import { isSameBooking } from "@scandic-hotels/booking-flow/utils/isSameBooking"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
|
|
import { createDetailsStore } from "@/stores/enter-details"
|
|
import {
|
|
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,
|
|
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<DetailsStore>(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,
|
|
roomCategories,
|
|
}
|
|
|
|
storeRef.current = createDetailsStore(
|
|
initialData,
|
|
searchParamsStr,
|
|
user,
|
|
breakfastPackages,
|
|
lang
|
|
)
|
|
}
|
|
|
|
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[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 (
|
|
<DetailsContext.Provider value={storeRef.current}>
|
|
{hasInitializedStore ? children : <LoadingSpinner fullPage />}
|
|
</DetailsContext.Provider>
|
|
)
|
|
}
|