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:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user