Files
web/stores/steps.ts
2024-11-21 10:18:36 +01:00

157 lines
4.2 KiB
TypeScript

"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<DetailsState, "data">
> = 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<StepState>()((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<T>(selector: (store: StepState) => T) {
const store = useContext(StepsContext)
if (!store) {
throw new Error(`useStepsStore must be used within StepsProvider`)
}
return useStore(store, selector)
}