Files
web/stores/details.ts

190 lines
5.9 KiB
TypeScript

import merge from "deepmerge"
import { produce } from "immer"
import { useContext } from "react"
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 { arrayMerge } from "@/utils/merge"
import { StepEnum } from "@/types/enums/step"
import type { DetailsState, InitialState } from "@/types/stores/details"
export const detailsStorageName = "details-storage"
export function createDetailsStore(
initialState: InitialState,
isMember: boolean
) {
if (typeof window !== "undefined") {
/**
* We need to initialize the store from sessionStorage ourselves
* since `persist` does it first after render and therefore
* we cannot use the data as `defaultValues` for our forms.
* RHF caches defaultValues on mount.
*/
const detailsStorageUnparsed = sessionStorage.getItem(detailsStorageName)
if (detailsStorageUnparsed) {
const detailsStorage: Record<
"state",
Pick<DetailsState, "data">
> = JSON.parse(detailsStorageUnparsed)
initialState = merge(detailsStorage.state.data, initialState, {
arrayMerge,
})
}
}
return create<DetailsState>()(
persist(
(set) => ({
actions: {
setIsSubmittingDisabled(isSubmittingDisabled) {
return set(
produce((state: DetailsState) => {
state.isSubmittingDisabled = isSubmittingDisabled
})
)
},
setTotalPrice(totalPrice) {
return set(
produce((state: DetailsState) => {
state.totalPrice = totalPrice
})
)
},
toggleSummaryOpen() {
return set(
produce((state: DetailsState) => {
state.isSummaryOpen = !state.isSummaryOpen
})
)
},
updateBedType(bedType) {
return set(
produce((state: DetailsState) => {
state.isValid["select-bed"] = true
state.data.bedType = bedType
})
)
},
updateBreakfast(breakfast) {
return set(
produce((state: DetailsState) => {
state.isValid.breakfast = true
state.data.breakfast = breakfast
})
)
},
updateDetails(data) {
return set(
produce((state: DetailsState) => {
state.isValid.details = true
state.data.countryCode = data.countryCode
state.data.dateOfBirth = data.dateOfBirth
state.data.email = data.email
state.data.firstName = data.firstName
state.data.join = data.join
state.data.lastName = data.lastName
if (data.join) {
state.data.membershipNo = undefined
} else {
state.data.membershipNo = data.membershipNo
}
state.data.phoneNumber = data.phoneNumber
state.data.zipCode = data.zipCode
})
)
},
},
data: merge(
{
bedType: undefined,
breakfast: undefined,
countryCode: "",
dateOfBirth: "",
email: "",
firstName: "",
join: false,
lastName: "",
membershipNo: "",
phoneNumber: "",
termsAccepted: false,
zipCode: "",
},
initialState
),
isSubmittingDisabled: false,
isSummaryOpen: false,
isValid: {
[StepEnum.selectBed]: false,
[StepEnum.breakfast]: false,
[StepEnum.details]: false,
[StepEnum.payment]: false,
},
totalPrice: {
euro: { currency: "", amount: 0 },
local: { currency: "", amount: 0 },
},
}),
{
name: detailsStorageName,
onRehydrateStorage(prevState) {
return function (state) {
if (state) {
const validatedBedType = bedTypeSchema.safeParse(state.data)
if (validatedBedType.success !== state.isValid["select-bed"]) {
state.isValid["select-bed"] = validatedBedType.success
}
const validatedBreakfast = breakfastStoreSchema.safeParse(
state.data
)
if (validatedBreakfast.success !== state.isValid.breakfast) {
state.isValid.breakfast = validatedBreakfast.success
}
const detailsSchema = isMember
? signedInDetailsSchema
: guestDetailsSchema
const validatedDetails = detailsSchema.safeParse(state.data)
if (validatedDetails.success !== state.isValid.details) {
state.isValid.details = validatedDetails.success
}
const mergedState = merge(state.data, prevState.data, {
arrayMerge,
})
state.data = mergedState
}
}
},
partialize(state) {
return {
data: state.data,
}
},
storage: createJSONStorage(() => sessionStorage),
}
)
)
}
export function useDetailsStore<T>(selector: (store: DetailsState) => T) {
const store = useContext(DetailsContext)
if (!store) {
throw new Error("useDetailsStore must be used within DetailsProvider")
}
return useStore(store, selector)
}