fix: Upgrade booking-flow eslint config * Upgrade booking-flow eslint config Approved-by: Bianca Widstam
252 lines
7.7 KiB
TypeScript
252 lines
7.7 KiB
TypeScript
"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<EnterDetailsStore | null>(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<EnterDetailsStore>(undefined)
|
|
const pointsCurrency = useGetPointsCurrency()
|
|
// eslint-disable-next-line react-hooks/refs
|
|
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 (
|
|
// eslint-disable-next-line react-hooks/refs
|
|
<EnterDetailsContext.Provider value={storeRef.current}>
|
|
{hasInitializedStore ? children : <LoadingSpinner fullPage />}
|
|
</EnterDetailsContext.Provider>
|
|
)
|
|
}
|