Merged in feature/bookingwidget-client-side (pull request #1481)
Move more of BookingWidget to client SW-1639 * feat: move getLocations in booking widget to client side so that it's also cached on the client reducing the blinking when switching urls (and reducing duplicate calls) Approved-by: Linus Flood
This commit is contained in:
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
|
|||||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
||||||
|
import { safeTry } from "@/utils/safeTry"
|
||||||
import { convertSearchParamsToObj, type SelectHotelParams } from "@/utils/url"
|
import { convertSearchParamsToObj, type SelectHotelParams } from "@/utils/url"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -46,13 +47,14 @@ export async function getHotelSearchDetails<
|
|||||||
): Promise<HotelSearchDetails<T> | null> {
|
): Promise<HotelSearchDetails<T> | null> {
|
||||||
const selectHotelParams = convertSearchParamsToObj<T>(searchParams)
|
const selectHotelParams = convertSearchParamsToObj<T>(searchParams)
|
||||||
|
|
||||||
const locations = await getLocations()
|
const [locations, error] = await safeTry(getLocations())
|
||||||
|
if (!locations || error) {
|
||||||
if (!locations || "error" in locations) return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const hotel =
|
const hotel =
|
||||||
("hotelId" in selectHotelParams &&
|
("hotelId" in selectHotelParams &&
|
||||||
(locations.data.find(
|
(locations.find(
|
||||||
(location) =>
|
(location) =>
|
||||||
isHotelLocation(location) &&
|
isHotelLocation(location) &&
|
||||||
"operaId" in location &&
|
"operaId" in location &&
|
||||||
@@ -72,7 +74,7 @@ export async function getHotelSearchDetails<
|
|||||||
|
|
||||||
const city =
|
const city =
|
||||||
(typeof cityName === "string" &&
|
(typeof cityName === "string" &&
|
||||||
locations.data.find(
|
locations.find(
|
||||||
(location) => location.name.toLowerCase() === cityName.toLowerCase()
|
(location) => location.name.toLowerCase() === cityName.toLowerCase()
|
||||||
)) ||
|
)) ||
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import BookingWidget, { preload } from "@/components/BookingWidget"
|
import BookingWidget from "@/components/BookingWidget"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
||||||
@@ -16,8 +16,6 @@ export default async function BookingWidgetPage({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
preload()
|
|
||||||
|
|
||||||
if (params.contentType === PageContentTypeEnum.hotelPage) {
|
if (params.contentType === PageContentTypeEnum.hotelPage) {
|
||||||
const hotelPageData = await getHotelPage()
|
const hotelPageData = await getHotelPage()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import BookingWidget, { preload } from "@/components/BookingWidget"
|
import BookingWidget from "@/components/BookingWidget"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
@@ -12,7 +12,5 @@ export default async function BookingWidgetPage({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
preload()
|
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import BookingWidget, { preload } from "@/components/BookingWidget"
|
import BookingWidget from "@/components/BookingWidget"
|
||||||
|
|
||||||
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
|
||||||
import type { PageArgs } from "@/types/params"
|
import type { PageArgs } from "@/types/params"
|
||||||
@@ -12,7 +12,5 @@ export default async function BookingWidgetPage({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
preload()
|
|
||||||
|
|
||||||
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
return <BookingWidget bookingWidgetSearchParams={searchParams} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react"
|
|||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { StickyElementNameEnum } from "@/stores/sticky-position"
|
import { StickyElementNameEnum } from "@/stores/sticky-position"
|
||||||
|
|
||||||
import Form, {
|
import Form, {
|
||||||
@@ -12,6 +13,7 @@ import Form, {
|
|||||||
} from "@/components/Forms/BookingWidget"
|
} from "@/components/Forms/BookingWidget"
|
||||||
import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
|
import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
|
||||||
import { CloseLargeIcon } from "@/components/Icons"
|
import { CloseLargeIcon } from "@/components/Icons"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
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 isValidJson from "@/utils/isValidJson"
|
||||||
@@ -31,32 +33,14 @@ import type {
|
|||||||
} from "@/types/components/bookingWidget"
|
} from "@/types/components/bookingWidget"
|
||||||
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||||
|
|
||||||
function getLocationObj(locations: Location[], destination: string) {
|
|
||||||
try {
|
|
||||||
const location = locations.find((location) => {
|
|
||||||
if (location.type === "hotels") {
|
|
||||||
return location.operaId === destination
|
|
||||||
} else if (location.type === "cities") {
|
|
||||||
return location.name.toLowerCase() === destination.toLowerCase()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (location) {
|
|
||||||
return location
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// ignore any errors
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BookingWidgetClient({
|
export default function BookingWidgetClient({
|
||||||
locations,
|
|
||||||
type,
|
type,
|
||||||
bookingWidgetSearchParams,
|
bookingWidgetSearchParams,
|
||||||
}: BookingWidgetClientProps) {
|
}: BookingWidgetClientProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const bookingWidgetRef = useRef(null)
|
const bookingWidgetRef = useRef(null)
|
||||||
|
const lang = useLang()
|
||||||
|
const [locations] = trpc.hotel.locations.get.useSuspenseQuery({ lang })
|
||||||
|
|
||||||
useStickyPosition({
|
useStickyPosition({
|
||||||
ref: bookingWidgetRef,
|
ref: bookingWidgetRef,
|
||||||
@@ -225,3 +209,22 @@ export function BookingWidgetSkeleton() {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLocationObj(locations: Location[], destination: string) {
|
||||||
|
try {
|
||||||
|
const location = locations.find((location) => {
|
||||||
|
if (location.type === "hotels") {
|
||||||
|
return location.operaId === destination
|
||||||
|
} else if (location.type === "cities") {
|
||||||
|
return location.name.toLowerCase() === destination.toLowerCase()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// ignore any errors
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import {
|
import { isBookingWidgetHidden } from "@/lib/trpc/memoizedRequests"
|
||||||
getLocations,
|
|
||||||
isBookingWidgetHidden,
|
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import BookingWidgetClient from "./Client"
|
import BookingWidgetClient from "./Client"
|
||||||
|
|
||||||
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
|
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
|
||||||
|
|
||||||
export function preload() {
|
|
||||||
void getLocations()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function BookingWidget({
|
export default async function BookingWidget({
|
||||||
type,
|
type,
|
||||||
bookingWidgetSearchParams,
|
bookingWidgetSearchParams,
|
||||||
@@ -21,15 +14,8 @@ export default async function BookingWidget({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const locations = await getLocations()
|
|
||||||
|
|
||||||
if (!locations || "error" in locations) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BookingWidgetClient
|
<BookingWidgetClient
|
||||||
locations={locations.data}
|
|
||||||
type={type}
|
type={type}
|
||||||
bookingWidgetSearchParams={bookingWidgetSearchParams}
|
bookingWidgetSearchParams={bookingWidgetSearchParams}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import type {
|
|||||||
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
||||||
|
|
||||||
export const getLocations = cache(async function getMemoizedLocations() {
|
export const getLocations = cache(async function getMemoizedLocations() {
|
||||||
return serverClient().hotel.locations.get()
|
const lang = getLang()
|
||||||
|
return serverClient().hotel.locations.get({ lang })
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getProfile = cache(async function getMemoizedProfile() {
|
export const getProfile = cache(async function getMemoizedProfile() {
|
||||||
|
|||||||
@@ -140,3 +140,7 @@ export const getHotelsByCountryInput = z.object({
|
|||||||
export const getHotelsByCityIdentifierInput = z.object({
|
export const getHotelsByCityIdentifierInput = z.object({
|
||||||
cityIdentifier: z.string(),
|
cityIdentifier: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getLocationsInput = z.object({
|
||||||
|
lang: z.nativeEnum(Lang),
|
||||||
|
})
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
getHotelsByCountryInput,
|
getHotelsByCountryInput,
|
||||||
getHotelsByCSFilterInput,
|
getHotelsByCSFilterInput,
|
||||||
getHotelsByHotelIdsAvailabilityInputSchema,
|
getHotelsByHotelIdsAvailabilityInputSchema,
|
||||||
|
getLocationsInput,
|
||||||
getMeetingRoomsInputSchema,
|
getMeetingRoomsInputSchema,
|
||||||
hotelInputSchema,
|
hotelInputSchema,
|
||||||
hotelsAvailabilityInputSchema,
|
hotelsAvailabilityInputSchema,
|
||||||
@@ -1272,9 +1273,14 @@ export const hotelQueryRouter = router({
|
|||||||
return validateHotelData.data.map((id: string) => parseInt(id, 10))
|
return validateHotelData.data.map((id: string) => parseInt(id, 10))
|
||||||
}),
|
}),
|
||||||
locations: router({
|
locations: router({
|
||||||
get: serviceProcedure.query(async function ({ ctx }) {
|
get: serviceProcedure.input(getLocationsInput).query(async function ({
|
||||||
|
input,
|
||||||
|
ctx,
|
||||||
|
}) {
|
||||||
|
const lang = input.lang ?? ctx.lang
|
||||||
|
|
||||||
const searchParams = new URLSearchParams()
|
const searchParams = new URLSearchParams()
|
||||||
searchParams.set("language", toApiLang(ctx.lang))
|
searchParams.set("language", toApiLang(lang))
|
||||||
|
|
||||||
const options: RequestOptionsWithOutBody = {
|
const options: RequestOptionsWithOutBody = {
|
||||||
// needs to clear default option as only
|
// needs to clear default option as only
|
||||||
@@ -1288,29 +1294,27 @@ export const hotelQueryRouter = router({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const countries = await getCountries(options, searchParams, ctx.lang)
|
const countries = await getCountries(options, searchParams, lang)
|
||||||
if (!countries) {
|
if (!countries) {
|
||||||
return null
|
throw new Error("Unable to fetch countries")
|
||||||
}
|
}
|
||||||
const countryNames = countries.data.map((country) => country.name)
|
const countryNames = countries.data.map((country) => country.name)
|
||||||
const citiesByCountry = await getCitiesByCountry(
|
const citiesByCountry = await getCitiesByCountry(
|
||||||
countryNames,
|
countryNames,
|
||||||
options,
|
options,
|
||||||
searchParams,
|
searchParams,
|
||||||
ctx.lang
|
lang
|
||||||
)
|
)
|
||||||
|
|
||||||
const locations = await getLocations(
|
const locations = await getLocations(
|
||||||
ctx.lang,
|
lang,
|
||||||
options,
|
options,
|
||||||
searchParams,
|
searchParams,
|
||||||
citiesByCountry
|
citiesByCountry
|
||||||
)
|
)
|
||||||
|
|
||||||
if (Array.isArray(locations)) {
|
if (!locations || "error" in locations) {
|
||||||
return {
|
throw new Error("Unable to fetch locations")
|
||||||
data: locations,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return locations
|
return locations
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { VariantProps } from "class-variance-authority"
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
import type { SearchParams } from "@/types/params"
|
import type { SearchParams } from "@/types/params"
|
||||||
import type { Locations } from "@/types/trpc/routers/hotel/locations"
|
|
||||||
import type {
|
import type {
|
||||||
bookingCodeSchema,
|
bookingCodeSchema,
|
||||||
bookingWidgetSchema,
|
bookingWidgetSchema,
|
||||||
@@ -32,7 +31,6 @@ export interface BookingWidgetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BookingWidgetClientProps {
|
export interface BookingWidgetClientProps {
|
||||||
locations: Locations
|
|
||||||
type?: BookingWidgetType
|
type?: BookingWidgetType
|
||||||
bookingWidgetSearchParams: SearchParams<BookingWidgetSearchData>["searchParams"]
|
bookingWidgetSearchParams: SearchParams<BookingWidgetSearchData>["searchParams"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user