fix: persist selection of bed and breakfast if same room

This commit is contained in:
Christel Westerberg
2024-12-04 16:16:32 +01:00
parent f075521421
commit 4210218852
11 changed files with 410 additions and 439 deletions

View File

@@ -22,10 +22,7 @@ export default function BedType({ bedTypes }: BedTypeProps) {
const initialBedType = useEnterDetailsStore( const initialBedType = useEnterDetailsStore(
(state) => state.formValues?.bedType?.roomTypeCode (state) => state.formValues?.bedType?.roomTypeCode
) )
const bedType = useEnterDetailsStore((state) => state.bedType?.roomTypeCode)
const completeStep = useEnterDetailsStore(
(state) => state.actions.completeStep
)
const updateBedType = useEnterDetailsStore( const updateBedType = useEnterDetailsStore(
(state) => state.actions.updateBedType (state) => state.actions.updateBedType
) )
@@ -81,9 +78,6 @@ export default function BedType({ bedTypes }: BedTypeProps) {
subtitle={width} subtitle={width}
title={roomType.description} title={roomType.description}
value={roomType.value} value={roomType.value}
handleSelectedOnClick={
bedType === roomType.value ? completeStep : undefined
}
/> />
) )
})} })}

View File

@@ -31,16 +31,7 @@ export default function Breakfast({ packages }: BreakfastProps) {
? "false" ? "false"
: undefined : undefined
) )
const breakfast = useEnterDetailsStore((state) =>
state.breakfast
? state.breakfast.code
: state.breakfast === false
? "false"
: undefined
)
const completeStep = useEnterDetailsStore(
(state) => state.actions.completeStep
)
const updateBreakfast = useEnterDetailsStore( const updateBreakfast = useEnterDetailsStore(
(state) => state.actions.updateBreakfast (state) => state.actions.updateBreakfast
) )
@@ -119,9 +110,6 @@ export default function Breakfast({ packages }: BreakfastProps) {
})} })}
title={intl.formatMessage({ id: "Breakfast buffet" })} title={intl.formatMessage({ id: "Breakfast buffet" })}
value={pkg.code} value={pkg.code}
handleSelectedOnClick={
breakfast === pkg.code ? completeStep : undefined
}
/> />
))} ))}
<RadioCard <RadioCard
@@ -138,9 +126,6 @@ export default function Breakfast({ packages }: BreakfastProps) {
})} })}
title={intl.formatMessage({ id: "No breakfast" })} title={intl.formatMessage({ id: "No breakfast" })}
value="false" value="false"
handleSelectedOnClick={
breakfast === "false" ? completeStep : undefined
}
/> />
</form> </form>
</div> </div>

View File

@@ -26,8 +26,7 @@ import type {
const formID = "enter-details" const formID = "enter-details"
export default function Details({ user, memberPrice }: DetailsProps) { export default function Details({ user, memberPrice }: DetailsProps) {
const intl = useIntl() const intl = useIntl()
const initialData = useEnterDetailsStore((state) => state.formValues.guest) const initialData = useEnterDetailsStore((state) => state.guest)
const join = useEnterDetailsStore((state) => state.guest.join)
const updateDetails = useEnterDetailsStore( const updateDetails = useEnterDetailsStore(
(state) => state.actions.updateDetails (state) => state.actions.updateDetails
) )
@@ -42,7 +41,7 @@ export default function Details({ user, memberPrice }: DetailsProps) {
dateOfBirth: initialData.dateOfBirth, dateOfBirth: initialData.dateOfBirth,
email: user?.email ?? initialData.email, email: user?.email ?? initialData.email,
firstName: user?.firstName ?? initialData.firstName, firstName: user?.firstName ?? initialData.firstName,
join, join: initialData.join,
lastName: user?.lastName ?? initialData.lastName, lastName: user?.lastName ?? initialData.lastName,
membershipNo: initialData.membershipNo, membershipNo: initialData.membershipNo,
phoneNumber: user?.phoneNumber ?? initialData.phoneNumber, phoneNumber: user?.phoneNumber ?? initialData.phoneNumber,

View File

@@ -8,7 +8,7 @@ import { detailsStorageName } from "@/stores/enter-details"
import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import LoadingSpinner from "@/components/LoadingSpinner" import LoadingSpinner from "@/components/LoadingSpinner"
import type { DetailsState } from "@/types/stores/enter-details" import type { PersistedState } from "@/types/stores/enter-details"
export default function PaymentCallback({ export default function PaymentCallback({
returnUrl, returnUrl,
@@ -23,12 +23,9 @@ export default function PaymentCallback({
const bookingData = window.sessionStorage.getItem(detailsStorageName) const bookingData = window.sessionStorage.getItem(detailsStorageName)
if (bookingData) { if (bookingData) {
const detailsStorage: Record< const detailsStorage: PersistedState = JSON.parse(bookingData)
"state",
Pick<DetailsState, "booking">
> = JSON.parse(bookingData)
const searchParams = createQueryParamsForEnterDetails( const searchParams = createQueryParamsForEnterDetails(
detailsStorage.state.booking, detailsStorage.booking,
searchObject searchObject
) )

View File

@@ -12,7 +12,6 @@ interface BaseCardProps
title: React.ReactNode title: React.ReactNode
type: "checkbox" | "radio" type: "checkbox" | "radio"
value?: string value?: string
handleSelectedOnClick?: () => void
} }
interface ListCardProps extends BaseCardProps { interface ListCardProps extends BaseCardProps {

View File

@@ -24,21 +24,20 @@ export default function Card({
title, title,
type, type,
value, value,
handleSelectedOnClick,
}: CardProps) { }: CardProps) {
const { register } = useFormContext() const { register, setValue } = useFormContext()
function onLabelClick(event: React.MouseEvent) { function onLabelClick(event: React.MouseEvent) {
// Preventing click event on label elements firing twice: https://github.com/facebook/react/issues/14295 // Preventing click event on label elements firing twice: https://github.com/facebook/react/issues/14295
event.preventDefault() event.preventDefault()
handleSelectedOnClick?.() setValue(name, value)
} }
return ( return (
<label <label
className={styles.label} className={styles.label}
data-declined={declined} data-declined={declined}
onClick={onLabelClick}
tabIndex={0} tabIndex={0}
onClick={handleSelectedOnClick ? onLabelClick : undefined}
> >
<Caption className={styles.title} color="burgundy" type="label" uppercase> <Caption className={styles.title} color="burgundy" type="label" uppercase>
{title} {title}

View File

@@ -1,13 +1,26 @@
"use client" "use client"
import { useRef } from "react" import { useEffect, useRef } from "react"
import { createDetailsStore } from "@/stores/enter-details" import { createDetailsStore } from "@/stores/enter-details"
import {
calcTotalMemberPrice,
calcTotalPublicPrice,
navigate,
writeToSessionStorage,
} from "@/stores/enter-details/helpers"
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema"
import {
guestDetailsSchema,
signedInDetailsSchema,
} from "@/components/HotelReservation/EnterDetails/Details/schema"
import { DetailsContext } from "@/contexts/Details" import { DetailsContext } from "@/contexts/Details"
import type { DetailsStore } from "@/types/contexts/enter-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 { DetailsProviderProps } from "@/types/providers/enter-details"
import type { InitialState } from "@/types/stores/enter-details" import type { DetailsState, InitialState } from "@/types/stores/enter-details"
export default function EnterDetailsProvider({ export default function EnterDetailsProvider({
bedTypes, bedTypes,
@@ -42,6 +55,84 @@ export default function EnterDetailsProvider({
) )
} }
useEffect(() => {
if (storeRef.current) {
storeRef.current.setState((state) => {
const newState: DetailsState = { ...state }
newState.bedType = state.formValues.bedType
newState.breakfast = state.formValues.breakfast
if (state.formValues.guest && !user) {
newState.guest = state.formValues.guest
}
if (
(newState.guest!.join || newState.guest!.membershipNo || user) &&
state.roomRate.memberRate
) {
const memberPrice = calcTotalMemberPrice(newState)
newState.roomPrice = memberPrice.roomPrice
newState.totalPrice = memberPrice.totalPrice
} else {
const publicPrice = calcTotalPublicPrice(newState)
newState.roomPrice = publicPrice.roomPrice
newState.totalPrice = publicPrice.totalPrice
}
const isValid = { ...newState.isValid }
const validateBooking = state.formValues
const validPaths = [StepEnum.selectBed]
const validatedBedType = bedTypeSchema.safeParse(validateBooking)
if (validatedBedType.success) {
isValid[StepEnum.selectBed] = true
validPaths.push(state.steps[1])
}
const validatedBreakfast =
breakfastStoreSchema.safeParse(validateBooking)
if (validatedBreakfast.success) {
isValid[StepEnum.breakfast] = true
validPaths.push(StepEnum.details)
}
const detailsSchema = user ? signedInDetailsSchema : guestDetailsSchema
const validatedDetails = detailsSchema.safeParse(validateBooking.guest)
/**
* Need to add the breakfast check here too since
* when a member comes into the flow, their data is
* already added and valid, and thus to avoid showing a
* step the user hasn't been on yet as complete
*/
if (isValid.breakfast && validatedDetails.success) {
isValid[StepEnum.details] = true
validPaths.push(StepEnum.payment)
}
if (!validPaths.includes(newState.currentStep!)) {
newState.currentStep = validPaths.at(-1)!
}
if (step !== newState.currentStep) {
const stateCurrentStep = newState.currentStep!
setTimeout(() => {
navigate(stateCurrentStep, searchParamsStr)
})
}
writeToSessionStorage({
bedType: newState.bedType,
booking: newState.booking,
breakfast: newState.breakfast,
guest: newState.guest,
})
return { ...newState, isValid }
})
}
}, [searchParamsStr, step, user])
return ( return (
<DetailsContext.Provider value={storeRef.current}> <DetailsContext.Provider value={storeRef.current}>
{children} {children}

View File

@@ -547,7 +547,7 @@ export const hotelQueryRouter = router({
const hotelData = await getHotelData( const hotelData = await getHotelData(
{ {
hotelId, hotelId,
language: ctx.lang, language: toApiLang(ctx.lang),
}, },
ctx.serviceToken ctx.serviceToken
) )
@@ -607,7 +607,11 @@ export const hotelQueryRouter = router({
const bedTypes = availableRoomsInCategory const bedTypes = availableRoomsInCategory
.map((availRoom) => { .map((availRoom) => {
const matchingRoom = hotelData?.included const matchingRoom = hotelData?.included
?.find((room) => room.name === availRoom.roomType) ?.find((room) =>
room.roomTypes
.map((roomType) => roomType.code)
.includes(availRoom.roomTypeCode)
)
?.roomTypes.find( ?.roomTypes.find(
(roomType) => roomType.code === availRoom.roomTypeCode (roomType) => roomType.code === availRoom.roomTypeCode
) )

View File

@@ -1,13 +1,22 @@
import deepmerge from "deepmerge"
import isEqual from "fast-deep-equal" import isEqual from "fast-deep-equal"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { arrayMerge } from "@/utils/merge"
import { detailsStorageName } from "."
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
import { CurrencyEnum } from "@/types/enums/currency" import { CurrencyEnum } from "@/types/enums/currency"
import { StepEnum } from "@/types/enums/step" import { StepEnum } from "@/types/enums/step"
import type { DetailsState, RoomRate } from "@/types/stores/enter-details" import type {
DetailsState,
PersistedState,
PersistedStatePart,
RoomRate,
} from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
export function langToCurrency() { export function langToCurrency() {
@@ -44,8 +53,28 @@ export function navigate(step: StepEnum, searchParams: string) {
window.history.pushState({ step }, "", `${step}?${searchParams}`) window.history.pushState({ step }, "", `${step}?${searchParams}`)
} }
export function checkIsSameBooking(prev: BookingData, next: BookingData) { export function checkIsSameRoom(prev: BookingData, next: BookingData) {
return isEqual(prev, next) const { rooms: prevRooms, ...prevBooking } = prev
const prevRoomsWithoutRateCodes = prevRooms.map(
({ rateCode, counterRateCode, ...room }) => room
)
const { rooms: nextRooms, ...nextBooking } = next
const nextRoomsWithoutRateCodes = nextRooms.map(
({ rateCode, counterRateCode, ...room }) => room
)
return isEqual(
{
...prevBooking,
rooms: prevRoomsWithoutRateCodes,
},
{
...nextBooking,
rooms: nextRoomsWithoutRateCodes,
}
)
} }
export function add(...nums: (number | string | undefined)[]) { export function add(...nums: (number | string | undefined)[]) {
@@ -160,9 +189,11 @@ export function calcTotalPrice(
> & > &
DetailsState["roomRate"]["publicRate"] DetailsState["roomRate"]["publicRate"]
) { ) {
// state is sometimes read-only, thus we
// need to create a copy of the values
const roomAndTotalPrice = { const roomAndTotalPrice = {
roomPrice: state.roomPrice, roomPrice: { ...state.roomPrice },
totalPrice: state.totalPrice, totalPrice: { ...state.totalPrice },
} }
if (state.requestedPrice?.pricePerStay) { if (state.requestedPrice?.pricePerStay) {
roomAndTotalPrice.roomPrice.requested = { roomAndTotalPrice.roomPrice.requested = {
@@ -222,3 +253,16 @@ export function calcTotalPrice(
return roomAndTotalPrice return roomAndTotalPrice
} }
export function writeToSessionStorage(part: PersistedStatePart) {
const unparsedData = sessionStorage.getItem(detailsStorageName)
if (unparsedData) {
const data: PersistedState = JSON.parse(unparsedData)
// @ts-expect-error - deepmerge is not to happy with
// the part type
const updated = deepmerge(data, part, { arrayMerge })
sessionStorage.setItem(detailsStorageName, JSON.stringify(updated))
} else {
sessionStorage.setItem(detailsStorageName, JSON.stringify(part))
}
}

View File

@@ -2,35 +2,28 @@ import deepmerge from "deepmerge"
import { produce } from "immer" import { produce } from "immer"
import { useContext } from "react" import { useContext } from "react"
import { create, useStore } from "zustand" import { create, useStore } from "zustand"
import { createJSONStorage, persist } from "zustand/middleware"
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema"
import {
guestDetailsSchema,
signedInDetailsSchema,
} from "@/components/HotelReservation/EnterDetails/Details/schema"
import { DetailsContext } from "@/contexts/Details" import { DetailsContext } from "@/contexts/Details"
import { arrayMerge } from "@/utils/merge"
import { import {
add, add,
calcTotalMemberPrice, calcTotalMemberPrice,
calcTotalPublicPrice, calcTotalPublicPrice,
checkIsSameBooking, checkIsSameRoom,
extractGuestFromUser, extractGuestFromUser,
getInitialRoomPrice, getInitialRoomPrice,
getInitialTotalPrice, getInitialTotalPrice,
langToCurrency, langToCurrency,
navigate, navigate,
writeToSessionStorage,
} from "./helpers" } from "./helpers"
import { CurrencyEnum } from "@/types/enums/currency"
import { StepEnum } from "@/types/enums/step" import { StepEnum } from "@/types/enums/step"
import type { import type {
DetailsState, DetailsState,
FormValues, FormValues,
InitialState, InitialState,
PersistedState,
} from "@/types/stores/enter-details" } from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
@@ -54,57 +47,61 @@ export function createDetailsStore(
user: SafeUser user: SafeUser
) { ) {
const isMember = !!user const isMember = !!user
const isBrowser = typeof window !== "undefined"
// Spread is done on purpose since we want
// a copy of initialState and not alter the
// original
const formValues: FormValues = { const formValues: FormValues = {
bedType: initialState.bedType, bedType: initialState.bedType,
booking: initialState.booking, booking: initialState.booking,
breakfast: undefined, /** TODO: Needs adjustment when breakfast included in rate is added */
breakfast:
initialState.breakfast === false ? initialState.breakfast : undefined,
guest: isMember guest: isMember
? deepmerge(defaultGuestState, extractGuestFromUser(user)) ? deepmerge(defaultGuestState, extractGuestFromUser(user))
: defaultGuestState, : defaultGuestState,
} }
if (isBrowser) { if (typeof window !== "undefined") {
const unparsedStorage = sessionStorage.getItem(detailsStorageName)
if (unparsedStorage) {
const detailsStorage: PersistedState = JSON.parse(unparsedStorage)
const isSameRoom = detailsStorage.booking
? checkIsSameRoom(initialState.booking, detailsStorage.booking)
: false
if (isSameRoom) {
formValues.bedType = detailsStorage.bedType
formValues.breakfast = detailsStorage.breakfast
}
if (!isMember) {
formValues.guest = detailsStorage.guest
}
}
}
let steps = [
StepEnum.selectBed,
StepEnum.breakfast,
StepEnum.details,
StepEnum.payment,
]
/** /**
* We need to initialize the store from sessionStorage ourselves * TODO:
* since `persist` does it first after render and therefore * - when included in rate, can packages still be received?
* we cannot use the data as `defaultValues` for our forms. * - no hotels yet with breakfast included in the rate so
* RHF caches defaultValues on mount. * impossible to build for atm.
*
* checking against initialState since that means the
* hotel doesn't offer breakfast
*
* matching breakfast first so the steps array is altered
* before the bedTypes possible step altering
*/ */
const detailsStorageUnparsed = sessionStorage.getItem(detailsStorageName) if (initialState.breakfast === false) {
if (detailsStorageUnparsed) { steps = steps.filter((step) => step !== StepEnum.breakfast)
const detailsStorage: Record<"state", FormValues> = JSON.parse( if (currentStep === StepEnum.breakfast) {
detailsStorageUnparsed currentStep = steps[1]
) }
const isSameBooking = checkIsSameBooking(
detailsStorage.state.booking,
initialState.booking
)
if (isSameBooking) {
if (!initialState.bedType && detailsStorage.state.bedType) {
formValues.bedType = detailsStorage.state.bedType
} }
if ("breakfast" in detailsStorage.state) { if (initialState.bedType && currentStep === StepEnum.selectBed) {
formValues.breakfast = detailsStorage.state.breakfast currentStep = steps[1]
}
if ("guest" in detailsStorage.state) {
if (!user) {
formValues.guest = deepmerge(
defaultGuestState,
detailsStorage.state.guest,
{ arrayMerge }
)
}
}
}
}
} }
const initialRoomPrice = getInitialRoomPrice(initialState.roomRate, isMember) const initialRoomPrice = getInitialRoomPrice(initialState.roomRate, isMember)
@@ -128,9 +125,7 @@ export function createDetailsStore(
}) })
} }
return create<DetailsState>()( return create<DetailsState>()((set) => ({
persist(
(set) => ({
actions: { actions: {
completeStep() { completeStep() {
return set( return set(
@@ -185,6 +180,8 @@ export function createDetailsStore(
state.isValid["select-bed"] = true state.isValid["select-bed"] = true
state.bedType = bedType state.bedType = bedType
writeToSessionStorage({ bedType })
const currentStepIndex = state.steps.indexOf(state.currentStep) const currentStepIndex = state.steps.indexOf(state.currentStep)
const nextStep = state.steps[currentStepIndex + 1] const nextStep = state.steps[currentStepIndex + 1]
state.currentStep = nextStep state.currentStep = nextStep
@@ -201,8 +198,7 @@ export function createDetailsStore(
const stateTotalLocalPrice = state.totalPrice.local.price const stateTotalLocalPrice = state.totalPrice.local.price
const addToTotalPrice = const addToTotalPrice =
(state.breakfast === undefined || (state.breakfast === undefined || state.breakfast === false) &&
state.breakfast === false) &&
!!breakfast !!breakfast
const subtractFromTotalPrice = const subtractFromTotalPrice =
(state.breakfast === undefined || state.breakfast) && (state.breakfast === undefined || state.breakfast) &&
@@ -230,8 +226,7 @@ export function createDetailsStore(
} }
if (subtractFromTotalPrice) { if (subtractFromTotalPrice) {
let currency = let currency = state.totalPrice.local.currency ?? langToCurrency()
state.totalPrice.local.currency ?? langToCurrency()
let currentBreakfastTotalPrice = 0 let currentBreakfastTotalPrice = 0
let currentBreakfastTotalRequestedPrice = 0 let currentBreakfastTotalRequestedPrice = 0
if (state.breakfast) { if (state.breakfast) {
@@ -245,13 +240,11 @@ export function createDetailsStore(
} }
let requestedPrice = let requestedPrice =
stateTotalRequestedPrice - stateTotalRequestedPrice - currentBreakfastTotalRequestedPrice
currentBreakfastTotalRequestedPrice
if (requestedPrice < 0) { if (requestedPrice < 0) {
requestedPrice = 0 requestedPrice = 0
} }
let localPrice = let localPrice = stateTotalLocalPrice - currentBreakfastTotalPrice
stateTotalLocalPrice - currentBreakfastTotalPrice
if (localPrice < 0) { if (localPrice < 0) {
localPrice = 0 localPrice = 0
} }
@@ -269,7 +262,7 @@ export function createDetailsStore(
} }
state.breakfast = breakfast state.breakfast = breakfast
writeToSessionStorage({ breakfast })
const currentStepIndex = state.steps.indexOf(state.currentStep) const currentStepIndex = state.steps.indexOf(state.currentStep)
const nextStep = state.steps[currentStepIndex + 1] const nextStep = state.steps[currentStepIndex + 1]
state.currentStep = nextStep state.currentStep = nextStep
@@ -306,6 +299,8 @@ export function createDetailsStore(
state.totalPrice = publicPrice.totalPrice state.totalPrice = publicPrice.totalPrice
} }
writeToSessionStorage({ guest: data })
const currentStepIndex = state.steps.indexOf(state.currentStep) const currentStepIndex = state.steps.indexOf(state.currentStep)
const nextStep = state.steps[currentStepIndex + 1] const nextStep = state.steps[currentStepIndex + 1]
state.currentStep = nextStep state.currentStep = nextStep
@@ -314,10 +309,10 @@ export function createDetailsStore(
) )
}, },
}, },
bedType: initialState.bedType ?? undefined, bedType: initialState.bedType ?? undefined,
booking: initialState.booking, booking: initialState.booking,
breakfast: undefined, breakfast:
initialState.breakfast === false ? initialState.breakfast : undefined,
currentStep, currentStep,
formValues, formValues,
guest: isMember guest: isMember
@@ -334,156 +329,9 @@ export function createDetailsStore(
packages: initialState.packages, packages: initialState.packages,
roomPrice: initialRoomPrice, roomPrice: initialRoomPrice,
roomRate: initialState.roomRate, roomRate: initialState.roomRate,
steps: [ steps,
StepEnum.selectBed,
StepEnum.breakfast,
StepEnum.details,
StepEnum.payment,
],
totalPrice: initialTotalPrice, totalPrice: initialTotalPrice,
}), }))
{
name: detailsStorageName,
merge(persistedState, currentState) {
if (
persistedState &&
Object.prototype.hasOwnProperty.call(persistedState, "booking")
) {
const isSameBooking = checkIsSameBooking(
// @ts-expect-error - persistedState cannot be typed
persistedState.booking,
currentState.booking
)
if (!isSameBooking) {
// We get the booking data from query params, and the "newest" booking data should always be used.
// Merging the two states can lead to issues since some params or values in arrays might be removed.
// @ts-expect-error - persistedState cannot be typed
delete persistedState.booking
return deepmerge(persistedState, currentState, {
arrayMerge,
})
}
}
return deepmerge(currentState, persistedState ?? {}, { arrayMerge })
},
onRehydrateStorage(initState) {
return function (state) {
if (state) {
if (
(state.guest.join || state.guest.membershipNo || isMember) &&
state.roomRate.memberRate
) {
const memberPrice = calcTotalMemberPrice(state)
state.roomPrice = memberPrice.roomPrice
state.totalPrice = memberPrice.totalPrice
} else {
const publicPrice = calcTotalPublicPrice(state)
state.roomPrice = publicPrice.roomPrice
state.totalPrice = publicPrice.totalPrice
}
/**
* TODO:
* - when included in rate, can packages still be received?
* - no hotels yet with breakfast included in the rate so
* impossible to build for atm.
*
* checking against initialState since that means the
* hotel doesn't offer breakfast
*
* matching breakfast first so the steps array is altered
* before the bedTypes possible step altering
*/
if (initialState.breakfast === false) {
state.steps = state.steps.filter(
(step) => step === StepEnum.breakfast
)
if (state.currentStep === StepEnum.breakfast) {
state.currentStep = state.steps[1]
}
}
if (initialState.bedType) {
if (state.currentStep === StepEnum.selectBed) {
state.currentStep = state.steps[1]
}
}
const isSameBooking = checkIsSameBooking(
initState.booking,
state.booking
)
const validateBooking = isSameBooking ? state : initState
const validPaths = [StepEnum.selectBed]
const validatedBedType = bedTypeSchema.safeParse(validateBooking)
if (validatedBedType.success) {
state.isValid["select-bed"] = true
validPaths.push(state.steps[1])
}
const validatedBreakfast =
breakfastStoreSchema.safeParse(validateBooking)
if (validatedBreakfast.success) {
state.isValid.breakfast = true
validPaths.push(StepEnum.details)
}
const detailsSchema = isMember
? signedInDetailsSchema
: guestDetailsSchema
const validatedDetails = detailsSchema.safeParse(
validateBooking.guest
)
// Need to add the breakfast check here too since
// when a member comes into the flow, their data is
// already added and valid, and thus to avoid showing a
// step the user hasn't been on yet as complete
if (state.isValid.breakfast && validatedDetails.success) {
state.isValid.details = true
validPaths.push(StepEnum.payment)
}
if (!validPaths.includes(state.currentStep)) {
state.currentStep = validPaths.at(-1)!
}
if (currentStep !== state.currentStep) {
const stateCurrentStep = state.currentStep
setTimeout(() => {
navigate(stateCurrentStep, searchParams)
})
}
if (isSameBooking) {
state = deepmerge<DetailsState>(initState, state, {
arrayMerge,
})
} else {
state = deepmerge<DetailsState>(state, initState, {
arrayMerge,
})
}
}
}
},
partialize(state) {
return {
bedType: state.bedType,
booking: state.booking,
breakfast: state.breakfast,
guest: state.guest,
totalPrice: state.totalPrice,
}
},
storage: createJSONStorage(() => sessionStorage),
}
)
)
} }
export function useEnterDetailsStore<T>(selector: (store: DetailsState) => T) { export function useEnterDetailsStore<T>(selector: (store: DetailsState) => T) {

View File

@@ -61,3 +61,14 @@ export type InitialState = Pick<DetailsState, "booking" | "packages"> &
} }
export type RoomRate = DetailsProviderProps["roomRate"] export type RoomRate = DetailsProviderProps["roomRate"]
export type PersistedState = Pick<
DetailsState,
"bedType" | "booking" | "breakfast" | "guest"
>
export type PersistedStatePart =
| Pick<DetailsState, "bedType">
| Pick<DetailsState, "booking">
| Pick<DetailsState, "breakfast">
| Pick<DetailsState, "guest">