"use client" import merge from "deepmerge" import { produce } from "immer" import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime" import { 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 { StepsContext } from "@/contexts/Steps" import { detailsStorageName as detailsStorageName } from "./details" import { StepEnum } from "@/types/enums/step" import type { DetailsState } from "@/types/stores/details" import type { StepState } from "@/types/stores/steps" export function createStepsStore( currentStep: StepEnum, isMember: boolean, noBedChoices: boolean, noBreakfast: boolean, searchParams: string, push: AppRouterInstance["push"] ) { const isBrowser = typeof window !== "undefined" const steps = [ StepEnum.selectBed, StepEnum.breakfast, StepEnum.details, StepEnum.payment, ] /** * 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. * * matching breakfast first so the steps array is altered * before the bedTypes possible step altering */ if (noBreakfast) { steps.splice(1, 1) if (currentStep === StepEnum.breakfast) { currentStep = steps[1] push(`${currentStep}?${searchParams}`) } } if (noBedChoices) { if (currentStep === StepEnum.selectBed) { currentStep = steps[1] push(`${currentStep}?${searchParams}`) } } const detailsStorageUnparsed = isBrowser ? sessionStorage.getItem(detailsStorageName) : null if (detailsStorageUnparsed) { const detailsStorage: Record< "state", Pick > = JSON.parse(detailsStorageUnparsed) const validPaths = [StepEnum.selectBed] const validatedBedType = bedTypeSchema.safeParse(detailsStorage.state.data) if (validatedBedType.success) { validPaths.push(steps[1]) } const validatedBreakfast = breakfastStoreSchema.safeParse( detailsStorage.state.data ) if (validatedBreakfast.success) { validPaths.push(StepEnum.details) } const detailsSchema = isMember ? signedInDetailsSchema : guestDetailsSchema const validatedDetails = detailsSchema.safeParse(detailsStorage.state.data) if (validatedDetails.success) { validPaths.push(StepEnum.payment) } if (!validPaths.includes(currentStep) && isBrowser) { // We will always have at least one valid path currentStep = validPaths.pop()! push(`${currentStep}?${searchParams}`) } } const initalData = { currentStep, steps, } return create()((set) => merge( { currentStep: StepEnum.selectBed, steps: [], completeStep() { return set( produce((state: StepState) => { const currentStepIndex = state.steps.indexOf(state.currentStep) const nextStep = state.steps[currentStepIndex + 1] state.currentStep = nextStep window.history.pushState( { step: nextStep }, "", nextStep + window.location.search ) }) ) }, navigate(step: StepEnum) { return set( produce((state) => { state.currentStep = step window.history.pushState( { step }, "", step + window.location.search ) }) ) }, setStep(step: StepEnum) { return set( produce((state: StepState) => { state.currentStep = step }) ) }, }, initalData ) ) } export function useStepsStore(selector: (store: StepState) => T) { const store = useContext(StepsContext) if (!store) { throw new Error(`useStepsStore must be used within StepsProvider`) } return useStore(store, selector) }