Merge branch 'develop'

This commit is contained in:
Linus Flood
2024-11-07 10:20:12 +01:00
261 changed files with 5132 additions and 2106 deletions

View File

@@ -1,46 +1,57 @@
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 {
guestDetailsSchema,
signedInDetailsSchema,
} from "@/components/HotelReservation/EnterDetails/Details/schema"
import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { BreakfastPackage } from "@/types/components/enterDetails/breakfast"
import { DetailsSchema } from "@/types/components/enterDetails/details"
import { SidePeekEnum } from "@/types/components/enterDetails/sidePeek"
import { StepEnum } from "@/types/components/enterDetails/step"
import { BedTypeEnum } from "@/types/enums/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 {
data: {
bedType: BedTypeEnum | undefined
userData: {
bedType: string | undefined
breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined
} & DetailsSchema
roomData: BookingData
steps: StepEnum[]
currentStep: StepEnum
activeSidePeek: SidePeekEnum | null
isValid: Record<StepEnum, boolean>
completeStep: (updatedData: Partial<EnterDetailsState["data"]>) => void
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
navigate: (
step: StepEnum,
updatedData?: Record<string, string | boolean | BreakfastPackage>
) => void
setCurrentStep: (step: StepEnum) => void
openSidePeek: (key: SidePeekEnum | null) => void
closeSidePeek: () => void
}
export function initEditDetailsState(currentStep: StepEnum) {
export function initEditDetailsState(
currentStep: StepEnum,
searchParams: ReadonlyURLSearchParams,
isMember: boolean
) {
const isBrowser = typeof window !== "undefined"
const sessionData = isBrowser
? sessionStorage.getItem(SESSION_STORAGE_KEY)
: null
const defaultData: EnterDetailsState["data"] = {
let roomData: BookingData
if (searchParams?.size) {
roomData = getQueryParamsForEnterDetails(searchParams)
}
const defaultUserData: EnterDetailsState["userData"] = {
bedType: undefined,
breakfast: undefined,
countryCode: "",
@@ -54,14 +65,14 @@ export function initEditDetailsState(currentStep: StepEnum) {
termsAccepted: false,
}
let inputData = {}
let inputUserData = {}
if (sessionData) {
inputData = JSON.parse(sessionData)
inputUserData = JSON.parse(sessionData)
}
const validPaths = [StepEnum.selectBed]
let initialData: EnterDetailsState["data"] = defaultData
let initialData: EnterDetailsState["userData"] = defaultUserData
const isValid = {
[StepEnum.selectBed]: false,
@@ -70,19 +81,20 @@ export function initEditDetailsState(currentStep: StepEnum) {
[StepEnum.payment]: false,
}
const validatedBedType = bedTypeSchema.safeParse(inputData)
const validatedBedType = bedTypeSchema.safeParse(inputUserData)
if (validatedBedType.success) {
validPaths.push(StepEnum.breakfast)
initialData = { ...initialData, ...validatedBedType.data }
isValid[StepEnum.selectBed] = true
}
const validatedBreakfast = breakfastStoreSchema.safeParse(inputData)
const validatedBreakfast = breakfastStoreSchema.safeParse(inputUserData)
if (validatedBreakfast.success) {
validPaths.push(StepEnum.details)
initialData = { ...initialData, ...validatedBreakfast.data }
isValid[StepEnum.breakfast] = true
}
const validatedDetails = detailsSchema.safeParse(inputData)
const detailsSchema = isMember ? signedInDetailsSchema : guestDetailsSchema
const validatedDetails = detailsSchema.safeParse(inputUserData)
if (validatedDetails.success) {
validPaths.push(StepEnum.payment)
initialData = { ...initialData, ...validatedDetails.data }
@@ -101,7 +113,8 @@ export function initEditDetailsState(currentStep: StepEnum) {
}
return create<EnterDetailsState>()((set, get) => ({
data: initialData,
userData: initialData,
roomData,
steps: Object.values(StepEnum),
setCurrentStep: (step) => set({ currentStep: step }),
navigate: (step, updatedData) =>
@@ -122,21 +135,21 @@ export function initEditDetailsState(currentStep: StepEnum) {
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) => {
produce((state: EnterDetailsState) => {
state.isValid[state.currentStep] = true
const nextStep =
state.steps[state.steps.indexOf(state.currentStep) + 1]
state.data = { ...state.data, ...updatedData }
// @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)
})

31
stores/sidepeek.ts Normal file
View File

@@ -0,0 +1,31 @@
import { create } from "zustand"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
interface SidePeekState {
activeSidePeek: SidePeekEnum | null
hotelId: string | null
roomTypeCode: string | null
openSidePeek: ({
key,
hotelId,
roomTypeCode,
}: {
key: SidePeekEnum | null
hotelId: string
roomTypeCode?: string
}) => void
closeSidePeek: () => void
}
const useSidePeekStore = create<SidePeekState>((set) => ({
activeSidePeek: null,
hotelId: null,
roomTypeCode: null,
openSidePeek: ({ key, hotelId, roomTypeCode }) =>
set({ activeSidePeek: key, hotelId, roomTypeCode }),
closeSidePeek: () =>
set({ activeSidePeek: null, hotelId: null, roomTypeCode: null }),
}))
export default useSidePeekStore

81
stores/sticky-position.ts Normal file
View File

@@ -0,0 +1,81 @@
import { create } from "zustand"
export enum StickyElementNameEnum {
SITEWIDE_ALERT = "SITEWIDE_ALERT",
BOOKING_WIDGET = "BOOKING_WIDGET",
BOOKING_WIDGET_MOBILE = "BOOKING_WIDGET_MOBILE",
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
}
export interface StickyElement {
height: number
ref: React.RefObject<HTMLElement>
group: string
priority: number
name: StickyElementNameEnum
}
interface StickyStore {
stickyElements: StickyElement[]
registerSticky: (
ref: React.RefObject<HTMLElement>,
name: StickyElementNameEnum,
group: string
) => void
unregisterSticky: (ref: React.RefObject<HTMLElement>) => void
updateHeights: () => void
getAllElements: () => Array<StickyElement>
}
// Map to define priorities based on StickyElementNameEnum
const priorityMap: Record<StickyElementNameEnum, number> = {
[StickyElementNameEnum.SITEWIDE_ALERT]: 1,
[StickyElementNameEnum.BOOKING_WIDGET]: 2,
[StickyElementNameEnum.BOOKING_WIDGET_MOBILE]: 2,
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,
}
const useStickyPositionStore = create<StickyStore>((set, get) => ({
stickyElements: [],
registerSticky: (ref, name, group) => {
const priority = priorityMap[name] || 0
set((state) => {
const newStickyElement: StickyElement = {
height: ref.current?.offsetHeight || 0,
ref,
group,
priority,
name,
}
const updatedStickyElements = [
...state.stickyElements,
newStickyElement,
].sort((a, b) => a.priority - b.priority)
return {
stickyElements: updatedStickyElements,
}
})
},
unregisterSticky: (ref) => {
set((state) => ({
stickyElements: state.stickyElements.filter((el) => el.ref !== ref),
}))
},
updateHeights: () => {
set((state) => ({
stickyElements: state.stickyElements.map((el) => ({
...el,
height: el.ref.current?.offsetHeight || el.height,
})),
}))
},
getAllElements: () => get().stickyElements,
}))
export default useStickyPositionStore