import { produce } from "immer" import { ReadonlyURLSearchParams } from "next/navigation" import { createContext, useContext } from "react" import { create, useStore } from "zustand" import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema" import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema" import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Details/schema" import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details" import { SidePeekEnum } from "@/types/components/hotelReservation/enterDetails/sidePeek" import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import { BreakfastPackageEnum } from "@/types/enums/breakfast" const SESSION_STORAGE_KEY = "enterDetails" interface EnterDetailsState { userData: { bedType: string | undefined breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined } & DetailsSchema roomData: BookingData steps: StepEnum[] currentStep: StepEnum activeSidePeek: SidePeekEnum | null isValid: Record completeStep: (updatedData: Partial) => void navigate: ( step: StepEnum, updatedData?: Record ) => void setCurrentStep: (step: StepEnum) => void openSidePeek: (key: SidePeekEnum | null) => void closeSidePeek: () => void } export function initEditDetailsState( currentStep: StepEnum, searchParams: ReadonlyURLSearchParams ) { const isBrowser = typeof window !== "undefined" const sessionData = isBrowser ? sessionStorage.getItem(SESSION_STORAGE_KEY) : null let roomData: BookingData if (searchParams?.size) { roomData = getQueryParamsForEnterDetails(searchParams) } const defaultUserData: EnterDetailsState["userData"] = { bedType: undefined, breakfast: undefined, countryCode: "", email: "", firstName: "", lastName: "", phoneNumber: "", join: false, zipCode: "", dateOfBirth: undefined, termsAccepted: false, } let inputUserData = {} if (sessionData) { inputUserData = JSON.parse(sessionData) } const validPaths = [StepEnum.selectBed] let initialData: EnterDetailsState["userData"] = defaultUserData const isValid = { [StepEnum.selectBed]: false, [StepEnum.breakfast]: false, [StepEnum.details]: false, [StepEnum.payment]: false, } const validatedBedType = bedTypeSchema.safeParse(inputUserData) if (validatedBedType.success) { validPaths.push(StepEnum.breakfast) initialData = { ...initialData, ...validatedBedType.data } isValid[StepEnum.selectBed] = true } const validatedBreakfast = breakfastStoreSchema.safeParse(inputUserData) if (validatedBreakfast.success) { validPaths.push(StepEnum.details) initialData = { ...initialData, ...validatedBreakfast.data } isValid[StepEnum.breakfast] = true } const validatedDetails = detailsSchema.safeParse(inputUserData) if (validatedDetails.success) { validPaths.push(StepEnum.payment) initialData = { ...initialData, ...validatedDetails.data } isValid[StepEnum.details] = true } if (!validPaths.includes(currentStep)) { currentStep = validPaths.pop()! // We will always have at least one valid path if (isBrowser) { window.history.pushState( { step: currentStep }, "", currentStep + window.location.search ) } } return create()((set, get) => ({ userData: initialData, roomData, steps: Object.values(StepEnum), setCurrentStep: (step) => set({ currentStep: step }), navigate: (step, updatedData) => set( produce((state) => { const sessionStorage = window.sessionStorage const previousDataString = sessionStorage.getItem(SESSION_STORAGE_KEY) const previousData = JSON.parse(previousDataString || "{}") sessionStorage.setItem( SESSION_STORAGE_KEY, JSON.stringify({ ...previousData, ...updatedData }) ) state.currentStep = step window.history.pushState({ step }, "", step + window.location.search) }) ), openSidePeek: (key) => set({ activeSidePeek: key }), closeSidePeek: () => set({ activeSidePeek: null }), currentStep, activeSidePeek: null, isValid, completeStep: (updatedData) => set( produce((state: EnterDetailsState) => { state.isValid[state.currentStep] = true const nextStep = state.steps[state.steps.indexOf(state.currentStep) + 1] // @ts-expect-error: ts has a hard time understanding that "false | true" equals "boolean" state.userData = { ...state.userData, ...updatedData, } state.currentStep = nextStep get().navigate(nextStep, updatedData) }) ), })) } export type EnterDetailsStore = ReturnType export const EnterDetailsContext = createContext(null) export const useEnterDetailsStore = ( selector: (store: EnterDetailsState) => T ): T => { const enterDetailsContextStore = useContext(EnterDetailsContext) if (!enterDetailsContextStore) { throw new Error( `useEnterDetailsStore must be used within EnterDetailsContextProvider` ) } return useStore(enterDetailsContextStore, selector) }