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 { guestDetailsSchema, signedInDetailsSchema, } from "@/components/HotelReservation/EnterDetails/Details/schema" import { createSelectRateUrl, getQueryParamsForEnterDetails, } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType" import { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" import { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details" import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import { BreakfastPackageEnum } from "@/types/enums/breakfast" const SESSION_STORAGE_KEY = "enterDetails" interface EnterDetailsState { userData: { bedType: BedTypeSchema | undefined breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined } & DetailsSchema roomData: BookingData steps: StepEnum[] selectRateUrl: string currentStep: StepEnum isValid: Record completeStep: (updatedData: Partial) => void navigate: ( step: StepEnum, updatedData?: Record< string, string | boolean | BreakfastPackage | BedTypeSchema > ) => void setCurrentStep: (step: StepEnum) => void } export function initEditDetailsState( currentStep: StepEnum, searchParams: ReadonlyURLSearchParams, isMember: boolean ) { const isBrowser = typeof window !== "undefined" const sessionData = isBrowser ? sessionStorage.getItem(SESSION_STORAGE_KEY) : null let roomData: BookingData let selectRateUrl: string if (searchParams?.size) { const data = getQueryParamsForEnterDetails(searchParams) roomData = data selectRateUrl = `select-rate?${createSelectRateUrl(data)}` } 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 detailsSchema = isMember ? signedInDetailsSchema : guestDetailsSchema 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, selectRateUrl, 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) }) ), currentStep, 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) }