Merged in feat/SW-977-update-bookingwidget-with-hotel-name (pull request #1071)

Feat/SW-977 update bookingwidget with hotel name

Approved-by: Fredrik Thorsson
This commit is contained in:
Pontus Dreij
2024-12-17 14:47:43 +00:00
8 changed files with 95 additions and 26 deletions

View File

@@ -0,0 +1 @@
export { default } from "../page"

View File

@@ -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<ContentTypeParams, URLSearchParams>) {
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 <BookingWidget searchParams={urlParams} />
}
return <BookingWidget searchParams={searchParams} />
}

View File

@@ -13,6 +13,7 @@ import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
import { CloseLargeIcon } from "@/components/Icons" import { CloseLargeIcon } from "@/components/Icons"
import useStickyPosition from "@/hooks/useStickyPosition" import useStickyPosition from "@/hooks/useStickyPosition"
import { debounce } from "@/utils/debounce" import { debounce } from "@/utils/debounce"
import isValidJson from "@/utils/isValidJson"
import { getFormattedUrlQueryParams } from "@/utils/url" import { getFormattedUrlQueryParams } from "@/utils/url"
import MobileToggleButton, { import MobileToggleButton, {
@@ -79,8 +80,8 @@ export default function BookingWidgetClient({
const selectedLocation = bookingWidgetSearchData const selectedLocation = bookingWidgetSearchData
? getLocationObj( ? getLocationObj(
(bookingWidgetSearchData.city ?? (bookingWidgetSearchData.hotel ??
bookingWidgetSearchData.hotel) as string bookingWidgetSearchData.city) as string
) )
: undefined : undefined
@@ -153,8 +154,9 @@ export default function BookingWidgetClient({
typeof window !== "undefined" typeof window !== "undefined"
? sessionStorage.getItem("searchData") ? sessionStorage.getItem("searchData")
: undefined : undefined
const initialSelectedLocation: Location | undefined = const initialSelectedLocation: Location | undefined =
sessionStorageSearchData sessionStorageSearchData && isValidJson(sessionStorageSearchData)
? JSON.parse(sessionStorageSearchData) ? JSON.parse(sessionStorageSearchData)
: undefined : undefined

View File

@@ -1,10 +1,8 @@
"use client" "use client"
import { useEffect, useMemo, useRef, useState } from "react"
import { useWatch } from "react-hook-form" import { useWatch } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import { StickyElementNameEnum } from "@/stores/sticky-position"
import { EditIcon, SearchIcon } from "@/components/Icons" import { EditIcon, SearchIcon } from "@/components/Icons"
import SkeletonShimmer from "@/components/SkeletonShimmer" import SkeletonShimmer from "@/components/SkeletonShimmer"
@@ -12,7 +10,7 @@ import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import useStickyPosition from "@/hooks/useStickyPosition" import isValidJson from "@/utils/isValidJson"
import styles from "./button.module.css" import styles from "./button.module.css"
@@ -31,9 +29,10 @@ export default function MobileToggleButton({
const location = useWatch({ name: "location" }) const location = useWatch({ name: "location" })
const rooms: BookingWidgetSchema["rooms"] = useWatch({ name: "rooms" }) const rooms: BookingWidgetSchema["rooms"] = useWatch({ name: "rooms" })
const parsedLocation: Location | null = location const parsedLocation: Location | null =
? JSON.parse(decodeURIComponent(location)) location && isValidJson(location)
: null ? JSON.parse(decodeURIComponent(location))
: null
const nights = dt(d.toDate).diff(dt(d.fromDate), "days") const nights = dt(d.toDate).diff(dt(d.fromDate), "days")

View File

@@ -13,6 +13,7 @@ import { useIntl } from "react-intl"
import SkeletonShimmer from "@/components/SkeletonShimmer" import SkeletonShimmer from "@/components/SkeletonShimmer"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import isValidJson from "@/utils/isValidJson"
import Input from "../Input" import Input from "../Input"
import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer" import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer"
@@ -28,16 +29,20 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
const name = "search" const name = "search"
export default function Search({ locations, handlePressEnter }: SearchProps) { export default function Search({ locations, handlePressEnter }: SearchProps) {
const { register, setValue, unregister } = const { register, setValue, unregister, getValues } =
useFormContext<BookingWidgetSchema>() useFormContext<BookingWidgetSchema>()
const intl = useIntl() const intl = useIntl()
const value = useWatch({ name }) const value = useWatch({ name })
const locationString = getValues("location")
const location =
locationString && isValidJson(locationString)
? JSON.parse(decodeURIComponent(locationString))
: null
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
reducer, reducer,
{ defaultLocations: locations }, { defaultLocations: locations },
init init
) )
const handleMatchLocations = useCallback( const handleMatchLocations = useCallback(
function (searchValue: string) { function (searchValue: string) {
return locations.filter((location) => { return locations.filter((location) => {
@@ -121,6 +126,7 @@ export default function Search({ locations, handlePressEnter }: SearchProps) {
typeof window !== "undefined" typeof window !== "undefined"
? sessionStorage.getItem(sessionStorageKey) ? sessionStorage.getItem(sessionStorageKey)
: undefined : undefined
const searchHistory = const searchHistory =
typeof window !== "undefined" typeof window !== "undefined"
? localStorage.getItem(localStorageKey) ? localStorage.getItem(localStorageKey)
@@ -128,8 +134,14 @@ export default function Search({ locations, handlePressEnter }: SearchProps) {
if (searchData || searchHistory) { if (searchData || searchHistory) {
dispatch({ dispatch({
payload: { payload: {
searchData: searchData ? JSON.parse(searchData) : undefined, searchData:
searchHistory: searchHistory ? JSON.parse(searchHistory) : null, isValidJson(searchData) && searchData
? JSON.parse(searchData)
: undefined,
searchHistory:
isValidJson(searchHistory) && searchHistory
? JSON.parse(searchHistory)
: null,
}, },
type: ActionType.SET_STORAGE_DATA, type: ActionType.SET_STORAGE_DATA,
}) })
@@ -157,6 +169,21 @@ export default function Search({ locations, handlePressEnter }: SearchProps) {
} }
}, [stayType, stayValue, unregister, setValue]) }, [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 ( return (
<Downshift <Downshift
initialSelectedItem={state.searchData} initialSelectedItem={state.searchData}
@@ -187,11 +214,7 @@ export default function Search({ locations, handlePressEnter }: SearchProps) {
color={isOpen ? "uiTextActive" : "red"} color={isOpen ? "uiTextActive" : "red"}
asChild asChild
> >
<span> <span>{getLocationLabel()}</span>
{state.searchData?.type === "hotels"
? state.searchData?.relationships?.city?.name
: intl.formatMessage({ id: "Where to" })}
</span>
</Caption> </Caption>
</label> </label>
<div {...getRootProps({}, { suppressRefError: true })}> <div {...getRootProps({}, { suppressRefError: true })}>

View File

@@ -45,7 +45,10 @@ export const bookingWidgetSchema = z
}), }),
location: z.string().refine( location: z.string().refine(
(value) => { (value) => {
if (value) { if (!value) {
return false
}
try {
const parsedValue: Location = JSON.parse(decodeURIComponent(value)) const parsedValue: Location = JSON.parse(decodeURIComponent(value))
switch (parsedValue?.type) { switch (parsedValue?.type) {
case "cities": case "cities":
@@ -54,6 +57,8 @@ export const bookingWidgetSchema = z
default: default:
return false return false
} }
} catch (error) {
return false
} }
}, },
{ message: "Required" } { message: "Required" }

View File

@@ -1,12 +1,10 @@
import { VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import { z } from "zod" import type { z } from "zod"
import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
import { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants"
import { GuestsRoom } from "./guestsRoomsPicker"
import type { Locations } from "@/types/trpc/routers/hotel/locations" 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<typeof bookingWidgetSchema> export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema>

9
utils/isValidJson.ts Normal file
View File

@@ -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
}
}