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 { StepEnum } from "@/types/enums/step" import type { DetailsState, InitialState } from "@/types/stores/details" export const storageName = "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(storageName) if (detailsStorageUnparsed) { const detailsStorage: Record< "state", Pick > = JSON.parse(detailsStorageUnparsed) initialState = merge(initialState, detailsStorage.state.data) } } return create()( 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 }) ) }, updateValidity(property, isValid) { return set( produce((state: DetailsState) => { state.isValid[property] = isValid }) ) }, }, 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: "", price: 0 }, local: { currency: "", price: 0 }, }, }), { name: storageName, onRehydrateStorage() { return function (state) { if (state) { const validatedBedType = bedTypeSchema.safeParse(state.data) if (validatedBedType.success) { state.actions.updateValidity(StepEnum.selectBed, true) } else { state.actions.updateValidity(StepEnum.selectBed, false) } const validatedBreakfast = breakfastStoreSchema.safeParse( state.data ) if (validatedBreakfast.success) { state.actions.updateValidity(StepEnum.breakfast, true) } else { state.actions.updateValidity(StepEnum.breakfast, false) } const detailsSchema = isMember ? signedInDetailsSchema : guestDetailsSchema const validatedDetails = detailsSchema.safeParse(state.data) if (validatedDetails.success) { state.actions.updateValidity(StepEnum.details, true) } else { state.actions.updateValidity(StepEnum.details, false) } } } }, partialize(state) { return { data: state.data, } }, storage: createJSONStorage(() => sessionStorage), } ) ) } export function useDetailsStore(selector: (store: DetailsState) => T) { const store = useContext(DetailsContext) if (!store) { throw new Error("useDetailsStore must be used within DetailsProvider") } return useStore(store, selector) }