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 DestinationPageSidePeek from "../Sidepeek"
|
||||||
import StaticMap from "../StaticMap"
|
import StaticMap from "../StaticMap"
|
||||||
import TopImages from "../TopImages"
|
import TopImages from "../TopImages"
|
||||||
|
import CityMap, { preloadHotels } from "./CityMap"
|
||||||
|
|
||||||
import styles from "./destinationCityPage.module.css"
|
import styles from "./destinationCityPage.module.css"
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ export default async function DestinationCityPage() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tracking, destinationCityPage, cityIdentifier } = pageData
|
const { tracking, destinationCityPage, cityIdentifier, city } = pageData
|
||||||
const {
|
const {
|
||||||
blocks,
|
blocks,
|
||||||
images,
|
images,
|
||||||
@@ -41,6 +42,8 @@ export default async function DestinationCityPage() {
|
|||||||
destination_settings,
|
destination_settings,
|
||||||
} = destinationCityPage
|
} = destinationCityPage
|
||||||
|
|
||||||
|
preloadHotels(cityIdentifier)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.pageContainer}>
|
<div className={styles.pageContainer}>
|
||||||
@@ -48,11 +51,7 @@ export default async function DestinationCityPage() {
|
|||||||
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
<Breadcrumbs variant={PageContentTypeEnum.destinationCityPage} />
|
<Breadcrumbs variant={PageContentTypeEnum.destinationCityPage} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{/* TODO: fetch translated city name from API when fetching hotel listing */}
|
<TopImages images={images} destinationName={city.name} />
|
||||||
<TopImages
|
|
||||||
images={images}
|
|
||||||
destinationName={destination_settings.city}
|
|
||||||
/>
|
|
||||||
</header>
|
</header>
|
||||||
<main className={styles.mainContent}>
|
<main className={styles.mainContent}>
|
||||||
<Suspense fallback={<HotelListingSkeleton />}>
|
<Suspense fallback={<HotelListingSkeleton />}>
|
||||||
@@ -78,6 +77,7 @@ export default async function DestinationCityPage() {
|
|||||||
</SidebarContentWrapper>
|
</SidebarContentWrapper>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
<CityMap city={city} cityIdentifier={cityIdentifier} />
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<TrackingSDK pageData={tracking} />
|
<TrackingSDK pageData={tracking} />
|
||||||
</Suspense>
|
</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"
|
import HotelListingClient from "./Client"
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ interface HotelListingProps {
|
|||||||
export default async function HotelListing({
|
export default async function HotelListing({
|
||||||
cityIdentifier,
|
cityIdentifier,
|
||||||
}: HotelListingProps) {
|
}: HotelListingProps) {
|
||||||
const hotels = await getHotelListDataByCityIdentifier(cityIdentifier)
|
const hotels = await getHotelsByCityIdentifier(cityIdentifier)
|
||||||
|
|
||||||
if (!hotels.length) {
|
if (!hotels.length) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ export default function DynamicMap({
|
|||||||
|
|
||||||
function zoomIn() {
|
function zoomIn() {
|
||||||
const currentZoom = map && map.getZoom()
|
const currentZoom = map && map.getZoom()
|
||||||
console.log(currentZoom)
|
|
||||||
if (currentZoom) {
|
if (currentZoom) {
|
||||||
map.setZoom(currentZoom + 1)
|
map.setZoom(currentZoom + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function zoomOut() {
|
function zoomOut() {
|
||||||
const currentZoom = map && map.getZoom()
|
const currentZoom = map && map.getZoom()
|
||||||
console.log(currentZoom)
|
|
||||||
if (currentZoom) {
|
if (currentZoom) {
|
||||||
map.setZoom(currentZoom - 1)
|
map.setZoom(currentZoom - 1)
|
||||||
}
|
}
|
||||||
@@ -65,6 +63,7 @@ export default function DynamicMap({
|
|||||||
const mapOptions: MapProps = {
|
const mapOptions: MapProps = {
|
||||||
defaultCenter: markers[0].coordinates, // Default center will be overridden by the bounds
|
defaultCenter: markers[0].coordinates, // Default center will be overridden by the bounds
|
||||||
minZoom: 3,
|
minZoom: 3,
|
||||||
|
maxZoom: 18,
|
||||||
defaultZoom: 8,
|
defaultZoom: 8,
|
||||||
disableDefaultUI: true,
|
disableDefaultUI: true,
|
||||||
clickableIcons: false,
|
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(
|
export const getDestinationCityPage = cache(
|
||||||
async function getMemoizedDestinationCityPage() {
|
async function getMemoizedDestinationCityPage() {
|
||||||
return serverClient().contentstack.destinationCityPage.get()
|
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() {
|
export const getStartPage = cache(async function getMemoizedStartPage() {
|
||||||
return serverClient().contentstack.startPage.get()
|
return serverClient().contentstack.startPage.get()
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
|
|||||||
|
|
||||||
import { generateTag } from "@/utils/generateTag"
|
import { generateTag } from "@/utils/generateTag"
|
||||||
|
|
||||||
import { getHotelListData } from "../../hotels/utils"
|
import { getCityByCityIdentifier } from "../../hotels/utils"
|
||||||
import { getHotelListDataInput } from "./input"
|
|
||||||
import {
|
import {
|
||||||
destinationCityPageRefsSchema,
|
destinationCityPageRefsSchema,
|
||||||
destinationCityPageSchema,
|
destinationCityPageSchema,
|
||||||
@@ -148,6 +147,27 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
)
|
)
|
||||||
return null
|
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}` })
|
getDestinationCityPageSuccessCounter.add(1, { lang, uid: `${uid}` })
|
||||||
console.info(
|
console.info(
|
||||||
@@ -159,18 +179,8 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...validatedResponse.data,
|
...validatedResponse.data,
|
||||||
cityIdentifier:
|
cityIdentifier,
|
||||||
validatedResponse.data.destinationCityPage.destination_settings.city,
|
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({
|
export const getHotelsByCountryInput = z.object({
|
||||||
country: z.nativeEnum(Country),
|
country: z.nativeEnum(Country),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getHotelsByCityIdentifierInput = z.object({
|
||||||
|
cityIdentifier: z.string(),
|
||||||
|
})
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
breakfastPackageInputSchema,
|
breakfastPackageInputSchema,
|
||||||
cityCoordinatesInputSchema,
|
cityCoordinatesInputSchema,
|
||||||
getAdditionalDataInputSchema,
|
getAdditionalDataInputSchema,
|
||||||
|
getHotelsByCityIdentifierInput,
|
||||||
getHotelsByCountryInput,
|
getHotelsByCountryInput,
|
||||||
getHotelsByCSFilterInput,
|
getHotelsByCSFilterInput,
|
||||||
getHotelsByHotelIdsAvailabilityInputSchema,
|
getHotelsByHotelIdsAvailabilityInputSchema,
|
||||||
@@ -51,7 +52,9 @@ import {
|
|||||||
getCitiesByCountry,
|
getCitiesByCountry,
|
||||||
getCountries,
|
getCountries,
|
||||||
getHotelIdsByCityId,
|
getHotelIdsByCityId,
|
||||||
|
getHotelIdsByCityIdentifier,
|
||||||
getHotelIdsByCountry,
|
getHotelIdsByCountry,
|
||||||
|
getHotelsByHotelIds,
|
||||||
getLocations,
|
getLocations,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
|
|
||||||
@@ -861,21 +864,22 @@ export const hotelQueryRouter = router({
|
|||||||
hotelIdsParams
|
hotelIdsParams
|
||||||
)
|
)
|
||||||
|
|
||||||
const hotels = await Promise.all(
|
return await getHotelsByHotelIds(hotelIds, lang, serviceToken)
|
||||||
hotelIds.map(async (hotelId) => {
|
}),
|
||||||
const [hotelData, url] = await Promise.all([
|
}),
|
||||||
getHotel(
|
byCityIdentifier: router({
|
||||||
{ hotelId, isCardOnlyPayment: false, language: lang },
|
get: contentStackBaseWithServiceProcedure
|
||||||
ctx.serviceToken
|
.input(getHotelsByCityIdentifierInput)
|
||||||
),
|
.query(async ({ ctx, input }) => {
|
||||||
getHotelPageUrl(lang, hotelId),
|
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({
|
byCSFilter: router({
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { getHotel } from "./query"
|
|||||||
import type { Country } from "@/types/enums/country"
|
import type { Country } from "@/types/enums/country"
|
||||||
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
||||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||||
import type { HotelData } from "@/types/hotel"
|
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||||
import type {
|
import type {
|
||||||
CitiesGroupedByCountry,
|
CitiesGroupedByCountry,
|
||||||
CityLocation,
|
CityLocation,
|
||||||
@@ -423,15 +423,15 @@ export async function getHotelIdsByCityIdentifier(
|
|||||||
serviceToken: string
|
serviceToken: string
|
||||||
) {
|
) {
|
||||||
const apiLang = toApiLang(Lang.en)
|
const apiLang = toApiLang(Lang.en)
|
||||||
const cityId = await getCityIdByCityIdentifier(cityIdentifier, serviceToken)
|
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
|
||||||
|
|
||||||
if (!cityId) {
|
if (!city) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const hotelIdsParams = new URLSearchParams({
|
const hotelIdsParams = new URLSearchParams({
|
||||||
language: apiLang,
|
language: apiLang,
|
||||||
city: cityId,
|
city: city.id,
|
||||||
})
|
})
|
||||||
const options: RequestOptionsWithOutBody = {
|
const options: RequestOptionsWithOutBody = {
|
||||||
// needs to clear default option as only
|
// needs to clear default option as only
|
||||||
@@ -444,11 +444,11 @@ export async function getHotelIdsByCityIdentifier(
|
|||||||
revalidate: env.CACHE_TIME_HOTELS,
|
revalidate: env.CACHE_TIME_HOTELS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const hotelIds = await getHotelIdsByCityId(cityId, options, hotelIdsParams)
|
const hotelIds = await getHotelIdsByCityId(city.id, options, hotelIdsParams)
|
||||||
return hotelIds
|
return hotelIds
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCityIdByCityIdentifier(
|
export async function getCityByCityIdentifier(
|
||||||
cityIdentifier: string,
|
cityIdentifier: string,
|
||||||
serviceToken: string
|
serviceToken: string
|
||||||
) {
|
) {
|
||||||
@@ -473,23 +473,18 @@ export async function getCityIdByCityIdentifier(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const cityId = locations
|
const city = locations
|
||||||
.filter((loc): loc is CityLocation => loc.type === "cities")
|
.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,
|
lang: Lang,
|
||||||
serviceToken: string,
|
serviceToken: string
|
||||||
cityIdentifier: string
|
|
||||||
) {
|
) {
|
||||||
const hotelIds = await getHotelIdsByCityIdentifier(
|
|
||||||
cityIdentifier,
|
|
||||||
serviceToken
|
|
||||||
)
|
|
||||||
|
|
||||||
const hotels = await Promise.all(
|
const hotels = await Promise.all(
|
||||||
hotelIds.map(async (hotelId) => {
|
hotelIds.map(async (hotelId) => {
|
||||||
const [hotelData, url] = await Promise.all([
|
const [hotelData, url] = await Promise.all([
|
||||||
@@ -504,7 +499,5 @@ export async function getHotelListData(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return hotels.filter(
|
return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel)
|
||||||
(hotel): hotel is HotelData & { url: string | null } => !!hotel
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user