feat: make steps of enter details flow dynamic depending on data
This commit is contained in:
159
stores/steps.ts
Normal file
159
stores/steps.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
"use client"
|
||||
import merge from "deepmerge"
|
||||
import { produce } from "immer"
|
||||
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 { storageName as detailsStorageName } from "./details"
|
||||
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type { DetailsState } from "@/types/stores/details"
|
||||
import type { StepState } from "@/types/stores/steps"
|
||||
|
||||
function push(data: Record<string, string>, url: string) {
|
||||
if (typeof window !== "undefined") {
|
||||
window.history.pushState(data, "", url + window.location.search)
|
||||
}
|
||||
}
|
||||
|
||||
export function createStepsStore(
|
||||
currentStep: StepEnum,
|
||||
isMember: boolean,
|
||||
noBedChoices: boolean,
|
||||
noBreakfast: boolean
|
||||
) {
|
||||
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({ step: currentStep }, currentStep)
|
||||
}
|
||||
}
|
||||
|
||||
if (noBedChoices) {
|
||||
if (currentStep === StepEnum.selectBed) {
|
||||
currentStep = steps[1]
|
||||
push({ step: currentStep }, currentStep)
|
||||
}
|
||||
}
|
||||
|
||||
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({ step: currentStep }, currentStep)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user