diff --git a/apps/scandic-web/components/ContentType/HotelPage/SidePeeks/Room/PricesAndAvailabilityButton/index.tsx b/apps/scandic-web/components/ContentType/HotelPage/SidePeeks/Room/PricesAndAvailabilityButton/index.tsx index 83d210465..64cd242dd 100644 --- a/apps/scandic-web/components/ContentType/HotelPage/SidePeeks/Room/PricesAndAvailabilityButton/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelPage/SidePeeks/Room/PricesAndAvailabilityButton/index.tsx @@ -1,7 +1,8 @@ "use client" +import { useBookingWidgetState } from "@scandic-hotels/booking-flow/hooks/useBookingWidgetState" +import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url" import { selectRateWithParams } from "@scandic-hotels/common/constants/routes/hotelReservation" -import { dt } from "@scandic-hotels/common/dt" import ButtonLink from "@scandic-hotels/design-system/ButtonLink" import useLang from "@/hooks/useLang" @@ -15,9 +16,15 @@ export default function PricesAndAvailabilityButton({ label, }: PricesAndAvailabilityProps) { const lang = useLang() - const fromdate = dt().format("YYYY-MM-DD") - const todate = dt().add(1, "day").format("YYYY-MM-DD") - const selectRateURL = selectRateWithParams(lang, hotelId, fromdate, todate) + const { fromDate, toDate, rooms } = useBookingWidgetState() + + const params = serializeBookingSearchParams({ + hotelId, + fromDate, + toDate, + rooms, + }) + const selectRateURL = selectRateWithParams(lang, params) const { name, roomTypes } = room diff --git a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/index.tsx b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/index.tsx index 4091d9441..12952f6b0 100644 --- a/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/index.tsx +++ b/packages/booking-flow/lib/components/BookingWidget/BookingWidgetForm/index.tsx @@ -13,6 +13,7 @@ import { import { trackBookingSearchClick } from "@scandic-hotels/tracking/booking" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" +import { setBookingWidgetState } from "../../../hooks/useBookingWidgetState" import useLang from "../../../hooks/useLang" import { BookingCodeFilterEnum, @@ -68,7 +69,11 @@ export default function Form({ type, onClose }: BookingWidgetFormProps) { // Followed current url structure to keep searchtype=redemption param incase of reward night ...(data.redemption ? { searchType: SEARCH_TYPE_REDEMPTION } : {}), }) - + setBookingWidgetState({ + fromDate: data.date.fromDate, + toDate: data.date.toDate, + rooms: data.rooms, + }) onClose() startTransition(() => { router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`) diff --git a/packages/booking-flow/lib/components/BookingWidget/Client.tsx b/packages/booking-flow/lib/components/BookingWidget/Client.tsx index 1007f935c..825391c3b 100644 --- a/packages/booking-flow/lib/components/BookingWidget/Client.tsx +++ b/packages/booking-flow/lib/components/BookingWidget/Client.tsx @@ -15,6 +15,7 @@ import { trpc } from "@scandic-hotels/trpc/client" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext" +import { useBookingWidgetState } from "../../hooks/useBookingWidgetState" import useLang from "../../hooks/useLang" import { type bookingCodeSchema, @@ -54,6 +55,7 @@ export default function BookingWidgetClient({ null ) const { bookingCodeEnabled } = useBookingFlowConfig() + const storedBookingWidgetState = useBookingWidgetState() const shouldFetchAutoComplete = !!data.hotelId || !!data.city @@ -207,6 +209,24 @@ export default function BookingWidgetClient({ } }, [methods, selectedBookingCode]) + useEffect(() => { + if ( + !data.fromDate && + !data.toDate && + !data.rooms && + storedBookingWidgetState + ) { + methods.reset({ + ...methods.getValues(), + date: { + fromDate: storedBookingWidgetState.fromDate, + toDate: storedBookingWidgetState.toDate, + }, + rooms: storedBookingWidgetState.rooms, + }) + } + }, [data, methods, storedBookingWidgetState]) + if (shouldShowSkeleton) { return } diff --git a/packages/booking-flow/lib/hooks/useBookingWidgetState.ts b/packages/booking-flow/lib/hooks/useBookingWidgetState.ts new file mode 100644 index 000000000..fd0d16736 --- /dev/null +++ b/packages/booking-flow/lib/hooks/useBookingWidgetState.ts @@ -0,0 +1,92 @@ +import { useEffect, useState } from "react" +import { z } from "zod" + +import { BOOKING_WIDGET_STATE } from "@scandic-hotels/common/constants/bookingWidget" +import { dt } from "@scandic-hotels/common/dt" + +import { adultsSchema, childAgeSchema, childBedSchema } from "../utils/url" + +const DEFAULT_ROOMS = [{ adults: 1, childrenInRoom: [] }] + +const stateSchema = z.object({ + fromDate: z.string(), + toDate: z.string(), + rooms: z.array( + z.object({ + adults: adultsSchema, + childrenInRoom: z + .array( + z.object({ + bed: childBedSchema, + age: childAgeSchema, + }) + ) + .optional(), + }) + ), +}) + +type BookingWidgetState = z.infer + +export function useBookingWidgetState() { + const [bookingState, setBookingState] = useState({ + fromDate: dt().format("YYYY-MM-DD"), + toDate: dt().add(1, "day").format("YYYY-MM-DD"), + rooms: DEFAULT_ROOMS, + }) + + useEffect(() => { + const now = dt() + const stored = getBookingWidgetState() + if (!stored) { + return + } + const storedFrom = dt(stored.fromDate) + const storedTo = dt(stored.toDate) + + const isDateParamValid = + storedFrom.isValid() && + storedTo.isValid() && + storedFrom.isSameOrAfter(now, "day") && + storedTo.isAfter(storedFrom) + + if (isDateParamValid && stored.rooms?.length) { + setBookingState({ + fromDate: storedFrom.format("YYYY-MM-DD"), + toDate: storedTo.format("YYYY-MM-DD"), + rooms: stored.rooms, + }) + } + }, []) + return bookingState +} + +function getBookingWidgetState(): BookingWidgetState | null { + if (typeof window === "undefined") { + return null + } + try { + const savedBookingWidgetState = sessionStorage.getItem(BOOKING_WIDGET_STATE) + if (savedBookingWidgetState) { + const stored = JSON.parse(savedBookingWidgetState) + return stateSchema.parse(stored) + } + } catch { + try { + sessionStorage.removeItem(BOOKING_WIDGET_STATE) + } catch {} + } + return null +} + +export function setBookingWidgetState(state: BookingWidgetState): void { + if (typeof window === "undefined") { + return + } + try { + sessionStorage.setItem( + BOOKING_WIDGET_STATE, + JSON.stringify(stateSchema.parse(state)) + ) + } catch {} +} diff --git a/packages/booking-flow/lib/utils/url.ts b/packages/booking-flow/lib/utils/url.ts index a6916a938..a66fe8a5c 100644 --- a/packages/booking-flow/lib/utils/url.ts +++ b/packages/booking-flow/lib/utils/url.ts @@ -39,9 +39,9 @@ const typeHints = { filters: "COMMA_SEPARATED_ARRAY", packages: "COMMA_SEPARATED_ARRAY", } as const -const adultsSchema = z.coerce.number().min(1).max(6).catch(0) -const childAgeSchema = z.coerce.number().catch(-1) -const childBedSchema = z.coerce.number().catch(-1) +export const adultsSchema = z.coerce.number().min(1).max(6).catch(0) +export const childAgeSchema = z.coerce.number().catch(-1) +export const childBedSchema = z.coerce.number().catch(-1) const searchTypeSchema = z.enum(bookingSearchTypes).optional().catch(undefined) export function parseBookingWidgetSearchParams( diff --git a/packages/booking-flow/package.json b/packages/booking-flow/package.json index 4bec39e97..78cca3d0e 100644 --- a/packages/booking-flow/package.json +++ b/packages/booking-flow/package.json @@ -28,6 +28,7 @@ "./components/SidePeekAccordions/ParkingAccordionItem": "./lib/components/SidePeekAccordions/ParkingAccordionItem.tsx", "./global.d.ts": "./global.d.ts", "./hooks/useHandleBookingStatus": "./lib/hooks/useHandleBookingStatus.ts", + "./hooks/useBookingWidgetState": "./lib/hooks/useBookingWidgetState.ts", "./pages/*": "./lib/pages/*.tsx", "./stores/enter-details/types": "./lib/stores/enter-details/types.ts", "./stores/hotels-map": "./lib/stores/hotels-map.ts", diff --git a/packages/common/constants/bookingWidget.ts b/packages/common/constants/bookingWidget.ts new file mode 100644 index 000000000..944106284 --- /dev/null +++ b/packages/common/constants/bookingWidget.ts @@ -0,0 +1 @@ +export const BOOKING_WIDGET_STATE = "bookingWidgetState" diff --git a/packages/common/constants/routes/hotelReservation.ts b/packages/common/constants/routes/hotelReservation.ts index a4a646e51..bdb771b3e 100644 --- a/packages/common/constants/routes/hotelReservation.ts +++ b/packages/common/constants/routes/hotelReservation.ts @@ -27,14 +27,8 @@ export function selectHotel(lang: Lang) { export function selectHotelMap(lang: Lang) { return `${hotelreservation(lang)}/select-hotel/map` } - -export function selectRateWithParams( - lang: Lang, - hotelId: string, - fromdate: string, - todate: string -) { - return `${hotelreservation(lang)}/select-rate?room%5B0%5D.adults=1&fromdate=${fromdate}&todate=${todate}&hotel=${hotelId}` +export function selectRateWithParams(lang: Lang, params: URLSearchParams) { + return `${hotelreservation(lang)}/select-rate?${params.toString()}` } export function alternativeHotels(lang: Lang) { diff --git a/packages/common/package.json b/packages/common/package.json index 840b611d1..c9654b894 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -14,6 +14,7 @@ "./polyfills": "./polyfills/index.ts", "./constants/alert": "./constants/alert.ts", "./constants/booking": "./constants/booking.ts", + "./constants/bookingWidget": "./constants/bookingWidget.ts", "./constants/currency": "./constants/currency.ts", "./constants/dateFormats": "./constants/dateFormats.ts", "./constants/facilities": "./constants/facilities.ts",