Merged in feat/SW-1751-destination-0-results (pull request #1448)
feat(SW-1751): Added alert if no results are found, also implemented default location data from Contentstack * feat(SW-1751): Added alert if no results are found, also implemented default location data from Contentstack Approved-by: Matilda Landström
This commit is contained in:
@@ -6,6 +6,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||||
|
|
||||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||||
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
@@ -16,6 +17,8 @@ import CityListingSkeleton from "./CityListingSkeleton"
|
|||||||
|
|
||||||
import styles from "./cityListing.module.css"
|
import styles from "./cityListing.module.css"
|
||||||
|
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export default function CityListing() {
|
export default function CityListing() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const scrollRef = useRef<HTMLElement>(null)
|
const scrollRef = useRef<HTMLElement>(null)
|
||||||
@@ -60,23 +63,35 @@ export default function CityListing() {
|
|||||||
listType="city"
|
listType="city"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
{activeCities.length === 0 ? (
|
||||||
className={`${styles.cityList} ${allCitiesVisible ? styles.allVisible : ""}`}
|
<Alert
|
||||||
>
|
type={AlertTypeEnum.Info}
|
||||||
{activeCities.map((city) => (
|
heading={intl.formatMessage({ id: "No matching locations found" })}
|
||||||
<li key={city.system.uid}>
|
text={intl.formatMessage({
|
||||||
<CityListingItem city={city} />
|
id: "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
</li>
|
})}
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{activeCities.length > 5 ? (
|
|
||||||
<ShowMoreButton
|
|
||||||
loadMoreData={handleShowMore}
|
|
||||||
showLess={allCitiesVisible}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : (
|
||||||
{showBackToTop && (
|
<>
|
||||||
<BackToTopButton position="center" onClick={scrollToTop} />
|
<ul
|
||||||
|
className={`${styles.cityList} ${allCitiesVisible ? styles.allVisible : ""}`}
|
||||||
|
>
|
||||||
|
{activeCities.map((city) => (
|
||||||
|
<li key={city.system.uid}>
|
||||||
|
<CityListingItem city={city} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{activeCities.length > 5 ? (
|
||||||
|
<ShowMoreButton
|
||||||
|
loadMoreData={handleShowMore}
|
||||||
|
showLess={allCitiesVisible}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{showBackToTop && (
|
||||||
|
<BackToTopButton position="center" onClick={scrollToTop} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||||
|
|
||||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||||
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import { debounce } from "@/utils/debounce"
|
import { debounce } from "@/utils/debounce"
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ import { getVisibleHotels } from "./utils"
|
|||||||
|
|
||||||
import styles from "./hotelList.module.css"
|
import styles from "./hotelList.module.css"
|
||||||
|
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||||
|
|
||||||
export default function HotelList() {
|
export default function HotelList() {
|
||||||
@@ -71,13 +73,23 @@ export default function HotelList() {
|
|||||||
listType="hotel"
|
listType="hotel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.hotelList}>
|
{activeHotels.length === 0 ? (
|
||||||
{visibleHotels.map(({ hotel, url }) => (
|
<Alert
|
||||||
<li key={hotel.operaId}>
|
type={AlertTypeEnum.Info}
|
||||||
<HotelListItem hotel={hotel} url={url} />
|
heading={intl.formatMessage({ id: "No matching hotels found" })}
|
||||||
</li>
|
text={intl.formatMessage({
|
||||||
))}
|
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
</ul>
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ul className={styles.hotelList}>
|
||||||
|
{visibleHotels.map(({ hotel, url }) => (
|
||||||
|
<li key={hotel.operaId}>
|
||||||
|
<HotelListItem hotel={hotel} url={url} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,22 @@ import HotelList from "./HotelList"
|
|||||||
|
|
||||||
import styles from "./cityMap.module.css"
|
import styles from "./cityMap.module.css"
|
||||||
|
|
||||||
|
import type { MapLocation } from "@/types/components/mapLocation"
|
||||||
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
||||||
|
|
||||||
interface CityMapProps {
|
interface CityMapProps {
|
||||||
mapId: string
|
mapId: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
city: CityLocation
|
city: CityLocation
|
||||||
|
defaultLocation: MapLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CityMap({ mapId, apiKey, city }: CityMapProps) {
|
export default function CityMap({
|
||||||
|
mapId,
|
||||||
|
apiKey,
|
||||||
|
city,
|
||||||
|
defaultLocation,
|
||||||
|
}: CityMapProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||||
(state) => ({
|
(state) => ({
|
||||||
@@ -30,7 +37,13 @@ export default function CityMap({ mapId, apiKey, city }: CityMapProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Map hotels={activeHotels} mapId={mapId} apiKey={apiKey} pageType="city">
|
<Map
|
||||||
|
hotels={activeHotels}
|
||||||
|
mapId={mapId}
|
||||||
|
apiKey={apiKey}
|
||||||
|
pageType="city"
|
||||||
|
defaultLocation={defaultLocation}
|
||||||
|
>
|
||||||
<Title
|
<Title
|
||||||
level="h2"
|
level="h2"
|
||||||
as="h3"
|
as="h3"
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ export default async function DestinationCityPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{destination_settings.city && (
|
{destination_settings.city && (
|
||||||
<StaticMap city={destination_settings.city} />
|
<StaticMap
|
||||||
|
city={destination_settings.city}
|
||||||
|
location={destination_settings.location}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</SidebarContentWrapper>
|
</SidebarContentWrapper>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -78,6 +81,7 @@ export default async function DestinationCityPage() {
|
|||||||
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
||||||
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
||||||
city={city}
|
city={city}
|
||||||
|
defaultLocation={destination_settings.location}
|
||||||
/>
|
/>
|
||||||
</HotelDataContainer>
|
</HotelDataContainer>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||||
|
|
||||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||||
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
import CityListItem from "../CityListItem"
|
import CityListItem from "../CityListItem"
|
||||||
@@ -12,6 +13,8 @@ import CityListSkeleton from "./CityListSkeleton"
|
|||||||
|
|
||||||
import styles from "./cityList.module.css"
|
import styles from "./cityList.module.css"
|
||||||
|
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export default function CityList() {
|
export default function CityList() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { filters, sortItems, activeCities, isLoading } =
|
const { filters, sortItems, activeCities, isLoading } =
|
||||||
@@ -39,13 +42,23 @@ export default function CityList() {
|
|||||||
listType="city"
|
listType="city"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.cityList}>
|
{activeCities.length === 0 ? (
|
||||||
{activeCities.map((city) => (
|
<Alert
|
||||||
<li key={city.system.uid}>
|
type={AlertTypeEnum.Info}
|
||||||
<CityListItem city={city} />
|
heading={intl.formatMessage({ id: "No matching locations found" })}
|
||||||
</li>
|
text={intl.formatMessage({
|
||||||
))}
|
id: "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
</ul>
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ul className={styles.cityList}>
|
||||||
|
{activeCities.map((city) => (
|
||||||
|
<li key={city.system.uid}>
|
||||||
|
<CityListItem city={city} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,20 @@ import CityList from "./CityList"
|
|||||||
|
|
||||||
import styles from "./countryMap.module.css"
|
import styles from "./countryMap.module.css"
|
||||||
|
|
||||||
|
import type { MapLocation } from "@/types/components/mapLocation"
|
||||||
|
|
||||||
interface CountryMapProps {
|
interface CountryMapProps {
|
||||||
mapId: string
|
mapId: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
country: string
|
country: string
|
||||||
|
defaultLocation: MapLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CountryMap({
|
export default function CountryMap({
|
||||||
mapId,
|
mapId,
|
||||||
apiKey,
|
apiKey,
|
||||||
country,
|
country,
|
||||||
|
defaultLocation,
|
||||||
}: CountryMapProps) {
|
}: CountryMapProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||||
@@ -33,7 +37,13 @@ export default function CountryMap({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Map hotels={activeHotels} mapId={mapId} apiKey={apiKey} pageType="country">
|
<Map
|
||||||
|
hotels={activeHotels}
|
||||||
|
mapId={mapId}
|
||||||
|
apiKey={apiKey}
|
||||||
|
pageType="country"
|
||||||
|
defaultLocation={defaultLocation}
|
||||||
|
>
|
||||||
<Title
|
<Title
|
||||||
level="h2"
|
level="h2"
|
||||||
as="h3"
|
as="h3"
|
||||||
|
|||||||
@@ -71,19 +71,22 @@ export default async function DestinationCountryPage() {
|
|||||||
sidePeekContent={sidepeek_content}
|
sidePeekContent={sidepeek_content}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<StaticMap
|
||||||
<StaticMap country={destination_settings.country} />
|
country={destination_settings.country}
|
||||||
|
location={destination_settings.location}
|
||||||
|
/>
|
||||||
</SidebarContentWrapper>
|
</SidebarContentWrapper>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
<CountryMap
|
<CountryMap
|
||||||
country={translatedCountry}
|
country={translatedCountry}
|
||||||
|
defaultLocation={destination_settings.location}
|
||||||
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
||||||
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
||||||
/>
|
/>
|
||||||
</CityDataContainer>
|
</CityDataContainer>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<TrackingSDK pageData={tracking} />
|
<TrackingSDK pageData={tracking} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import MapProvider from "../../Map/MapProvider"
|
|||||||
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
||||||
import InputForm from "./InputForm"
|
import InputForm from "./InputForm"
|
||||||
|
|
||||||
export default async function OverviewMapContainer() {
|
import type { MapLocation } from "@/types/components/mapLocation"
|
||||||
|
|
||||||
|
interface OverviewMapContainerProps {
|
||||||
|
defaultLocation: MapLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function OverviewMapContainer({
|
||||||
|
defaultLocation,
|
||||||
|
}: OverviewMapContainerProps) {
|
||||||
const hotelData = await getAllHotels()
|
const hotelData = await getAllHotels()
|
||||||
|
|
||||||
if (!hotelData) {
|
if (!hotelData) {
|
||||||
@@ -19,11 +27,23 @@ 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 />
|
<InputForm />
|
||||||
<DynamicMap mapId={googleMapId} markers={markers}>
|
<DynamicMap
|
||||||
|
mapId={googleMapId}
|
||||||
|
markers={markers}
|
||||||
|
defaultCoordinates={defaultCoordinates}
|
||||||
|
defaultZoom={defaultZoom}
|
||||||
|
>
|
||||||
<MapContent geojson={geoJson} />
|
<MapContent geojson={geoJson} />
|
||||||
</DynamicMap>
|
</DynamicMap>
|
||||||
</MapProvider>
|
</MapProvider>
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ 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}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||||
|
|
||||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||||
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
@@ -16,6 +17,8 @@ import HotelListingSkeleton from "./HotelListingSkeleton"
|
|||||||
|
|
||||||
import styles from "./hotelListing.module.css"
|
import styles from "./hotelListing.module.css"
|
||||||
|
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export default function HotelListing() {
|
export default function HotelListing() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const scrollRef = useRef<HTMLElement>(null)
|
const scrollRef = useRef<HTMLElement>(null)
|
||||||
@@ -60,23 +63,35 @@ export default function HotelListing() {
|
|||||||
listType="hotel"
|
listType="hotel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
{activeHotels.length === 0 ? (
|
||||||
className={`${styles.hotelList} ${allHotelsVisible ? styles.allVisible : ""}`}
|
<Alert
|
||||||
>
|
type={AlertTypeEnum.Info}
|
||||||
{activeHotels.map(({ hotel, url }) => (
|
heading={intl.formatMessage({ id: "No matching hotels found" })}
|
||||||
<li key={hotel.name}>
|
text={intl.formatMessage({
|
||||||
<HotelListingItem hotel={hotel} url={url} />
|
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
</li>
|
})}
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{activeHotels.length > 5 ? (
|
|
||||||
<ShowMoreButton
|
|
||||||
loadMoreData={handleShowMore}
|
|
||||||
showLess={allHotelsVisible}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : (
|
||||||
{showBackToTop && (
|
<>
|
||||||
<BackToTopButton position="center" onClick={scrollToTop} />
|
<ul
|
||||||
|
className={`${styles.hotelList} ${allHotelsVisible ? styles.allVisible : ""}`}
|
||||||
|
>
|
||||||
|
{activeHotels.map(({ hotel, url }) => (
|
||||||
|
<li key={hotel.name}>
|
||||||
|
<HotelListingItem hotel={hotel} url={url} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{activeHotels.length > 5 ? (
|
||||||
|
<ShowMoreButton
|
||||||
|
loadMoreData={handleShowMore}
|
||||||
|
showLess={allHotelsVisible}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{showBackToTop && (
|
||||||
|
<BackToTopButton position="center" onClick={scrollToTop} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,17 +14,24 @@ import styles from "./dynamicMap.module.css"
|
|||||||
|
|
||||||
import type { DestinationMarker } from "@/types/components/maps/destinationMarkers"
|
import type { DestinationMarker } from "@/types/components/maps/destinationMarkers"
|
||||||
|
|
||||||
|
const BACKUP_COORDINATES = {
|
||||||
|
lat: 59.3293,
|
||||||
|
lng: 18.0686,
|
||||||
|
}
|
||||||
|
|
||||||
interface DynamicMapProps {
|
interface DynamicMapProps {
|
||||||
markers: DestinationMarker[]
|
markers: DestinationMarker[]
|
||||||
mapId: string
|
mapId: string
|
||||||
onTilesLoaded?: () => void
|
defaultCoordinates: google.maps.LatLngLiteral | null
|
||||||
|
defaultZoom: number
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DynamicMap({
|
export default function DynamicMap({
|
||||||
markers,
|
markers,
|
||||||
mapId,
|
mapId,
|
||||||
onTilesLoaded,
|
defaultCoordinates,
|
||||||
|
defaultZoom,
|
||||||
onClose,
|
onClose,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<DynamicMapProps>) {
|
}: PropsWithChildren<DynamicMapProps>) {
|
||||||
@@ -33,13 +40,17 @@ export default function DynamicMap({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (map) {
|
if (map) {
|
||||||
const bounds = new google.maps.LatLngBounds()
|
if (markers.length) {
|
||||||
markers.forEach((marker) => {
|
const bounds = new google.maps.LatLngBounds()
|
||||||
bounds.extend(marker.coordinates)
|
markers.forEach((marker) => {
|
||||||
})
|
bounds.extend(marker.coordinates)
|
||||||
map.fitBounds(bounds)
|
})
|
||||||
|
map.fitBounds(bounds, 100)
|
||||||
|
} else if (defaultCoordinates) {
|
||||||
|
map.setCenter(defaultCoordinates)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [map, markers])
|
}, [map, markers, defaultCoordinates])
|
||||||
|
|
||||||
useHandleKeyUp((event: KeyboardEvent) => {
|
useHandleKeyUp((event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && onClose) {
|
if (event.key === "Escape" && onClose) {
|
||||||
@@ -61,13 +72,10 @@ export default function DynamicMap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapOptions: MapProps = {
|
const mapOptions: MapProps = {
|
||||||
defaultCenter: markers[0]?.coordinates || {
|
defaultCenter: defaultCoordinates || BACKUP_COORDINATES, // Default center will be overridden by the bounds
|
||||||
lat: 59.3293,
|
|
||||||
lng: 18.0686,
|
|
||||||
}, // Default center will be overridden by the bounds
|
|
||||||
minZoom: 3,
|
minZoom: 3,
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
defaultZoom: 5,
|
defaultZoom,
|
||||||
disableDefaultUI: true,
|
disableDefaultUI: true,
|
||||||
clickableIcons: false,
|
clickableIcons: false,
|
||||||
gestureHandling: "greedy",
|
gestureHandling: "greedy",
|
||||||
@@ -76,9 +84,7 @@ export default function DynamicMap({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.mapWrapper}>
|
<div className={styles.mapWrapper}>
|
||||||
<Map {...mapOptions} onTilesLoaded={onTilesLoaded}>
|
<Map {...mapOptions}>{children}</Map>
|
||||||
{children}
|
|
||||||
</Map>
|
|
||||||
<div className={styles.ctaButtons}>
|
<div className={styles.ctaButtons}>
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "./utils"
|
|||||||
|
|
||||||
import styles from "./map.module.css"
|
import styles from "./map.module.css"
|
||||||
|
|
||||||
|
import type { MapLocation } from "@/types/components/mapLocation"
|
||||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||||
|
|
||||||
interface MapProps {
|
interface MapProps {
|
||||||
@@ -27,14 +28,16 @@ interface MapProps {
|
|||||||
mapId: string
|
mapId: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
pageType: "city" | "country"
|
pageType: "city" | "country"
|
||||||
|
defaultLocation: MapLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Map({
|
export default function Map({
|
||||||
hotels,
|
hotels,
|
||||||
mapId,
|
mapId,
|
||||||
apiKey,
|
apiKey,
|
||||||
children,
|
defaultLocation,
|
||||||
pageType,
|
pageType,
|
||||||
|
children,
|
||||||
}: PropsWithChildren<MapProps>) {
|
}: PropsWithChildren<MapProps>) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
@@ -48,6 +51,14 @@ export default function Map({
|
|||||||
|
|
||||||
const markers = getHotelMapMarkers(hotels)
|
const markers = getHotelMapMarkers(hotels)
|
||||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||||
|
const defaultCoordinates = defaultLocation
|
||||||
|
? {
|
||||||
|
lat: defaultLocation.latitude,
|
||||||
|
lng: defaultLocation.longitude,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
const defaultZoom =
|
||||||
|
defaultLocation?.default_zoom ?? (pageType === "city" ? 10 : 3)
|
||||||
|
|
||||||
// Calculate the height of the map based on the viewport height from the start-point (below the header and booking widget)
|
// Calculate the height of the map based on the viewport height from the start-point (below the header and booking widget)
|
||||||
const handleMapHeight = useCallback(() => {
|
const handleMapHeight = useCallback(() => {
|
||||||
@@ -112,7 +123,13 @@ export default function Map({
|
|||||||
aria-label={"Mapview"}
|
aria-label={"Mapview"}
|
||||||
>
|
>
|
||||||
<aside className={styles.sidebar}>{children}</aside>
|
<aside className={styles.sidebar}>{children}</aside>
|
||||||
<DynamicMap markers={markers} mapId={mapId} onClose={handleClose}>
|
<DynamicMap
|
||||||
|
markers={markers}
|
||||||
|
mapId={mapId}
|
||||||
|
onClose={handleClose}
|
||||||
|
defaultCoordinates={defaultCoordinates}
|
||||||
|
defaultZoom={defaultZoom}
|
||||||
|
>
|
||||||
<MapContent geojson={geoJson} />
|
<MapContent geojson={geoJson} />
|
||||||
</DynamicMap>
|
</DynamicMap>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -5,29 +5,48 @@ import MapButton from "./MapButton"
|
|||||||
|
|
||||||
import styles from "./staticMap.module.css"
|
import styles from "./staticMap.module.css"
|
||||||
|
|
||||||
|
import type { MapLocation } from "@/types/components/mapLocation"
|
||||||
|
|
||||||
interface StaticMapProps {
|
interface StaticMapProps {
|
||||||
country?: string
|
country?: string
|
||||||
city?: string
|
city?: string
|
||||||
|
location: MapLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom levels on the static map are always more zoomed in than on the dynamic maps.
|
||||||
|
// One of the reasons is because of the smaller size of the map. This function
|
||||||
|
// adjusts the zoom level for the static map based on the default zoom level
|
||||||
|
// of the location and whether the location is a country or a city.
|
||||||
|
function getZoomLevel(defaultZoom: number | undefined, isCountry: boolean) {
|
||||||
|
if (defaultZoom) {
|
||||||
|
return isCountry ? defaultZoom - 3 : defaultZoom - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCountry ? 4 : 10
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function DestinationStaticMap({
|
export default async function DestinationStaticMap({
|
||||||
country,
|
country,
|
||||||
city,
|
city,
|
||||||
|
location,
|
||||||
}: StaticMapProps) {
|
}: StaticMapProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const altText = city
|
const altText = city
|
||||||
? intl.formatMessage({ id: "Map of the city" })
|
? intl.formatMessage({ id: "Map of the city" })
|
||||||
: intl.formatMessage({ id: "Map of the country" })
|
: intl.formatMessage({ id: "Map of the country" })
|
||||||
const zoomLevel = city ? 10 : 3
|
const coordinates = location
|
||||||
|
? { lat: location.latitude, lng: location.longitude }
|
||||||
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.mapWrapper}>
|
<div className={styles.mapWrapper}>
|
||||||
<StaticMap
|
<StaticMap
|
||||||
country={country}
|
country={country}
|
||||||
city={city}
|
city={city}
|
||||||
|
coordinates={coordinates}
|
||||||
width={320}
|
width={320}
|
||||||
height={200}
|
height={200}
|
||||||
zoomLevel={zoomLevel}
|
zoomLevel={getZoomLevel(location?.default_zoom, !!country)}
|
||||||
altText={altText}
|
altText={altText}
|
||||||
/>
|
/>
|
||||||
<MapButton className={styles.button} />
|
<MapButton className={styles.button} />
|
||||||
|
|||||||
@@ -50,6 +50,15 @@
|
|||||||
height: min(calc(80dvh - 180px), 500px);
|
height: min(calc(80dvh - 180px), 500px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alertWrapper:not(:empty) {
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x4) 0;
|
||||||
|
border-top: 1px solid var(--Base-Border-Subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertWrapper:not(:empty) + .footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Divider from "@/components/TempDesignSystem/Divider"
|
|||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
|
||||||
|
import Alert from "../TempDesignSystem/Alert"
|
||||||
import Filter from "./Filter"
|
import Filter from "./Filter"
|
||||||
import Sort from "./Sort"
|
import Sort from "./Sort"
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ import type {
|
|||||||
CategorizedFilters,
|
CategorizedFilters,
|
||||||
SortItem,
|
SortItem,
|
||||||
} from "@/types/components/destinationFilterAndSort"
|
} from "@/types/components/destinationFilterAndSort"
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
interface HotelFilterAndSortProps {
|
interface HotelFilterAndSortProps {
|
||||||
filters: CategorizedFilters
|
filters: CategorizedFilters
|
||||||
@@ -62,12 +64,24 @@ export default function DestinationFilterAndSort({
|
|||||||
resetPendingValues: state.actions.resetPendingValues,
|
resetPendingValues: state.actions.resetPendingValues,
|
||||||
setIsLoading: state.actions.setIsLoading,
|
setIsLoading: state.actions.setIsLoading,
|
||||||
}))
|
}))
|
||||||
|
const alertHeading =
|
||||||
|
listType === "city"
|
||||||
|
? intl.formatMessage({ id: "No matching locations found" })
|
||||||
|
: intl.formatMessage({ id: "No matching hotels found" })
|
||||||
|
|
||||||
|
const alertText =
|
||||||
|
listType === "city"
|
||||||
|
? intl.formatMessage({
|
||||||
|
id: "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
|
})
|
||||||
|
|
||||||
function submitAndClose(close: () => void) {
|
function submitAndClose(close: () => void) {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const sort = pendingSort
|
const sort = pendingSort
|
||||||
const filters = pendingFilters
|
const filters = pendingFilters
|
||||||
|
|
||||||
const parsedUrl = new URL(window.location.href)
|
const parsedUrl = new URL(window.location.href)
|
||||||
const searchParams = parsedUrl.searchParams
|
const searchParams = parsedUrl.searchParams
|
||||||
if (sort === defaultSort && searchParams.has("sort")) {
|
if (sort === defaultSort && searchParams.has("sort")) {
|
||||||
@@ -75,7 +89,6 @@ export default function DestinationFilterAndSort({
|
|||||||
} else if (sort !== defaultSort) {
|
} else if (sort !== defaultSort) {
|
||||||
searchParams.set("sort", sort)
|
searchParams.set("sort", sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [firstFilter, ...remainingFilters] = filters
|
const [firstFilter, ...remainingFilters] = filters
|
||||||
|
|
||||||
parsedUrl.pathname = basePath
|
parsedUrl.pathname = basePath
|
||||||
@@ -138,6 +151,16 @@ export default function DestinationFilterAndSort({
|
|||||||
<Divider color="subtle" />
|
<Divider color="subtle" />
|
||||||
<Filter filters={filters} />
|
<Filter filters={filters} />
|
||||||
</div>
|
</div>
|
||||||
|
{pendingCount === 0 && (
|
||||||
|
<div className={styles.alertWrapper}>
|
||||||
|
<Alert
|
||||||
|
type={AlertTypeEnum.Warning}
|
||||||
|
heading={alertHeading}
|
||||||
|
text={alertText}
|
||||||
|
ariaRole="status"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<Button
|
<Button
|
||||||
onClick={clearPendingFilters}
|
onClick={clearPendingFilters}
|
||||||
@@ -151,6 +174,7 @@ export default function DestinationFilterAndSort({
|
|||||||
intent="tertiary"
|
intent="tertiary"
|
||||||
size="large"
|
size="large"
|
||||||
theme="base"
|
theme="base"
|
||||||
|
disabled={pendingCount === 0}
|
||||||
onClick={() => submitAndClose(close)}
|
onClick={() => submitAndClose(close)}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import type { AriaRole } from "react"
|
||||||
|
|
||||||
import type { AlertTypeEnum } from "@/types/enums/alert"
|
import type { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
import type { SidepeekContent } from "@/types/trpc/routers/contentstack/siteConfig"
|
import type { SidepeekContent } from "@/types/trpc/routers/contentstack/siteConfig"
|
||||||
@@ -21,4 +22,6 @@ export interface AlertProps extends VariantProps<typeof alertVariants> {
|
|||||||
title: string
|
title: string
|
||||||
keepSearchParams?: boolean
|
keepSearchParams?: boolean
|
||||||
} | null
|
} | null
|
||||||
|
ariaRole?: AriaRole
|
||||||
|
ariaLive?: "off" | "assertive" | "polite"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
import Link from "../Link"
|
import Link from "../Link"
|
||||||
@@ -19,6 +21,8 @@ export default function Alert({
|
|||||||
phoneContact,
|
phoneContact,
|
||||||
sidepeekCtaText,
|
sidepeekCtaText,
|
||||||
sidepeekContent,
|
sidepeekContent,
|
||||||
|
ariaLive,
|
||||||
|
ariaRole,
|
||||||
}: AlertProps) {
|
}: AlertProps) {
|
||||||
const classNames = alertVariants({
|
const classNames = alertVariants({
|
||||||
className,
|
className,
|
||||||
@@ -31,7 +35,7 @@ export default function Alert({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<section className={classNames}>
|
<section className={classNames} role={ariaRole} aria-live={ariaLive}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<span className={styles.iconWrapper}>
|
<span className={styles.iconWrapper}>
|
||||||
<Icon className={styles.icon} width={24} height={24} />
|
<Icon className={styles.icon} width={24} height={24} />
|
||||||
|
|||||||
@@ -321,6 +321,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Er der andet, du gerne vil have os til at vide, før din ankomst?",
|
"Is there anything else you would like us to know before your arrival?": "Er der andet, du gerne vil have os til at vide, før din ankomst?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke muligt at administrere dine kommunikationspræferencer lige nu, prøv venligst igen senere eller kontakt support, hvis problemet fortsætter.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke muligt at administrere dine kommunikationspræferencer lige nu, prøv venligst igen senere eller kontakt support, hvis problemet fortsætter.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ud til, at ingen hoteller matcher dine filtre. Prøv at justere din søgning for at finde det perfekte ophold.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ud til, at ingen hoteller matcher dine filtre. Prøv at justere din søgning for at finde det perfekte ophold.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "Det ser ud til, at ingen placeringer matcher dine filtre. Prøv at justere din søgning for at finde det perfekte ophold.",
|
||||||
"Jacuzzi": "Jacuzzi",
|
"Jacuzzi": "Jacuzzi",
|
||||||
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||||
"Join at no cost": "Tilmeld dig uden omkostninger",
|
"Join at no cost": "Tilmeld dig uden omkostninger",
|
||||||
@@ -426,7 +427,8 @@
|
|||||||
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
||||||
"No content published": "Intet indhold offentliggjort",
|
"No content published": "Intet indhold offentliggjort",
|
||||||
"No hotels match your filters": "Ingen rum matchede dine filtre.",
|
"No hotels match your filters": "Ingen rum matchede dine filtre.",
|
||||||
"No matching location found": "Der blev ikke fundet nogen matchende placering",
|
"No matching hotels found": "Der blev ikke fundet noget matchende hotel",
|
||||||
|
"No matching locations found": "Der blev ikke fundet nogen matchende placering",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "Ingen tilgængelige priser",
|
"No prices available": "Ingen tilgængelige priser",
|
||||||
"No results": "Ingen resultater",
|
"No results": "Ingen resultater",
|
||||||
|
|||||||
@@ -322,6 +322,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Gibt es noch etwas, das Sie uns vor Ihrer Ankunft mitteilen möchten?",
|
"Is there anything else you would like us to know before your arrival?": "Gibt es noch etwas, das Sie uns vor Ihrer Ankunft mitteilen möchten?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Es ist derzeit nicht möglich, Ihre Kommunikationseinstellungen zu verwalten. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Es ist derzeit nicht möglich, Ihre Kommunikationseinstellungen zu verwalten. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Es scheint, dass keine Hotels Ihren Filtern entsprechen. Versuchen Sie, Ihre Suche anzupassen, um den perfekten Aufenthalt zu finden.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Es scheint, dass keine Hotels Ihren Filtern entsprechen. Versuchen Sie, Ihre Suche anzupassen, um den perfekten Aufenthalt zu finden.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "Es scheint, dass kein Standort Ihren Filtern entspricht. Versuchen Sie, Ihre Suche anzupassen, um den perfekten Aufenthalt zu finden.",
|
||||||
"Jacuzzi": "Whirlpool",
|
"Jacuzzi": "Whirlpool",
|
||||||
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||||
"Join at no cost": "Kostenlos beitreten",
|
"Join at no cost": "Kostenlos beitreten",
|
||||||
@@ -428,7 +429,8 @@
|
|||||||
"No charges were made.": "Es wurden keine Gebühren erhoben.",
|
"No charges were made.": "Es wurden keine Gebühren erhoben.",
|
||||||
"No content published": "Kein Inhalt veröffentlicht",
|
"No content published": "Kein Inhalt veröffentlicht",
|
||||||
"No hotels match your filters": "Kein Zimmer entspricht Ihren Filtern.",
|
"No hotels match your filters": "Kein Zimmer entspricht Ihren Filtern.",
|
||||||
"No matching location found": "Kein passender Standort gefunden",
|
"No matching hotels found": "Kein passendes Hotel gefunden",
|
||||||
|
"No matching locations found": "Kein passender Standort gefunden",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "Keine Preise verfügbar",
|
"No prices available": "Keine Preise verfügbar",
|
||||||
"No results": "Keine Ergebnisse",
|
"No results": "Keine Ergebnisse",
|
||||||
|
|||||||
@@ -323,6 +323,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Is there anything else you would like us to know before your arrival?",
|
"Is there anything else you would like us to know before your arrival?": "Is there anything else you would like us to know before your arrival?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
"Jacuzzi": "Jacuzzi",
|
"Jacuzzi": "Jacuzzi",
|
||||||
"Join Scandic Friends": "Join Scandic Friends",
|
"Join Scandic Friends": "Join Scandic Friends",
|
||||||
"Join at no cost": "Join at no cost",
|
"Join at no cost": "Join at no cost",
|
||||||
@@ -429,7 +430,8 @@
|
|||||||
"No charges were made.": "No charges were made.",
|
"No charges were made.": "No charges were made.",
|
||||||
"No content published": "No content published",
|
"No content published": "No content published",
|
||||||
"No hotels match your filters": "No hotels match your filters",
|
"No hotels match your filters": "No hotels match your filters",
|
||||||
"No matching location found": "No matching location found",
|
"No matching hotels found": "No matching hotels found",
|
||||||
|
"No matching locations found": "No matching locations found",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "No prices available",
|
"No prices available": "No prices available",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
|
|||||||
@@ -321,6 +321,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Onko jotain muuta, mitä haluaisit meidän tietävän ennen saapumistasi?",
|
"Is there anything else you would like us to know before your arrival?": "Onko jotain muuta, mitä haluaisit meidän tietävän ennen saapumistasi?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Viestintäasetuksiasi ei voi hallita juuri nyt. Yritä myöhemmin uudelleen tai ota yhteyttä tukeen, jos ongelma jatkuu.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Viestintäasetuksiasi ei voi hallita juuri nyt. Yritä myöhemmin uudelleen tai ota yhteyttä tukeen, jos ongelma jatkuu.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Näyttää siltä, että mikään hotelli ei vastaa suodattimiasi. Yritä muokata hakuasi löytääksesi täydellisen oleskelun.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Näyttää siltä, että mikään hotelli ei vastaa suodattimiasi. Yritä muokata hakuasi löytääksesi täydellisen oleskelun.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "Näyttää siltä, että mikään sijainti ei vastaa suodattimiasi. Yritä muokata hakuasi löytääksesi täydellisen oleskelun.",
|
||||||
"Jacuzzi": "Poreallas",
|
"Jacuzzi": "Poreallas",
|
||||||
"Join Scandic Friends": "Liity jäseneksi",
|
"Join Scandic Friends": "Liity jäseneksi",
|
||||||
"Join at no cost": "Liity maksutta",
|
"Join at no cost": "Liity maksutta",
|
||||||
@@ -427,7 +428,8 @@
|
|||||||
"No charges were made.": "Ei maksuja tehty.",
|
"No charges were made.": "Ei maksuja tehty.",
|
||||||
"No content published": "Ei julkaistua sisältöä",
|
"No content published": "Ei julkaistua sisältöä",
|
||||||
"No hotels match your filters": "Yksikään huone ei vastannut suodattimiasi",
|
"No hotels match your filters": "Yksikään huone ei vastannut suodattimiasi",
|
||||||
"No matching location found": "Vastaavaa sijaintia ei löytynyt",
|
"No matching hotels found": "Vastaavaa hotellia ei löytynyt",
|
||||||
|
"No matching locations found": "Vastaavaa sijaintia ei löytynyt",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "Hintoja ei ole saatavilla",
|
"No prices available": "Hintoja ei ole saatavilla",
|
||||||
"No results": "Ei tuloksia",
|
"No results": "Ei tuloksia",
|
||||||
|
|||||||
@@ -320,6 +320,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Er det noe annet du vil at vi skal vite før ankomsten din?",
|
"Is there anything else you would like us to know before your arrival?": "Er det noe annet du vil at vi skal vite før ankomsten din?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke mulig å administrere kommunikasjonspreferansene dine akkurat nå, prøv igjen senere eller kontakt support hvis problemet vedvarer.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke mulig å administrere kommunikasjonspreferansene dine akkurat nå, prøv igjen senere eller kontakt support hvis problemet vedvarer.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ut til at ingen hoteller samsvarer med filtrene dine. Prøv å justere søket for å finne det perfekte oppholdet.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ut til at ingen hoteller samsvarer med filtrene dine. Prøv å justere søket for å finne det perfekte oppholdet.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "Det ser ut til at ingen steder samsvarer med filtrene dine. Prøv å justere søket for å finne det perfekte oppholdet.",
|
||||||
"Jacuzzi": "Boblebad",
|
"Jacuzzi": "Boblebad",
|
||||||
"Join Scandic Friends": "Bli med i Scandic Friends",
|
"Join Scandic Friends": "Bli med i Scandic Friends",
|
||||||
"Join at no cost": "Bli med uten kostnad",
|
"Join at no cost": "Bli med uten kostnad",
|
||||||
@@ -426,7 +427,8 @@
|
|||||||
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
||||||
"No content published": "Ingen innhold publisert",
|
"No content published": "Ingen innhold publisert",
|
||||||
"No hotels match your filters": "Ingen rom samsvarte med filtrene dine",
|
"No hotels match your filters": "Ingen rom samsvarte med filtrene dine",
|
||||||
"No matching location found": "Fant ingen samsvarende plassering",
|
"No matching hotels found": "Fant ingen samsvarende hotell",
|
||||||
|
"No matching locations found": "Fant ingen samsvarende plassering",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "Ingen priser tilgjengelig",
|
"No prices available": "Ingen priser tilgjengelig",
|
||||||
"No results": "Ingen resultater",
|
"No results": "Ingen resultater",
|
||||||
|
|||||||
@@ -320,6 +320,7 @@
|
|||||||
"Is there anything else you would like us to know before your arrival?": "Är det något mer du vill att vi ska veta innan din ankomst?",
|
"Is there anything else you would like us to know before your arrival?": "Är det något mer du vill att vi ska veta innan din ankomst?",
|
||||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det gick inte att hantera dina kommunikationsinställningar just nu, försök igen senare eller kontakta supporten om problemet kvarstår.",
|
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det gick inte att hantera dina kommunikationsinställningar just nu, försök igen senare eller kontakta supporten om problemet kvarstår.",
|
||||||
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det verkar som att inga hotell matchar dina filter. Prova att justera din sökning för att hitta den perfekta vistelsen.",
|
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det verkar som att inga hotell matchar dina filter. Prova att justera din sökning för att hitta den perfekta vistelsen.",
|
||||||
|
"It looks like no location match your filters. Try adjusting your search to find the perfect stay.": "Det verkar som att ingen plats matchar dina filter. Prova att justera din sökning för att hitta den perfekta vistelsen.",
|
||||||
"Jacuzzi": "Jacuzzi",
|
"Jacuzzi": "Jacuzzi",
|
||||||
"Join Scandic Friends": "Gå med i Scandic Friends",
|
"Join Scandic Friends": "Gå med i Scandic Friends",
|
||||||
"Join at no cost": "Gå med utan kostnad",
|
"Join at no cost": "Gå med utan kostnad",
|
||||||
@@ -426,7 +427,8 @@
|
|||||||
"No charges were made.": "Inga avgifter har debiterats.",
|
"No charges were made.": "Inga avgifter har debiterats.",
|
||||||
"No content published": "Inget innehåll publicerat",
|
"No content published": "Inget innehåll publicerat",
|
||||||
"No hotels match your filters": "Inga rum matchade dina filter",
|
"No hotels match your filters": "Inga rum matchade dina filter",
|
||||||
"No matching location found": "Ingen matchande plats hittades",
|
"No matching hotels found": "Inget matchande hotell hittades",
|
||||||
|
"No matching locations found": "Ingen matchande plats hittades",
|
||||||
"No membership benefits applied": "No membership benefits applied",
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "Inga priser tillgängliga",
|
"No prices available": "Inga priser tillgängliga",
|
||||||
"No results": "Inga resultat",
|
"No results": "Inga resultat",
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ query GetDestinationCityPage($locale: String!, $uid: String!) {
|
|||||||
city_norway
|
city_norway
|
||||||
city_poland
|
city_poland
|
||||||
city_sweden
|
city_sweden
|
||||||
|
location {
|
||||||
|
longitude
|
||||||
|
latitude
|
||||||
|
default_zoom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
heading
|
heading
|
||||||
preamble
|
preamble
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ query GetDestinationCountryPage($locale: String!, $uid: String!) {
|
|||||||
title
|
title
|
||||||
destination_settings {
|
destination_settings {
|
||||||
country
|
country
|
||||||
|
location {
|
||||||
|
longitude
|
||||||
|
latitude
|
||||||
|
default_zoom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
heading
|
heading
|
||||||
preamble
|
preamble
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ query GetDestinationOverviewPage($locale: String!, $uid: String!) {
|
|||||||
__typename
|
__typename
|
||||||
...CardGallery_DestinationOverviewPage
|
...CardGallery_DestinationOverviewPage
|
||||||
}
|
}
|
||||||
|
location {
|
||||||
|
longitude
|
||||||
|
latitude
|
||||||
|
default_zoom
|
||||||
|
}
|
||||||
system {
|
system {
|
||||||
...System
|
...System
|
||||||
created_at
|
created_at
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "../schemas/blocks/accordion"
|
} from "../schemas/blocks/accordion"
|
||||||
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
||||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
|
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||||
import {
|
import {
|
||||||
linkRefsUnionSchema,
|
linkRefsUnionSchema,
|
||||||
linkUnionSchema,
|
linkUnionSchema,
|
||||||
@@ -24,6 +25,39 @@ import {
|
|||||||
} from "@/types/components/tracking"
|
} from "@/types/components/tracking"
|
||||||
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
|
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
|
||||||
|
|
||||||
|
const destinationCityPageDestinationSettingsSchema = z
|
||||||
|
.object({
|
||||||
|
city_denmark: z.string().optional().nullable(),
|
||||||
|
city_finland: z.string().optional().nullable(),
|
||||||
|
city_germany: z.string().optional().nullable(),
|
||||||
|
city_poland: z.string().optional().nullable(),
|
||||||
|
city_norway: z.string().optional().nullable(),
|
||||||
|
city_sweden: z.string().optional().nullable(),
|
||||||
|
location: mapLocationSchema,
|
||||||
|
})
|
||||||
|
.transform(
|
||||||
|
({
|
||||||
|
city_denmark,
|
||||||
|
city_finland,
|
||||||
|
city_germany,
|
||||||
|
city_norway,
|
||||||
|
city_poland,
|
||||||
|
city_sweden,
|
||||||
|
location,
|
||||||
|
}) => {
|
||||||
|
const cities = [
|
||||||
|
city_denmark,
|
||||||
|
city_finland,
|
||||||
|
city_germany,
|
||||||
|
city_poland,
|
||||||
|
city_norway,
|
||||||
|
city_sweden,
|
||||||
|
].filter((city): city is string => Boolean(city))
|
||||||
|
|
||||||
|
return { city: cities[0], location }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const destinationCityListDataSchema = z
|
export const destinationCityListDataSchema = z
|
||||||
.object({
|
.object({
|
||||||
all_destination_city_page: z.object({
|
all_destination_city_page: z.object({
|
||||||
@@ -31,36 +65,7 @@ export const destinationCityListDataSchema = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
destination_settings: z
|
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||||
.object({
|
|
||||||
city_denmark: z.string().optional().nullable(),
|
|
||||||
city_finland: z.string().optional().nullable(),
|
|
||||||
city_germany: z.string().optional().nullable(),
|
|
||||||
city_poland: z.string().optional().nullable(),
|
|
||||||
city_norway: z.string().optional().nullable(),
|
|
||||||
city_sweden: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.transform(
|
|
||||||
({
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_norway,
|
|
||||||
city_poland,
|
|
||||||
city_sweden,
|
|
||||||
}) => {
|
|
||||||
const cities = [
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_poland,
|
|
||||||
city_norway,
|
|
||||||
city_sweden,
|
|
||||||
].filter((city): city is string => Boolean(city))
|
|
||||||
|
|
||||||
return { city: cities[0] }
|
|
||||||
}
|
|
||||||
),
|
|
||||||
sort_order: z.number().nullable(),
|
sort_order: z.number().nullable(),
|
||||||
preamble: z.string(),
|
preamble: z.string(),
|
||||||
experiences: z
|
experiences: z
|
||||||
@@ -112,71 +117,11 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
destinationCityPageContent,
|
destinationCityPageContent,
|
||||||
])
|
])
|
||||||
|
|
||||||
export const destinationCityPageDestinationSettingsSchema = z
|
|
||||||
.object({
|
|
||||||
city_denmark: z.string().optional().nullable(),
|
|
||||||
city_finland: z.string().optional().nullable(),
|
|
||||||
city_germany: z.string().optional().nullable(),
|
|
||||||
city_poland: z.string().optional().nullable(),
|
|
||||||
city_norway: z.string().optional().nullable(),
|
|
||||||
city_sweden: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.transform(
|
|
||||||
({
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_norway,
|
|
||||||
city_poland,
|
|
||||||
city_sweden,
|
|
||||||
}) => {
|
|
||||||
const cities = [
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_poland,
|
|
||||||
city_norway,
|
|
||||||
city_sweden,
|
|
||||||
].filter((city): city is string => Boolean(city))
|
|
||||||
|
|
||||||
return { city: cities[0] }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const destinationCityPageSchema = z
|
export const destinationCityPageSchema = z
|
||||||
.object({
|
.object({
|
||||||
destination_city_page: z.object({
|
destination_city_page: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
destination_settings: z
|
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||||
.object({
|
|
||||||
city_denmark: z.string().optional().nullable(),
|
|
||||||
city_finland: z.string().optional().nullable(),
|
|
||||||
city_germany: z.string().optional().nullable(),
|
|
||||||
city_poland: z.string().optional().nullable(),
|
|
||||||
city_norway: z.string().optional().nullable(),
|
|
||||||
city_sweden: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.transform(
|
|
||||||
({
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_norway,
|
|
||||||
city_poland,
|
|
||||||
city_sweden,
|
|
||||||
}) => {
|
|
||||||
const cities = [
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_poland,
|
|
||||||
city_norway,
|
|
||||||
city_sweden,
|
|
||||||
].filter((city): city is string => Boolean(city))
|
|
||||||
|
|
||||||
return { city: cities[0] }
|
|
||||||
}
|
|
||||||
),
|
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
preamble: z.string(),
|
preamble: z.string(),
|
||||||
experiences: z
|
experiences: z
|
||||||
@@ -254,36 +199,7 @@ export const cityPageUrlsSchema = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
destination_settings: z
|
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||||
.object({
|
|
||||||
city_denmark: z.string().optional().nullable(),
|
|
||||||
city_finland: z.string().optional().nullable(),
|
|
||||||
city_germany: z.string().optional().nullable(),
|
|
||||||
city_poland: z.string().optional().nullable(),
|
|
||||||
city_norway: z.string().optional().nullable(),
|
|
||||||
city_sweden: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.transform(
|
|
||||||
({
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_norway,
|
|
||||||
city_poland,
|
|
||||||
city_sweden,
|
|
||||||
}) => {
|
|
||||||
const cities = [
|
|
||||||
city_denmark,
|
|
||||||
city_finland,
|
|
||||||
city_germany,
|
|
||||||
city_poland,
|
|
||||||
city_norway,
|
|
||||||
city_sweden,
|
|
||||||
].filter((city): city is string => Boolean(city))
|
|
||||||
|
|
||||||
return { city: cities[0] }
|
|
||||||
}
|
|
||||||
),
|
|
||||||
system: systemSchema,
|
system: systemSchema,
|
||||||
})
|
})
|
||||||
.transform((data) => {
|
.transform((data) => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "../schemas/blocks/accordion"
|
} from "../schemas/blocks/accordion"
|
||||||
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
||||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
|
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||||
import {
|
import {
|
||||||
linkRefsUnionSchema,
|
linkRefsUnionSchema,
|
||||||
linkUnionSchema,
|
linkUnionSchema,
|
||||||
@@ -52,6 +53,7 @@ export const destinationCountryPageSchema = z
|
|||||||
title: z.string(),
|
title: z.string(),
|
||||||
destination_settings: z.object({
|
destination_settings: z.object({
|
||||||
country: z.nativeEnum(Country),
|
country: z.nativeEnum(Country),
|
||||||
|
location: mapLocationSchema,
|
||||||
}),
|
}),
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
preamble: z.string(),
|
preamble: z.string(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
cardGalleryRefsSchema,
|
cardGalleryRefsSchema,
|
||||||
cardGallerySchema,
|
cardGallerySchema,
|
||||||
} from "../schemas/blocks/cardGallery"
|
} from "../schemas/blocks/cardGallery"
|
||||||
|
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
import { DestinationOverviewPageEnum } from "@/types/enums/destinationOverviewPage"
|
import { DestinationOverviewPageEnum } from "@/types/enums/destinationOverviewPage"
|
||||||
@@ -26,6 +27,7 @@ export const destinationOverviewPageSchema = z.object({
|
|||||||
destination_overview_page: z.object({
|
destination_overview_page: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options),
|
blocks: discriminatedUnionArray(blocksSchema.options),
|
||||||
|
location: mapLocationSchema,
|
||||||
system: systemSchema.merge(
|
system: systemSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const mapLocationSchema = z
|
||||||
|
.object({
|
||||||
|
longitude: z.number().nullable(),
|
||||||
|
latitude: z.number().nullable(),
|
||||||
|
default_zoom: z.number().nullable(),
|
||||||
|
})
|
||||||
|
.nullish()
|
||||||
|
.transform((val) => {
|
||||||
|
if (val) {
|
||||||
|
const longitude = val.longitude
|
||||||
|
const latitude = val.latitude
|
||||||
|
const default_zoom = val.default_zoom || 3
|
||||||
|
|
||||||
|
if (longitude !== null && latitude !== null) {
|
||||||
|
return {
|
||||||
|
longitude,
|
||||||
|
latitude,
|
||||||
|
default_zoom: Math.round(default_zoom),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
5
apps/scandic-web/types/components/mapLocation.ts
Normal file
5
apps/scandic-web/types/components/mapLocation.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { z } from "zod"
|
||||||
|
|
||||||
|
import type { mapLocationSchema } from "@/server/routers/contentstack/schemas/mapLocation"
|
||||||
|
|
||||||
|
export type MapLocation = z.output<typeof mapLocationSchema>
|
||||||
Reference in New Issue
Block a user