fix: improve loading on destination overview page
- Only load data from Contentstack - Use static JSON for destination list - Some logic improvements to data handling and types
This commit is contained in:
@@ -12,7 +12,6 @@ import DestinationCityPage from "@/components/ContentType/DestinationPage/Destin
|
|||||||
import DestinationCityPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCityPage/DestinationCityPageSkeleton"
|
import DestinationCityPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCityPage/DestinationCityPageSkeleton"
|
||||||
import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage"
|
import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage"
|
||||||
import DestinationCountryPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCountryPage/DestinationCountryPageSkeleton"
|
import DestinationCountryPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCountryPage/DestinationCountryPageSkeleton"
|
||||||
import DestinationOverviewPage from "@/components/ContentType/DestinationPage/DestinationOverviewPage"
|
|
||||||
import HotelPage from "@/components/ContentType/HotelPage"
|
import HotelPage from "@/components/ContentType/HotelPage"
|
||||||
import HotelSubpage from "@/components/ContentType/HotelSubpage"
|
import HotelSubpage from "@/components/ContentType/HotelSubpage"
|
||||||
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
||||||
@@ -23,8 +22,8 @@ import { getLang } from "@/i18n/serverContext"
|
|||||||
import { isValidSession } from "@/utils/session"
|
import { isValidSession } from "@/utils/session"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ContentTypeParams,
|
|
||||||
LangParams,
|
LangParams,
|
||||||
|
NonAppRouterContentTypeParams,
|
||||||
PageArgs,
|
PageArgs,
|
||||||
UIDParams,
|
UIDParams,
|
||||||
} from "@/types/params"
|
} from "@/types/params"
|
||||||
@@ -36,7 +35,7 @@ export default async function ContentTypePage({
|
|||||||
params,
|
params,
|
||||||
searchParams,
|
searchParams,
|
||||||
}: PageArgs<
|
}: PageArgs<
|
||||||
LangParams & ContentTypeParams & UIDParams,
|
LangParams & NonAppRouterContentTypeParams & UIDParams,
|
||||||
{ subpage?: string; filterFromUrl?: string }
|
{ subpage?: string; filterFromUrl?: string }
|
||||||
>) {
|
>) {
|
||||||
const pathname = headers().get("x-pathname") || ""
|
const pathname = headers().get("x-pathname") || ""
|
||||||
@@ -62,11 +61,6 @@ export default async function ContentTypePage({
|
|||||||
}
|
}
|
||||||
case PageContentTypeEnum.loyaltyPage:
|
case PageContentTypeEnum.loyaltyPage:
|
||||||
return <LoyaltyPage />
|
return <LoyaltyPage />
|
||||||
case PageContentTypeEnum.destinationOverviewPage:
|
|
||||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
return <DestinationOverviewPage />
|
|
||||||
case PageContentTypeEnum.destinationCountryPage:
|
case PageContentTypeEnum.destinationCountryPage:
|
||||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||||
return notFound()
|
return notFound()
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export default function DestinationOverviewPageBreadcrumbs() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "../../../[contentType]/[uid]/@preview/loading"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "../../../[contentType]/[uid]/@preview/page"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/nextjs"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { DestinationOverviewPageError } from "@/components/ContentType/DestinationPage/DestinationOverviewPage/error"
|
||||||
|
|
||||||
|
export default function Error({
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string }
|
||||||
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error) return
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
Sentry.captureException(error)
|
||||||
|
}, [error])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DestinationOverviewPageError>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "An error occurred ({errorId})" },
|
||||||
|
{
|
||||||
|
errorId: `${error.digest}@${Date.now()}`,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
</DestinationOverviewPageError>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "../../[contentType]/[uid]/layout"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { DestinationOverviewPageLoading as default } from "@/components/ContentType/DestinationPage/DestinationOverviewPage"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
import DestinationOverviewPage from "@/components/ContentType/DestinationPage/DestinationOverviewPage"
|
||||||
|
|
||||||
|
export { generateMetadata } from "@/utils/generateMetadata"
|
||||||
|
|
||||||
|
export default function DestinationOverviewPagePage() {
|
||||||
|
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DestinationOverviewPage />
|
||||||
|
}
|
||||||
+17
-15
@@ -27,21 +27,23 @@ export default async function Destination({
|
|||||||
<AccordionItem title={country} subtitle={accordionSubtitle}>
|
<AccordionItem title={country} subtitle={accordionSubtitle}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<ul className={styles.citiesList}>
|
<ul className={styles.citiesList}>
|
||||||
{cities.map((city) => (
|
{cities.map((city) =>
|
||||||
<li key={city.id}>
|
city.hotelCount > 0 ? (
|
||||||
{city.url ? (
|
<li key={city.id}>
|
||||||
<Link
|
{city.url ? (
|
||||||
href={city.url}
|
<Link
|
||||||
color="baseTextMediumContrast"
|
href={city.url}
|
||||||
textDecoration="underline"
|
color="baseTextMediumContrast"
|
||||||
>
|
textDecoration="underline"
|
||||||
{`${city.name} (${city.hotelCount})`}
|
>
|
||||||
</Link>
|
{`${city.name} (${city.hotelCount})`}
|
||||||
) : (
|
</Link>
|
||||||
<Body>{`${city.name} (${city.hotelCount})`}</Body>
|
) : (
|
||||||
)}
|
<Body>{`${city.name} (${city.hotelCount})`}</Body>
|
||||||
</li>
|
)}
|
||||||
))}
|
</li>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
{countryUrl && (
|
{countryUrl && (
|
||||||
<Link href={countryUrl} variant="icon" color="burgundy" weight="bold">
|
<Link href={countryUrl} variant="icon" color="burgundy" weight="bold">
|
||||||
|
|||||||
+23
-21
@@ -7,9 +7,7 @@ import styles from "./destinationsList.module.css"
|
|||||||
|
|
||||||
import type { DestinationsListProps } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
|
import type { DestinationsListProps } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
|
||||||
|
|
||||||
export default function DestinationsList({
|
export function DestinationsList({ destinations }: DestinationsListProps) {
|
||||||
destinations,
|
|
||||||
}: DestinationsListProps) {
|
|
||||||
const middleIndex = Math.ceil(destinations.length / 2)
|
const middleIndex = Math.ceil(destinations.length / 2)
|
||||||
const accordionLeft = destinations.slice(0, middleIndex)
|
const accordionLeft = destinations.slice(0, middleIndex)
|
||||||
const accordionRight = destinations.slice(middleIndex)
|
const accordionRight = destinations.slice(middleIndex)
|
||||||
@@ -17,27 +15,31 @@ export default function DestinationsList({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.listContainer}>
|
<div className={styles.listContainer}>
|
||||||
<Accordion className={styles.accordion}>
|
<Accordion className={styles.accordion}>
|
||||||
{accordionLeft.map((data) => (
|
{accordionLeft.map((data) =>
|
||||||
<Destination
|
data.numberOfHotels > 0 ? (
|
||||||
key={data.country}
|
<Destination
|
||||||
country={data.country}
|
key={data.country}
|
||||||
countryUrl={data.countryUrl}
|
country={data.country}
|
||||||
numberOfHotels={data.numberOfHotels}
|
countryUrl={data.countryUrl}
|
||||||
cities={data.cities}
|
numberOfHotels={data.numberOfHotels}
|
||||||
/>
|
cities={data.cities}
|
||||||
))}
|
/>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Divider color="subtle" className={styles.divider} />
|
<Divider color="subtle" className={styles.divider} />
|
||||||
<Accordion className={styles.accordion}>
|
<Accordion className={styles.accordion}>
|
||||||
{accordionRight.map((data) => (
|
{accordionRight.map((data) =>
|
||||||
<Destination
|
data.numberOfHotels > 0 ? (
|
||||||
key={data.country}
|
<Destination
|
||||||
country={data.country}
|
key={data.country}
|
||||||
countryUrl={data.countryUrl}
|
country={data.country}
|
||||||
numberOfHotels={data.numberOfHotels}
|
countryUrl={data.countryUrl}
|
||||||
cities={data.cities}
|
numberOfHotels={data.numberOfHotels}
|
||||||
/>
|
cities={data.cities}
|
||||||
))}
|
/>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-6
@@ -1,16 +1,19 @@
|
|||||||
|
import { getDestinationsList } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import DestinationsList from "./DestinationsList"
|
import { DestinationsList } from "./DestinationsList"
|
||||||
|
|
||||||
import styles from "./hotelsSection.module.css"
|
import styles from "./hotelsSection.module.css"
|
||||||
|
|
||||||
import type { HotelsSectionProps } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
|
export default async function HotelsSection() {
|
||||||
|
|
||||||
export default async function HotelsSection({
|
|
||||||
destinations,
|
|
||||||
}: HotelsSectionProps) {
|
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
const destinations = await getDestinationsList()
|
||||||
|
|
||||||
|
if (destinations.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
|
|||||||
+2
-20
@@ -6,17 +6,8 @@ import MapContent from "../../Map/MapContent"
|
|||||||
import MapProvider from "../../Map/MapProvider"
|
import MapProvider from "../../Map/MapProvider"
|
||||||
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
||||||
import ActiveMapCard from "./ActiveMapCard"
|
import ActiveMapCard from "./ActiveMapCard"
|
||||||
import InputForm from "./InputForm"
|
|
||||||
|
|
||||||
import type { MapLocation } from "@/types/components/mapLocation"
|
export default async function OverviewMapContainer() {
|
||||||
|
|
||||||
interface OverviewMapContainerProps {
|
|
||||||
defaultLocation: MapLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function OverviewMapContainer({
|
|
||||||
defaultLocation,
|
|
||||||
}: OverviewMapContainerProps) {
|
|
||||||
const hotelData = await getAllHotels()
|
const hotelData = await getAllHotels()
|
||||||
|
|
||||||
if (!hotelData) {
|
if (!hotelData) {
|
||||||
@@ -28,22 +19,13 @@ export default async function OverviewMapContainer({
|
|||||||
|
|
||||||
const markers = getHotelMapMarkers(hotelData)
|
const markers = getHotelMapMarkers(hotelData)
|
||||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||||
const defaultCoordinates = defaultLocation
|
|
||||||
? {
|
|
||||||
lat: defaultLocation.latitude,
|
|
||||||
lng: defaultLocation.longitude,
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
const defaultZoom = defaultLocation?.default_zoom ?? 3
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapProvider apiKey={googleMapsApiKey} pageType="overview">
|
<MapProvider apiKey={googleMapsApiKey} pageType="overview">
|
||||||
<InputForm />
|
|
||||||
<DynamicMap
|
<DynamicMap
|
||||||
mapId={googleMapId}
|
mapId={googleMapId}
|
||||||
markers={markers}
|
markers={markers}
|
||||||
defaultCoordinates={defaultCoordinates}
|
fitBounds
|
||||||
defaultZoom={defaultZoom}
|
|
||||||
gestureHandling="cooperative"
|
gestureHandling="cooperative"
|
||||||
>
|
>
|
||||||
<MapContent geojson={geoJson} />
|
<MapContent geojson={geoJson} />
|
||||||
|
|||||||
+12
-2
@@ -2,11 +2,21 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-width);
|
height: 610px;
|
||||||
height: 700px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.mapContainer {
|
||||||
|
height: 580px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.mapContainer {
|
||||||
|
height: 560px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x9);
|
gap: var(--Spacing-x9);
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import styles from "./destinationOverviewPage.module.css"
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from "react"
|
||||||
|
|
||||||
|
export function DestinationOverviewPageError({ children }: PropsWithChildren) {
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.blocks}>{children}</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
+25
-16
@@ -1,9 +1,7 @@
|
|||||||
import {
|
import { getDestinationOverviewPage } from "@/lib/trpc/memoizedRequests"
|
||||||
getDestinationOverviewPage,
|
|
||||||
getDestinationsList,
|
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import Blocks from "@/components/Blocks"
|
import Blocks from "@/components/Blocks"
|
||||||
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
|
||||||
import HotelsSection from "./HotelsSection"
|
import HotelsSection from "./HotelsSection"
|
||||||
@@ -12,10 +10,7 @@ import OverviewMapContainer from "./OverviewMapContainer"
|
|||||||
import styles from "./destinationOverviewPage.module.css"
|
import styles from "./destinationOverviewPage.module.css"
|
||||||
|
|
||||||
export default async function DestinationOverviewPage() {
|
export default async function DestinationOverviewPage() {
|
||||||
const [pageData, destinationsData] = await Promise.all([
|
const pageData = await getDestinationOverviewPage()
|
||||||
getDestinationOverviewPage(),
|
|
||||||
getDestinationsList(),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!pageData) {
|
if (!pageData) {
|
||||||
return null
|
return null
|
||||||
@@ -26,21 +21,35 @@ export default async function DestinationOverviewPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.mapContainer}>
|
<div className={styles.mapContainer}>
|
||||||
<OverviewMapContainer
|
<OverviewMapContainer />
|
||||||
defaultLocation={destinationOverviewPage.location}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div className={styles.blocks}>
|
<div className={styles.blocks}>
|
||||||
<Blocks blocks={destinationOverviewPage.blocks} />
|
<Blocks blocks={destinationOverviewPage.blocks} />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{destinationsData && (
|
<aside className={styles.hotelsAccordions}>
|
||||||
<aside className={styles.hotelsAccordions}>
|
<HotelsSection />
|
||||||
<HotelsSection destinations={destinationsData} />
|
</aside>
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
<TrackingSDK pageData={tracking} />
|
<TrackingSDK pageData={tracking} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DestinationOverviewPageLoading() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.mapContainer}>
|
||||||
|
<SkeletonShimmer width={"100%"} height={"100%"} />
|
||||||
|
</div>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.blocks}>
|
||||||
|
<SkeletonShimmer width={"100%"} height={"100%"} />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<aside className={styles.hotelsAccordions}>
|
||||||
|
<SkeletonShimmer width={"100%"} height={"100%"} />
|
||||||
|
</aside>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapWrapper::after {
|
.mapWrapperWithCloseButton:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import "client-only"
|
import "client-only"
|
||||||
|
|
||||||
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
|
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
|
||||||
|
import { cx } from "class-variance-authority"
|
||||||
import { type PropsWithChildren, useEffect, useRef } from "react"
|
import { type PropsWithChildren, useEffect, useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -28,8 +29,8 @@ const BACKUP_COORDINATES = {
|
|||||||
interface DynamicMapProps {
|
interface DynamicMapProps {
|
||||||
markers: DestinationMarker[]
|
markers: DestinationMarker[]
|
||||||
mapId: string
|
mapId: string
|
||||||
defaultCoordinates: google.maps.LatLngLiteral | null
|
defaultCenter?: google.maps.LatLngLiteral
|
||||||
defaultZoom: number
|
defaultZoom?: number
|
||||||
fitBounds?: boolean
|
fitBounds?: boolean
|
||||||
gestureHandling?: "greedy" | "cooperative" | "auto" | "none"
|
gestureHandling?: "greedy" | "cooperative" | "auto" | "none"
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
@@ -38,8 +39,8 @@ interface DynamicMapProps {
|
|||||||
export default function DynamicMap({
|
export default function DynamicMap({
|
||||||
markers,
|
markers,
|
||||||
mapId,
|
mapId,
|
||||||
defaultCoordinates,
|
defaultCenter = BACKUP_COORDINATES,
|
||||||
defaultZoom,
|
defaultZoom = 3,
|
||||||
fitBounds = true,
|
fitBounds = true,
|
||||||
onClose,
|
onClose,
|
||||||
gestureHandling = "auto",
|
gestureHandling = "auto",
|
||||||
@@ -61,18 +62,14 @@ export default function DynamicMap({
|
|||||||
}, [activeMarker, pageType])
|
}, [activeMarker, pageType])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (map && fitBounds) {
|
if (map && fitBounds && markers?.length) {
|
||||||
if (markers.length) {
|
const bounds = new google.maps.LatLngBounds()
|
||||||
const bounds = new google.maps.LatLngBounds()
|
markers.forEach((marker) => {
|
||||||
markers.forEach((marker) => {
|
bounds.extend(marker.coordinates)
|
||||||
bounds.extend(marker.coordinates)
|
})
|
||||||
})
|
map.fitBounds(bounds, 100)
|
||||||
map.fitBounds(bounds, 100)
|
|
||||||
} else if (defaultCoordinates) {
|
|
||||||
map.setCenter(defaultCoordinates)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [map, markers, defaultCoordinates, fitBounds])
|
}, [map, fitBounds, markers])
|
||||||
|
|
||||||
useHandleKeyUp((event: KeyboardEvent) => {
|
useHandleKeyUp((event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && onClose) {
|
if (event.key === "Escape" && onClose) {
|
||||||
@@ -94,7 +91,7 @@ export default function DynamicMap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapOptions: MapProps = {
|
const mapOptions: MapProps = {
|
||||||
defaultCenter: defaultCoordinates || BACKUP_COORDINATES, // Default center will be overridden by the bounds
|
defaultCenter, // Default center will be overridden by the bounds
|
||||||
minZoom: 3,
|
minZoom: 3,
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
defaultZoom,
|
defaultZoom,
|
||||||
@@ -105,7 +102,13 @@ export default function DynamicMap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.mapWrapper} ref={ref}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
styles.mapWrapper,
|
||||||
|
onClose && styles.mapWrapperWithCloseButton
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
<ErrorBoundary fallback={<h2>Unable to display map</h2>}>
|
<ErrorBoundary fallback={<h2>Unable to display map</h2>}>
|
||||||
<Map {...mapOptions}>{children}</Map>
|
<Map {...mapOptions}>{children}</Map>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default function Map({
|
|||||||
|
|
||||||
const markers = getHotelMapMarkers(hotels)
|
const markers = getHotelMapMarkers(hotels)
|
||||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||||
const defaultCoordinates = activeHotel
|
const defaultCenter = activeHotel
|
||||||
? {
|
? {
|
||||||
lat: activeHotel.hotel.location.latitude,
|
lat: activeHotel.hotel.location.latitude,
|
||||||
lng: activeHotel.hotel.location.longitude,
|
lng: activeHotel.hotel.location.longitude,
|
||||||
@@ -81,7 +81,7 @@ export default function Map({
|
|||||||
lat: defaultLocation.latitude,
|
lat: defaultLocation.latitude,
|
||||||
lng: defaultLocation.longitude,
|
lng: defaultLocation.longitude,
|
||||||
}
|
}
|
||||||
: null
|
: undefined
|
||||||
const defaultZoom = activeHotel
|
const defaultZoom = activeHotel
|
||||||
? 15
|
? 15
|
||||||
: (defaultLocation?.default_zoom ?? (pageType === "city" ? 10 : 3))
|
: (defaultLocation?.default_zoom ?? (pageType === "city" ? 10 : 3))
|
||||||
@@ -171,7 +171,7 @@ export default function Map({
|
|||||||
markers={markers}
|
markers={markers}
|
||||||
mapId={mapId}
|
mapId={mapId}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
defaultCoordinates={defaultCoordinates}
|
defaultCenter={defaultCenter}
|
||||||
defaultZoom={defaultZoom}
|
defaultZoom={defaultZoom}
|
||||||
fitBounds={!activeHotel}
|
fitBounds={!activeHotel}
|
||||||
gestureHandling="greedy"
|
gestureHandling="greedy"
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Faciliteter",
|
"Amenities": "Faciliteter",
|
||||||
"Amusement park": "Forlystelsespark",
|
"Amusement park": "Forlystelsespark",
|
||||||
"An account with this email already exists. Please try signing in instead.": "Der findes allerede en konto med denne e-mailadresse. Log venligst ind i stedet.",
|
"An account with this email already exists. Please try signing in instead.": "Der findes allerede en konto med denne e-mailadresse. Log venligst ind i stedet.",
|
||||||
|
"An error occurred ({errorId})": "Der opstod en fejl ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "Der opstod en fejl under forsøget på at administrere dine præferencer. Prøv venligst igen senere.",
|
"An error occurred trying to manage your preferences, please try again later.": "Der opstod en fejl under forsøget på at administrere dine præferencer. Prøv venligst igen senere.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.",
|
"An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.",
|
||||||
"An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.",
|
"An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Annehmlichkeiten",
|
"Amenities": "Annehmlichkeiten",
|
||||||
"Amusement park": "Vergnügungspark",
|
"Amusement park": "Vergnügungspark",
|
||||||
"An account with this email already exists. Please try signing in instead.": "Ein Konto mit dieser E-Mail-Adresse existiert bereits. Bitte melden Sie sich stattdessen an.",
|
"An account with this email already exists. Please try signing in instead.": "Ein Konto mit dieser E-Mail-Adresse existiert bereits. Bitte melden Sie sich stattdessen an.",
|
||||||
|
"An error occurred ({errorId})": "Ein Fehler ist aufgetreten ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "Beim Versuch, Ihre Einstellungen zu verwalten, ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
"An error occurred trying to manage your preferences, please try again later.": "Beim Versuch, Ihre Einstellungen zu verwalten, ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
"An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||||
"An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.",
|
"An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Amenities",
|
"Amenities": "Amenities",
|
||||||
"Amusement park": "Amusement park",
|
"Amusement park": "Amusement park",
|
||||||
"An account with this email already exists. Please try signing in instead.": "An account with this email already exists. Please try signing in instead.",
|
"An account with this email already exists. Please try signing in instead.": "An account with this email already exists. Please try signing in instead.",
|
||||||
|
"An error occurred ({errorId})": "An error occurred ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "An error occurred trying to manage your preferences, please try again later.",
|
"An error occurred trying to manage your preferences, please try again later.": "An error occurred trying to manage your preferences, please try again later.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.",
|
"An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.",
|
||||||
"An error occurred when trying to update profile.": "An error occurred when trying to update profile.",
|
"An error occurred when trying to update profile.": "An error occurred when trying to update profile.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Mukavuudet",
|
"Amenities": "Mukavuudet",
|
||||||
"Amusement park": "Huvipuisto",
|
"Amusement park": "Huvipuisto",
|
||||||
"An account with this email already exists. Please try signing in instead.": "Tällä sähköpostiosoitteella on jo olemassa tili. Joten käytä sitä kirjautuaksesi sisään.",
|
"An account with this email already exists. Please try signing in instead.": "Tällä sähköpostiosoitteella on jo olemassa tili. Joten käytä sitä kirjautuaksesi sisään.",
|
||||||
|
"An error occurred ({errorId})": "Tapahtui virhe ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "Asetusten hallinnassa tapahtui virhe. Yritä myöhemmin uudelleen.",
|
"An error occurred trying to manage your preferences, please try again later.": "Asetusten hallinnassa tapahtui virhe. Yritä myöhemmin uudelleen.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.",
|
"An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.",
|
||||||
"An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.",
|
"An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Fasiliteter",
|
"Amenities": "Fasiliteter",
|
||||||
"Amusement park": "Tivoli",
|
"Amusement park": "Tivoli",
|
||||||
"An account with this email already exists. Please try signing in instead.": "En konto med denne e-postadressen eksisterer allerede. Vennligst logg inn i stedet.",
|
"An account with this email already exists. Please try signing in instead.": "En konto med denne e-postadressen eksisterer allerede. Vennligst logg inn i stedet.",
|
||||||
|
"An error occurred ({errorId})": "Det oppstod en feil ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "Det oppstod en feil under forsøket på å administrere innstillingene dine. Prøv igjen senere.",
|
"An error occurred trying to manage your preferences, please try again later.": "Det oppstod en feil under forsøket på å administrere innstillingene dine. Prøv igjen senere.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.",
|
"An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.",
|
||||||
"An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.",
|
"An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"Amenities": "Bekvämligheter",
|
"Amenities": "Bekvämligheter",
|
||||||
"Amusement park": "Nöjespark",
|
"Amusement park": "Nöjespark",
|
||||||
"An account with this email already exists. Please try signing in instead.": "Ett konto med denna mailadress finns redan. Vänligen försök logga in istället.",
|
"An account with this email already exists. Please try signing in instead.": "Ett konto med denna mailadress finns redan. Vänligen försök logga in istället.",
|
||||||
|
"An error occurred ({errorId})": "Ett fel uppstod ({errorId})",
|
||||||
"An error occurred trying to manage your preferences, please try again later.": "Ett fel uppstod när du försökte hantera dina inställningar, försök igen senare.",
|
"An error occurred trying to manage your preferences, please try again later.": "Ett fel uppstod när du försökte hantera dina inställningar, försök igen senare.",
|
||||||
"An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.",
|
"An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.",
|
||||||
"An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.",
|
"An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.",
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
query GetDestinationOverviewPage($locale: String!, $uid: String!) {
|
query GetDestinationOverviewPage($locale: String!, $uid: String!) {
|
||||||
destination_overview_page(uid: $uid, locale: $locale) {
|
destination_overview_page(uid: $uid, locale: $locale) {
|
||||||
title
|
heading
|
||||||
url
|
url
|
||||||
blocks {
|
blocks {
|
||||||
__typename
|
__typename
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ export const getDestinationOverviewPage = cache(
|
|||||||
return serverClient().contentstack.destinationOverviewPage.get()
|
return serverClient().contentstack.destinationOverviewPage.get()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getDestinationsList = cache(
|
export const getDestinationsList = cache(
|
||||||
async function getMemoizedDestinationsList() {
|
async function getMemoizedDestinationsList() {
|
||||||
return serverClient().contentstack.destinationOverviewPage.destinations.get()
|
return serverClient().contentstack.destinationOverviewPage.destinations.get()
|
||||||
|
|||||||
+1047
File diff suppressed because it is too large
Load Diff
+1046
File diff suppressed because it is too large
Load Diff
+1046
File diff suppressed because it is too large
Load Diff
+1046
File diff suppressed because it is too large
Load Diff
+1047
File diff suppressed because it is too large
Load Diff
+1047
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
|
|
||||||
export const destinationOverviewPageSchema = z.object({
|
export const destinationOverviewPageSchema = z.object({
|
||||||
destination_overview_page: z.object({
|
destination_overview_page: z.object({
|
||||||
title: z.string(),
|
heading: z.string().nullish(),
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options),
|
blocks: discriminatedUnionArray(blocksSchema.options),
|
||||||
location: mapLocationSchema,
|
location: mapLocationSchema,
|
||||||
system: systemSchema.merge(
|
system: systemSchema.merge(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Lang } from "@/constants/languages"
|
||||||
import {
|
import {
|
||||||
GetDestinationOverviewPage,
|
GetDestinationOverviewPage,
|
||||||
GetDestinationOverviewPageRefs,
|
GetDestinationOverviewPageRefs,
|
||||||
@@ -20,6 +21,12 @@ import {
|
|||||||
} from "../../hotels/utils"
|
} from "../../hotels/utils"
|
||||||
import { getCityPageUrls } from "../destinationCityPage/utils"
|
import { getCityPageUrls } from "../destinationCityPage/utils"
|
||||||
import { getCountryPageUrls } from "../destinationCountryPage/utils"
|
import { getCountryPageUrls } from "../destinationCountryPage/utils"
|
||||||
|
import destinationsDataDa from "./destinations-da.json" with { assert: "json" }
|
||||||
|
import destinationsDataDe from "./destinations-de.json" with { assert: "json" }
|
||||||
|
import destinationsDataEn from "./destinations-en.json" with { assert: "json" }
|
||||||
|
import destinationsDataFi from "./destinations-fi.json" with { assert: "json" }
|
||||||
|
import destinationsDataNo from "./destinations-no.json" with { assert: "json" }
|
||||||
|
import destinationsDataSv from "./destinations-sv.json" with { assert: "json" }
|
||||||
import {
|
import {
|
||||||
destinationOverviewPageRefsSchema,
|
destinationOverviewPageRefsSchema,
|
||||||
destinationOverviewPageSchema,
|
destinationOverviewPageSchema,
|
||||||
@@ -200,80 +207,123 @@ export const destinationOverviewPageQueryRouter = router({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
destinations: router({
|
destinations: router({
|
||||||
get: serviceProcedure.query(async function ({ ctx }) {
|
get: serviceProcedure.query(async function ({
|
||||||
const countries = await getCountries({
|
ctx,
|
||||||
lang: ctx.lang,
|
}): Promise<DestinationsData> {
|
||||||
serviceToken: ctx.serviceToken,
|
// For go live we are using static data here, as it rarely changes.
|
||||||
})
|
// This also improves operational reliance as we are not hammering
|
||||||
|
// a lot of endpoints for a lot of data.
|
||||||
|
// Re-implement once we have better API support and established caching
|
||||||
|
// patterns and mechanisms.
|
||||||
|
|
||||||
const countryPages = await getCountryPageUrls(ctx.lang)
|
// NOTE: To update the static data set `useStaticData = false`.
|
||||||
|
// Then go to the "Hotels & Destinations" page and visit every language.
|
||||||
|
// At the time of commit http://localhost:3000/en/destinations.
|
||||||
|
// This will update the JSON file locally, each page load for each language,
|
||||||
|
// if all data loads correctly.
|
||||||
|
// Set back `useStaticData = true` again and test with the updated JSON file.
|
||||||
|
// Add, commit and push the updated JSON files with useStaticData = true here.
|
||||||
|
const useStaticData = true
|
||||||
|
|
||||||
if (!countries) {
|
if (useStaticData) {
|
||||||
return null
|
switch (ctx.lang) {
|
||||||
|
case Lang.da:
|
||||||
|
return destinationsDataDa
|
||||||
|
case Lang.de:
|
||||||
|
return destinationsDataDe
|
||||||
|
case Lang.fi:
|
||||||
|
return destinationsDataFi
|
||||||
|
case Lang.en:
|
||||||
|
return destinationsDataEn
|
||||||
|
case Lang.no:
|
||||||
|
return destinationsDataNo
|
||||||
|
case Lang.sv:
|
||||||
|
return destinationsDataSv
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return await updateJSONOnDisk()
|
||||||
}
|
}
|
||||||
|
|
||||||
const countryNames = countries.data.map((country) => country.name)
|
async function updateJSONOnDisk() {
|
||||||
|
const { lang } = ctx
|
||||||
|
|
||||||
const citiesByCountry = await getCitiesByCountry({
|
const countries = await getCountries({
|
||||||
lang: ctx.lang,
|
lang,
|
||||||
countries: countryNames,
|
serviceToken: ctx.serviceToken,
|
||||||
serviceToken: ctx.serviceToken,
|
})
|
||||||
onlyPublished: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const cityPages = await getCityPageUrls(ctx.lang)
|
if (!countries) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const destinations: DestinationsData = await Promise.all(
|
const countryNames = countries.data.map((country) => country.name)
|
||||||
Object.entries(citiesByCountry).map(async ([country, cities]) => {
|
|
||||||
const citiesWithHotelCount = await Promise.all(
|
|
||||||
cities.map(async (city) => {
|
|
||||||
const [hotels] = await safeTry(
|
|
||||||
getHotelIdsByCityId({
|
|
||||||
cityId: city.id,
|
|
||||||
serviceToken: ctx.serviceToken,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const cityPage = cityPages.find(
|
const citiesByCountry = await getCitiesByCountry({
|
||||||
(cityPage) => cityPage.city === city.cityIdentifier
|
lang,
|
||||||
)
|
countries: countryNames,
|
||||||
|
serviceToken: ctx.serviceToken,
|
||||||
|
onlyPublished: true,
|
||||||
|
})
|
||||||
|
|
||||||
if (!cityPage) {
|
const cityPages = await getCityPageUrls(lang)
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
const destinations = await Promise.all(
|
||||||
id: city.id,
|
Object.entries(citiesByCountry).map(async ([country, cities]) => {
|
||||||
name: city.name,
|
const activeCitiesWithHotelCount: Cities = await Promise.all(
|
||||||
hotelIds: hotels || [],
|
cities.map(async (city) => {
|
||||||
hotelCount: hotels?.length ?? 0,
|
const [hotels] = await safeTry(
|
||||||
url: cityPage.url,
|
getHotelIdsByCityId({
|
||||||
}
|
cityId: city.id,
|
||||||
})
|
serviceToken: ctx.serviceToken,
|
||||||
)
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const activeCitiesWithHotelCount: Cities =
|
const cityPage = cityPages.find(
|
||||||
citiesWithHotelCount.filter(
|
(cityPage) => cityPage.city === city.cityIdentifier
|
||||||
(city): city is Cities[number] => !!city
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: city.id,
|
||||||
|
name: city.name,
|
||||||
|
hotelIds: hotels || [],
|
||||||
|
hotelCount: hotels ? hotels.length : 0,
|
||||||
|
url: cityPage?.url,
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const countryPage = countryPages.find(
|
const countryPages = await getCountryPageUrls(lang)
|
||||||
(countryPage) => countryPage.country === country
|
const countryPage = countryPages.find(
|
||||||
)
|
(countryPage) => countryPage.country === country
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
country,
|
country,
|
||||||
countryUrl: countryPage?.url,
|
countryUrl: countryPage?.url,
|
||||||
numberOfHotels: activeCitiesWithHotelCount.reduce(
|
numberOfHotels: activeCitiesWithHotelCount.reduce(
|
||||||
(acc, city) => acc + city.hotelCount,
|
(acc, city) => acc + city.hotelCount,
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
cities: activeCitiesWithHotelCount,
|
cities: activeCitiesWithHotelCount,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const data = destinations.sort((a, b) =>
|
||||||
|
a.country.localeCompare(b.country)
|
||||||
|
)
|
||||||
|
const fs = await import("node:fs")
|
||||||
|
fs.writeFileSync(
|
||||||
|
`./server/routers/contentstack/destinationOverviewPage/destinations-${lang}.json`,
|
||||||
|
JSON.stringify(data),
|
||||||
|
{
|
||||||
|
encoding: "utf-8",
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
)
|
return data
|
||||||
|
}
|
||||||
return destinations.sort((a, b) => a.country.localeCompare(b.country))
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CancellationRuleEnum } from "@/constants/booking"
|
import { CancellationRuleEnum } from "@/constants/booking"
|
||||||
|
import { Lang } from "@/constants/languages"
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
@@ -79,7 +80,6 @@ import type {
|
|||||||
} from "@/types/trpc/routers/hotel/availability"
|
} from "@/types/trpc/routers/hotel/availability"
|
||||||
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
|
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
|
||||||
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
||||||
import type { Lang } from "@/constants/routes/hotelReservation"
|
|
||||||
|
|
||||||
export const getHotel = cache(
|
export const getHotel = cache(
|
||||||
async (input: HotelInput, serviceToken: string) => {
|
async (input: HotelInput, serviceToken: string) => {
|
||||||
@@ -1198,7 +1198,9 @@ export const hotelQueryRouter = router({
|
|||||||
}) {
|
}) {
|
||||||
const lang = input?.lang ?? ctx.lang
|
const lang = input?.lang ?? ctx.lang
|
||||||
const countries = await getCountries({
|
const countries = await getCountries({
|
||||||
lang: lang,
|
// Countries need to be in English regardless of incoming lang because
|
||||||
|
// we use the names as input for API endpoints.
|
||||||
|
lang: Lang.en,
|
||||||
serviceToken: ctx.serviceToken,
|
serviceToken: ctx.serviceToken,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+17
-13
@@ -1,17 +1,21 @@
|
|||||||
export type DestinationsData = {
|
export type City = {
|
||||||
country: string
|
id: string
|
||||||
countryUrl: string | undefined
|
name: string
|
||||||
numberOfHotels: number
|
hotelIds: string[]
|
||||||
cities: {
|
hotelCount: number
|
||||||
id: string
|
url?: string
|
||||||
name: string
|
}
|
||||||
hotelIds: string[]
|
|
||||||
hotelCount: number
|
|
||||||
url: string
|
|
||||||
}[]
|
|
||||||
}[]
|
|
||||||
|
|
||||||
export type Cities = DestinationsData[number]["cities"]
|
export type DestinationCountry = {
|
||||||
|
country: string
|
||||||
|
countryUrl?: string
|
||||||
|
numberOfHotels: number
|
||||||
|
cities: City[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DestinationsData = DestinationCountry[]
|
||||||
|
|
||||||
|
export type Cities = DestinationCountry["cities"]
|
||||||
|
|
||||||
export type HotelsSectionProps = {
|
export type HotelsSectionProps = {
|
||||||
destinations: DestinationsData
|
destinations: DestinationsData
|
||||||
|
|||||||
@@ -29,6 +29,21 @@ export type ContentTypeParams = {
|
|||||||
| PageContentTypeEnum.startPage
|
| PageContentTypeEnum.startPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is purely for use in `apps/scandic-web/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx`
|
||||||
|
// It is meant as an interim solution while we move away from the switch-case
|
||||||
|
// approach into routing these based on App router.
|
||||||
|
// Folders in `apps/scandic-web/app/[lang]/(live)/(public)` should not be in this type.
|
||||||
|
export type NonAppRouterContentTypeParams = {
|
||||||
|
contentType:
|
||||||
|
| PageContentTypeEnum.loyaltyPage
|
||||||
|
| PageContentTypeEnum.contentPage
|
||||||
|
| PageContentTypeEnum.hotelPage
|
||||||
|
| PageContentTypeEnum.collectionPage
|
||||||
|
| PageContentTypeEnum.destinationCountryPage
|
||||||
|
| PageContentTypeEnum.destinationCityPage
|
||||||
|
| PageContentTypeEnum.startPage
|
||||||
|
}
|
||||||
|
|
||||||
export type ContentTypeWebviewParams = {
|
export type ContentTypeWebviewParams = {
|
||||||
contentType: "loyalty-page" | "account-page"
|
contentType: "loyalty-page" | "account-page"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user