Merged in feat/SW-1457-city-dynamic-map (pull request #1320)

feat(SW-1457): Added map and fetching hotels by cityIdentifier

* feat(SW-1457): Added map and fetching hotels by cityIdentifier


Approved-by: Fredrik Thorsson
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-02-12 13:02:19 +00:00
parent 019a5db549
commit 1532898c23
10 changed files with 106 additions and 88 deletions

View File

@@ -0,0 +1,33 @@
import { env } from "@/env/server"
import { getHotelsByCityIdentifier } from "@/lib/trpc/memoizedRequests"
import Title from "@/components/TempDesignSystem/Text/Title"
import Map from "../../Map"
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
interface CityMapProps {
city: CityLocation
cityIdentifier: string
}
export function preloadHotels(cityIdentifier: string) {
void getHotelsByCityIdentifier(cityIdentifier)
}
export default async function CityMap({ city, cityIdentifier }: CityMapProps) {
const hotels = await getHotelsByCityIdentifier(cityIdentifier)
return (
<Map
hotels={hotels}
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
apiKey={env.GOOGLE_STATIC_MAP_KEY}
>
<div>
<Title level="h2" as="h3">
{city.name}
</Title>
</div>
</Map>
)
}

View File

@@ -16,6 +16,7 @@ import SidebarContentWrapper from "../SidebarContentWrapper"
import DestinationPageSidePeek from "../Sidepeek"
import StaticMap from "../StaticMap"
import TopImages from "../TopImages"
import CityMap, { preloadHotels } from "./CityMap"
import styles from "./destinationCityPage.module.css"
@@ -28,7 +29,7 @@ export default async function DestinationCityPage() {
return null
}
const { tracking, destinationCityPage, cityIdentifier } = pageData
const { tracking, destinationCityPage, cityIdentifier, city } = pageData
const {
blocks,
images,
@@ -41,6 +42,8 @@ export default async function DestinationCityPage() {
destination_settings,
} = destinationCityPage
preloadHotels(cityIdentifier)
return (
<>
<div className={styles.pageContainer}>
@@ -48,11 +51,7 @@ export default async function DestinationCityPage() {
<Suspense fallback={<BreadcrumbsSkeleton />}>
<Breadcrumbs variant={PageContentTypeEnum.destinationCityPage} />
</Suspense>
{/* TODO: fetch translated city name from API when fetching hotel listing */}
<TopImages
images={images}
destinationName={destination_settings.city}
/>
<TopImages images={images} destinationName={city.name} />
</header>
<main className={styles.mainContent}>
<Suspense fallback={<HotelListingSkeleton />}>
@@ -78,6 +77,7 @@ export default async function DestinationCityPage() {
</SidebarContentWrapper>
</aside>
</div>
<CityMap city={city} cityIdentifier={cityIdentifier} />
<Suspense fallback={null}>
<TrackingSDK pageData={tracking} />
</Suspense>

View File

@@ -1,23 +0,0 @@
.countryMap {
--destination-map-height: 100dvh;
position: absolute;
top: 0;
left: 0;
height: var(--destination-map-height);
width: 100dvw;
z-index: var(--hotel-dynamic-map-z-index);
display: flex;
background-color: var(--Base-Surface-Primary-light-Normal);
}
.wrapper {
position: absolute;
top: 0;
left: 0;
}
.closeButton {
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Spacing-x-half);
}

View File

@@ -1,4 +1,4 @@
import { getHotelListDataByCityIdentifier } from "@/lib/trpc/memoizedRequests"
import { getHotelsByCityIdentifier } from "@/lib/trpc/memoizedRequests"
import HotelListingClient from "./Client"
@@ -9,7 +9,7 @@ interface HotelListingProps {
export default async function HotelListing({
cityIdentifier,
}: HotelListingProps) {
const hotels = await getHotelListDataByCityIdentifier(cityIdentifier)
const hotels = await getHotelsByCityIdentifier(cityIdentifier)
if (!hotels.length) {
return null

View File

@@ -49,14 +49,12 @@ export default function DynamicMap({
function zoomIn() {
const currentZoom = map && map.getZoom()
console.log(currentZoom)
if (currentZoom) {
map.setZoom(currentZoom + 1)
}
}
function zoomOut() {
const currentZoom = map && map.getZoom()
console.log(currentZoom)
if (currentZoom) {
map.setZoom(currentZoom - 1)
}
@@ -65,6 +63,7 @@ export default function DynamicMap({
const mapOptions: MapProps = {
defaultCenter: markers[0].coordinates, // Default center will be overridden by the bounds
minZoom: 3,
maxZoom: 18,
defaultZoom: 8,
disableDefaultUI: true,
clickableIcons: false,

View File

@@ -213,20 +213,18 @@ export const getHotelsByCountry = cache(
})
}
)
export const getHotelsByCityIdentifier = cache(
async function getMemoizedHotelsByCityIdentifier(cityIdentifier: string) {
return serverClient().hotel.hotels.byCityIdentifier.get({
cityIdentifier,
})
}
)
export const getDestinationCityPage = cache(
async function getMemoizedDestinationCityPage() {
return serverClient().contentstack.destinationCityPage.get()
}
)
export const getHotelListDataByCityIdentifier = cache(
async function getMemoizedHotelListDataByCityIdentifier(
cityIdentifier: string
) {
return serverClient().contentstack.destinationCityPage.hotelList({
cityIdentifier,
})
}
)
export const getStartPage = cache(async function getMemoizedStartPage() {
return serverClient().contentstack.startPage.get()

View File

@@ -8,8 +8,7 @@ import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { getHotelListData } from "../../hotels/utils"
import { getHotelListDataInput } from "./input"
import { getCityByCityIdentifier } from "../../hotels/utils"
import {
destinationCityPageRefsSchema,
destinationCityPageSchema,
@@ -148,6 +147,27 @@ export const destinationCityPageQueryRouter = router({
)
return null
}
const cityIdentifier =
validatedResponse.data.destinationCityPage.destination_settings.city
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
if (!city) {
getDestinationCityPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`,
})
console.error(
"contentstack.destinationCityPage not found error",
JSON.stringify({
query: { lang, uid },
error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`,
})
)
return null
}
getDestinationCityPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
@@ -159,18 +179,8 @@ export const destinationCityPageQueryRouter = router({
return {
...validatedResponse.data,
cityIdentifier:
validatedResponse.data.destinationCityPage.destination_settings.city,
cityIdentifier,
city,
}
}),
hotelList: contentStackUidWithServiceProcedure
.input(getHotelListDataInput)
.query(async ({ ctx, input }) => {
const { lang, serviceToken } = ctx
const { cityIdentifier } = input
const hotels = await getHotelListData(lang, serviceToken, cityIdentifier)
return hotels
}),
})

View File

@@ -121,3 +121,7 @@ export const getAdditionalDataInputSchema = z.object({
export const getHotelsByCountryInput = z.object({
country: z.nativeEnum(Country),
})
export const getHotelsByCityIdentifierInput = z.object({
cityIdentifier: z.string(),
})

View File

@@ -24,6 +24,7 @@ import {
breakfastPackageInputSchema,
cityCoordinatesInputSchema,
getAdditionalDataInputSchema,
getHotelsByCityIdentifierInput,
getHotelsByCountryInput,
getHotelsByCSFilterInput,
getHotelsByHotelIdsAvailabilityInputSchema,
@@ -51,7 +52,9 @@ import {
getCitiesByCountry,
getCountries,
getHotelIdsByCityId,
getHotelIdsByCityIdentifier,
getHotelIdsByCountry,
getHotelsByHotelIds,
getLocations,
} from "./utils"
@@ -861,21 +864,22 @@ export const hotelQueryRouter = router({
hotelIdsParams
)
const hotels = await Promise.all(
hotelIds.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([
getHotel(
{ hotelId, isCardOnlyPayment: false, language: lang },
ctx.serviceToken
),
getHotelPageUrl(lang, hotelId),
])
return await getHotelsByHotelIds(hotelIds, lang, serviceToken)
}),
}),
byCityIdentifier: router({
get: contentStackBaseWithServiceProcedure
.input(getHotelsByCityIdentifierInput)
.query(async ({ ctx, input }) => {
const { lang, serviceToken } = ctx
const { cityIdentifier } = input
return hotelData ? { ...hotelData, url } : null
})
const hotelIds = await getHotelIdsByCityIdentifier(
cityIdentifier,
serviceToken
)
return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel)
return await getHotelsByHotelIds(hotelIds, lang, serviceToken)
}),
}),
byCSFilter: router({

View File

@@ -20,7 +20,7 @@ import { getHotel } from "./query"
import type { Country } from "@/types/enums/country"
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
import type { HotelData } from "@/types/hotel"
import type { HotelDataWithUrl } from "@/types/hotel"
import type {
CitiesGroupedByCountry,
CityLocation,
@@ -423,15 +423,15 @@ export async function getHotelIdsByCityIdentifier(
serviceToken: string
) {
const apiLang = toApiLang(Lang.en)
const cityId = await getCityIdByCityIdentifier(cityIdentifier, serviceToken)
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
if (!cityId) {
if (!city) {
return []
}
const hotelIdsParams = new URLSearchParams({
language: apiLang,
city: cityId,
city: city.id,
})
const options: RequestOptionsWithOutBody = {
// needs to clear default option as only
@@ -444,11 +444,11 @@ export async function getHotelIdsByCityIdentifier(
revalidate: env.CACHE_TIME_HOTELS,
},
}
const hotelIds = await getHotelIdsByCityId(cityId, options, hotelIdsParams)
const hotelIds = await getHotelIdsByCityId(city.id, options, hotelIdsParams)
return hotelIds
}
export async function getCityIdByCityIdentifier(
export async function getCityByCityIdentifier(
cityIdentifier: string,
serviceToken: string
) {
@@ -473,23 +473,18 @@ export async function getCityIdByCityIdentifier(
return null
}
const cityId = locations
const city = locations
.filter((loc): loc is CityLocation => loc.type === "cities")
.find((loc) => loc.cityIdentifier === cityIdentifier)?.id
.find((loc) => loc.cityIdentifier === cityIdentifier)
return cityId ?? null
return city ?? null
}
export async function getHotelListData(
export async function getHotelsByHotelIds(
hotelIds: string[],
lang: Lang,
serviceToken: string,
cityIdentifier: string
serviceToken: string
) {
const hotelIds = await getHotelIdsByCityIdentifier(
cityIdentifier,
serviceToken
)
const hotels = await Promise.all(
hotelIds.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([
@@ -504,7 +499,5 @@ export async function getHotelListData(
})
)
return hotels.filter(
(hotel): hotel is HotelData & { url: string | null } => !!hotel
)
return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel)
}