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:
Joakim Jäderberg
2025-03-05 13:37:33 +00:00
parent 916912a170
commit f36b90e474
10 changed files with 54 additions and 62 deletions

View File

@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
import { getLocations } from "@/lib/trpc/memoizedRequests"
import { generateChildrenString } from "@/components/HotelReservation/utils"
import { safeTry } from "@/utils/safeTry"
import { convertSearchParamsToObj, type SelectHotelParams } from "@/utils/url"
import type {
@@ -46,13 +47,14 @@ export async function getHotelSearchDetails<
): Promise<HotelSearchDetails<T> | null> {
const selectHotelParams = convertSearchParamsToObj<T>(searchParams)
const locations = await getLocations()
if (!locations || "error" in locations) return null
const [locations, error] = await safeTry(getLocations())
if (!locations || error) {
return null
}
const hotel =
("hotelId" in selectHotelParams &&
(locations.data.find(
(locations.find(
(location) =>
isHotelLocation(location) &&
"operaId" in location &&
@@ -72,7 +74,7 @@ export async function getHotelSearchDetails<
const city =
(typeof cityName === "string" &&
locations.data.find(
locations.find(
(location) => location.name.toLowerCase() === cityName.toLowerCase()
)) ||
null

View File

@@ -1,7 +1,7 @@
import { env } from "@/env/server"
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import BookingWidget, { preload } from "@/components/BookingWidget"
import BookingWidget from "@/components/BookingWidget"
import { getLang } from "@/i18n/serverContext"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
@@ -16,8 +16,6 @@ export default async function BookingWidgetPage({
return null
}
preload()
if (params.contentType === PageContentTypeEnum.hotelPage) {
const hotelPageData = await getHotelPage()

View File

@@ -1,6 +1,6 @@
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 { LangParams, PageArgs } from "@/types/params"
@@ -12,7 +12,5 @@ export default async function BookingWidgetPage({
return null
}
preload()
return <BookingWidget bookingWidgetSearchParams={searchParams} />
}

View File

@@ -1,6 +1,6 @@
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 { PageArgs } from "@/types/params"
@@ -12,7 +12,5 @@ export default async function BookingWidgetPage({
return null
}
preload()
return <BookingWidget bookingWidgetSearchParams={searchParams} />
}

View File

@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { dt } from "@/lib/dt"
import { trpc } from "@/lib/trpc/client"
import { StickyElementNameEnum } from "@/stores/sticky-position"
import Form, {
@@ -12,6 +13,7 @@ import Form, {
} from "@/components/Forms/BookingWidget"
import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
import { CloseLargeIcon } from "@/components/Icons"
import useLang from "@/hooks/useLang"
import useStickyPosition from "@/hooks/useStickyPosition"
import { debounce } from "@/utils/debounce"
import isValidJson from "@/utils/isValidJson"
@@ -31,32 +33,14 @@ import type {
} from "@/types/components/bookingWidget"
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({
locations,
type,
bookingWidgetSearchParams,
}: BookingWidgetClientProps) {
const [isOpen, setIsOpen] = useState(false)
const bookingWidgetRef = useRef(null)
const lang = useLang()
const [locations] = trpc.hotel.locations.get.useSuspenseQuery({ lang })
useStickyPosition({
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
}

View File

@@ -1,16 +1,9 @@
import {
getLocations,
isBookingWidgetHidden,
} from "@/lib/trpc/memoizedRequests"
import { isBookingWidgetHidden } from "@/lib/trpc/memoizedRequests"
import BookingWidgetClient from "./Client"
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
export function preload() {
void getLocations()
}
export default async function BookingWidget({
type,
bookingWidgetSearchParams,
@@ -21,15 +14,8 @@ export default async function BookingWidget({
return null
}
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
return (
<BookingWidgetClient
locations={locations.data}
type={type}
bookingWidgetSearchParams={bookingWidgetSearchParams}
/>

View File

@@ -21,7 +21,8 @@ import type {
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
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() {

View File

@@ -140,3 +140,7 @@ export const getHotelsByCountryInput = z.object({
export const getHotelsByCityIdentifierInput = z.object({
cityIdentifier: z.string(),
})
export const getLocationsInput = z.object({
lang: z.nativeEnum(Lang),
})

View File

@@ -30,6 +30,7 @@ import {
getHotelsByCountryInput,
getHotelsByCSFilterInput,
getHotelsByHotelIdsAvailabilityInputSchema,
getLocationsInput,
getMeetingRoomsInputSchema,
hotelInputSchema,
hotelsAvailabilityInputSchema,
@@ -1272,9 +1273,14 @@ export const hotelQueryRouter = router({
return validateHotelData.data.map((id: string) => parseInt(id, 10))
}),
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()
searchParams.set("language", toApiLang(ctx.lang))
searchParams.set("language", toApiLang(lang))
const options: RequestOptionsWithOutBody = {
// 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) {
return null
throw new Error("Unable to fetch countries")
}
const countryNames = countries.data.map((country) => country.name)
const citiesByCountry = await getCitiesByCountry(
countryNames,
options,
searchParams,
ctx.lang
lang
)
const locations = await getLocations(
ctx.lang,
lang,
options,
searchParams,
citiesByCountry
)
if (Array.isArray(locations)) {
return {
data: locations,
}
if (!locations || "error" in locations) {
throw new Error("Unable to fetch locations")
}
return locations

View File

@@ -2,7 +2,6 @@ import type { VariantProps } from "class-variance-authority"
import type { z } from "zod"
import type { SearchParams } from "@/types/params"
import type { Locations } from "@/types/trpc/routers/hotel/locations"
import type {
bookingCodeSchema,
bookingWidgetSchema,
@@ -32,7 +31,6 @@ export interface BookingWidgetProps {
}
export interface BookingWidgetClientProps {
locations: Locations
type?: BookingWidgetType
bookingWidgetSearchParams: SearchParams<BookingWidgetSearchData>["searchParams"]
}