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()
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function DestinationOverviewPageBreadcrumbs() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "../../../[contentType]/[uid]/@preview/loading"
|
||||||
@@ -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"
|
||||||
@@ -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 />
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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