diff --git a/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/[...paths]/page.tsx b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/[...paths]/page.tsx new file mode 100644 index 000000000..03a82e5f5 --- /dev/null +++ b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/[...paths]/page.tsx @@ -0,0 +1 @@ +export { default } from "../page" diff --git a/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx new file mode 100644 index 000000000..fc57012f6 --- /dev/null +++ b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx @@ -0,0 +1,32 @@ +import { env } from "@/env/server" +import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests" + +import BookingWidget, { preload } from "@/components/BookingWidget" +import { getLang } from "@/i18n/serverContext" + +import type { ContentTypeParams, PageArgs } from "@/types/params" + +export default async function BookingWidgetPage({ + params, + searchParams, +}: PageArgs) { + if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) return null + + preload() + + const urlParams = new URLSearchParams() + + if (params.contentType === "hotel-page") { + const hotelPageData = await getHotelPage() + + const hotelData = await getHotelData({ + hotelId: hotelPageData?.hotel_page_id || "", + language: getLang(), + }) + urlParams.set("hotel", hotelData?.data?.id || "") + urlParams.set("city", hotelData?.data?.attributes?.cityName || "") + + return + } + return +} diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 3c17caf8e..017d9c76a 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -13,6 +13,7 @@ import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" import { CloseLargeIcon } from "@/components/Icons" import useStickyPosition from "@/hooks/useStickyPosition" import { debounce } from "@/utils/debounce" +import isValidJson from "@/utils/isValidJson" import { getFormattedUrlQueryParams } from "@/utils/url" import MobileToggleButton, { @@ -79,8 +80,8 @@ export default function BookingWidgetClient({ const selectedLocation = bookingWidgetSearchData ? getLocationObj( - (bookingWidgetSearchData.city ?? - bookingWidgetSearchData.hotel) as string + (bookingWidgetSearchData.hotel ?? + bookingWidgetSearchData.city) as string ) : undefined @@ -153,8 +154,9 @@ export default function BookingWidgetClient({ typeof window !== "undefined" ? sessionStorage.getItem("searchData") : undefined + const initialSelectedLocation: Location | undefined = - sessionStorageSearchData + sessionStorageSearchData && isValidJson(sessionStorageSearchData) ? JSON.parse(sessionStorageSearchData) : undefined diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx index 99e691359..ce4513d58 100644 --- a/components/BookingWidget/MobileToggleButton/index.tsx +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -1,10 +1,8 @@ "use client" -import { useEffect, useMemo, useRef, useState } from "react" import { useWatch } from "react-hook-form" import { useIntl } from "react-intl" import { dt } from "@/lib/dt" -import { StickyElementNameEnum } from "@/stores/sticky-position" import { EditIcon, SearchIcon } from "@/components/Icons" import SkeletonShimmer from "@/components/SkeletonShimmer" @@ -12,7 +10,7 @@ import Divider from "@/components/TempDesignSystem/Divider" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import useLang from "@/hooks/useLang" -import useStickyPosition from "@/hooks/useStickyPosition" +import isValidJson from "@/utils/isValidJson" import styles from "./button.module.css" @@ -31,9 +29,10 @@ export default function MobileToggleButton({ const location = useWatch({ name: "location" }) const rooms: BookingWidgetSchema["rooms"] = useWatch({ name: "rooms" }) - const parsedLocation: Location | null = location - ? JSON.parse(decodeURIComponent(location)) - : null + const parsedLocation: Location | null = + location && isValidJson(location) + ? JSON.parse(decodeURIComponent(location)) + : null const nights = dt(d.toDate).diff(dt(d.fromDate), "days") diff --git a/components/Forms/BookingWidget/FormContent/Search/index.tsx b/components/Forms/BookingWidget/FormContent/Search/index.tsx index aaf7fda7b..3df3f9c7c 100644 --- a/components/Forms/BookingWidget/FormContent/Search/index.tsx +++ b/components/Forms/BookingWidget/FormContent/Search/index.tsx @@ -13,6 +13,7 @@ import { useIntl } from "react-intl" import SkeletonShimmer from "@/components/SkeletonShimmer" import Caption from "@/components/TempDesignSystem/Text/Caption" +import isValidJson from "@/utils/isValidJson" import Input from "../Input" import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer" @@ -28,16 +29,20 @@ import type { Location } from "@/types/trpc/routers/hotel/locations" const name = "search" export default function Search({ locations, handlePressEnter }: SearchProps) { - const { register, setValue, unregister } = + const { register, setValue, unregister, getValues } = useFormContext() const intl = useIntl() const value = useWatch({ name }) + const locationString = getValues("location") + const location = + locationString && isValidJson(locationString) + ? JSON.parse(decodeURIComponent(locationString)) + : null const [state, dispatch] = useReducer( reducer, { defaultLocations: locations }, init ) - const handleMatchLocations = useCallback( function (searchValue: string) { return locations.filter((location) => { @@ -121,6 +126,7 @@ export default function Search({ locations, handlePressEnter }: SearchProps) { typeof window !== "undefined" ? sessionStorage.getItem(sessionStorageKey) : undefined + const searchHistory = typeof window !== "undefined" ? localStorage.getItem(localStorageKey) @@ -128,8 +134,14 @@ export default function Search({ locations, handlePressEnter }: SearchProps) { if (searchData || searchHistory) { dispatch({ payload: { - searchData: searchData ? JSON.parse(searchData) : undefined, - searchHistory: searchHistory ? JSON.parse(searchHistory) : null, + searchData: + isValidJson(searchData) && searchData + ? JSON.parse(searchData) + : undefined, + searchHistory: + isValidJson(searchHistory) && searchHistory + ? JSON.parse(searchHistory) + : null, }, type: ActionType.SET_STORAGE_DATA, }) @@ -157,6 +169,21 @@ export default function Search({ locations, handlePressEnter }: SearchProps) { } }, [stayType, stayValue, unregister, setValue]) + useEffect(() => { + sessionStorage.setItem(sessionStorageKey, locationString) + }, [locationString]) + + function getLocationLabel(): string { + if (location?.type === "hotels") { + return location?.relationships?.city?.name || "" + } + if (state.searchData?.type === "hotels") { + return state.searchData?.relationships?.city?.name || "" + } + + return intl.formatMessage({ id: "Where to" }) + } + return ( - - {state.searchData?.type === "hotels" - ? state.searchData?.relationships?.city?.name - : intl.formatMessage({ id: "Where to" })} - + {getLocationLabel()}
diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index a5e62a989..c8beb98d5 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -45,7 +45,10 @@ export const bookingWidgetSchema = z }), location: z.string().refine( (value) => { - if (value) { + if (!value) { + return false + } + try { const parsedValue: Location = JSON.parse(decodeURIComponent(value)) switch (parsedValue?.type) { case "cities": @@ -54,6 +57,8 @@ export const bookingWidgetSchema = z default: return false } + } catch (error) { + return false } }, { message: "Required" } diff --git a/types/components/bookingWidget/index.ts b/types/components/bookingWidget/index.ts index c668617da..7e9c79d16 100644 --- a/types/components/bookingWidget/index.ts +++ b/types/components/bookingWidget/index.ts @@ -1,12 +1,10 @@ -import { VariantProps } from "class-variance-authority" -import { z } from "zod" - -import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" -import { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants" - -import { GuestsRoom } from "./guestsRoomsPicker" +import type { VariantProps } from "class-variance-authority" +import type { z } from "zod" import type { Locations } from "@/types/trpc/routers/hotel/locations" +import type { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" +import type { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants" +import type { GuestsRoom } from "./guestsRoomsPicker" export type BookingWidgetSchema = z.output diff --git a/utils/isValidJson.ts b/utils/isValidJson.ts new file mode 100644 index 000000000..498cff121 --- /dev/null +++ b/utils/isValidJson.ts @@ -0,0 +1,9 @@ +export default function isValidJson(value: string | null | undefined): boolean { + if (!value || value === "undefined") return false + try { + JSON.parse(value) + return true + } catch { + return false + } +}