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 DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
@@ -16,6 +17,8 @@ import CityListingSkeleton from "./CityListingSkeleton"
|
||||
|
||||
import styles from "./cityListing.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
export default function CityListing() {
|
||||
const intl = useIntl()
|
||||
const scrollRef = useRef<HTMLElement>(null)
|
||||
@@ -60,23 +63,35 @@ export default function CityListing() {
|
||||
listType="city"
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
{activeCities.length === 0 ? (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No matching locations found" })}
|
||||
text={intl.formatMessage({
|
||||
id: "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||
})}
|
||||
/>
|
||||
) : 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>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useIntl } from "react-intl"
|
||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||
|
||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
|
||||
@@ -16,6 +17,7 @@ import { getVisibleHotels } from "./utils"
|
||||
|
||||
import styles from "./hotelList.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
|
||||
export default function HotelList() {
|
||||
@@ -71,13 +73,23 @@ export default function HotelList() {
|
||||
listType="hotel"
|
||||
/>
|
||||
</div>
|
||||
<ul className={styles.hotelList}>
|
||||
{visibleHotels.map(({ hotel, url }) => (
|
||||
<li key={hotel.operaId}>
|
||||
<HotelListItem hotel={hotel} url={url} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{activeHotels.length === 0 ? (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No matching hotels found" })}
|
||||
text={intl.formatMessage({
|
||||
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<ul className={styles.hotelList}>
|
||||
{visibleHotels.map(({ hotel, url }) => (
|
||||
<li key={hotel.operaId}>
|
||||
<HotelListItem hotel={hotel} url={url} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,15 +11,22 @@ import HotelList from "./HotelList"
|
||||
|
||||
import styles from "./cityMap.module.css"
|
||||
|
||||
import type { MapLocation } from "@/types/components/mapLocation"
|
||||
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
interface CityMapProps {
|
||||
mapId: string
|
||||
apiKey: string
|
||||
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 { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||
(state) => ({
|
||||
@@ -30,7 +37,13 @@ export default function CityMap({ mapId, apiKey, city }: CityMapProps) {
|
||||
)
|
||||
|
||||
return (
|
||||
<Map hotels={activeHotels} mapId={mapId} apiKey={apiKey} pageType="city">
|
||||
<Map
|
||||
hotels={activeHotels}
|
||||
mapId={mapId}
|
||||
apiKey={apiKey}
|
||||
pageType="city"
|
||||
defaultLocation={defaultLocation}
|
||||
>
|
||||
<Title
|
||||
level="h2"
|
||||
as="h3"
|
||||
|
||||
@@ -69,7 +69,10 @@ export default async function DestinationCityPage() {
|
||||
)}
|
||||
|
||||
{destination_settings.city && (
|
||||
<StaticMap city={destination_settings.city} />
|
||||
<StaticMap
|
||||
city={destination_settings.city}
|
||||
location={destination_settings.location}
|
||||
/>
|
||||
)}
|
||||
</SidebarContentWrapper>
|
||||
</aside>
|
||||
@@ -78,6 +81,7 @@ export default async function DestinationCityPage() {
|
||||
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
||||
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
||||
city={city}
|
||||
defaultLocation={destination_settings.location}
|
||||
/>
|
||||
</HotelDataContainer>
|
||||
</Suspense>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
|
||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||
|
||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
import CityListItem from "../CityListItem"
|
||||
@@ -12,6 +13,8 @@ import CityListSkeleton from "./CityListSkeleton"
|
||||
|
||||
import styles from "./cityList.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
export default function CityList() {
|
||||
const intl = useIntl()
|
||||
const { filters, sortItems, activeCities, isLoading } =
|
||||
@@ -39,13 +42,23 @@ export default function CityList() {
|
||||
listType="city"
|
||||
/>
|
||||
</div>
|
||||
<ul className={styles.cityList}>
|
||||
{activeCities.map((city) => (
|
||||
<li key={city.system.uid}>
|
||||
<CityListItem city={city} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{activeCities.length === 0 ? (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No matching locations found" })}
|
||||
text={intl.formatMessage({
|
||||
id: "It looks like no location match your filters. Try adjusting your search to find the perfect stay.",
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<ul className={styles.cityList}>
|
||||
{activeCities.map((city) => (
|
||||
<li key={city.system.uid}>
|
||||
<CityListItem city={city} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,16 +12,20 @@ import CityList from "./CityList"
|
||||
|
||||
import styles from "./countryMap.module.css"
|
||||
|
||||
import type { MapLocation } from "@/types/components/mapLocation"
|
||||
|
||||
interface CountryMapProps {
|
||||
mapId: string
|
||||
apiKey: string
|
||||
country: string
|
||||
defaultLocation: MapLocation
|
||||
}
|
||||
|
||||
export default function CountryMap({
|
||||
mapId,
|
||||
apiKey,
|
||||
country,
|
||||
defaultLocation,
|
||||
}: CountryMapProps) {
|
||||
const intl = useIntl()
|
||||
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||
@@ -33,7 +37,13 @@ export default function CountryMap({
|
||||
)
|
||||
|
||||
return (
|
||||
<Map hotels={activeHotels} mapId={mapId} apiKey={apiKey} pageType="country">
|
||||
<Map
|
||||
hotels={activeHotels}
|
||||
mapId={mapId}
|
||||
apiKey={apiKey}
|
||||
pageType="country"
|
||||
defaultLocation={defaultLocation}
|
||||
>
|
||||
<Title
|
||||
level="h2"
|
||||
as="h3"
|
||||
|
||||
@@ -71,19 +71,22 @@ export default async function DestinationCountryPage() {
|
||||
sidePeekContent={sidepeek_content}
|
||||
/>
|
||||
)}
|
||||
|
||||
<StaticMap country={destination_settings.country} />
|
||||
<StaticMap
|
||||
country={destination_settings.country}
|
||||
location={destination_settings.location}
|
||||
/>
|
||||
</SidebarContentWrapper>
|
||||
</aside>
|
||||
</div>
|
||||
<CountryMap
|
||||
country={translatedCountry}
|
||||
defaultLocation={destination_settings.location}
|
||||
apiKey={env.GOOGLE_STATIC_MAP_KEY}
|
||||
mapId={env.GOOGLE_DYNAMIC_MAP_ID}
|
||||
/>
|
||||
</CityDataContainer>
|
||||
</Suspense>
|
||||
<TrackingSDK pageData={tracking} />
|
||||
<TrackingSDK pageData={tracking} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,15 @@ import MapProvider from "../../Map/MapProvider"
|
||||
import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils"
|
||||
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()
|
||||
|
||||
if (!hotelData) {
|
||||
@@ -19,11 +27,23 @@ export default async function OverviewMapContainer() {
|
||||
|
||||
const markers = getHotelMapMarkers(hotelData)
|
||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||
const defaultCoordinates = defaultLocation
|
||||
? {
|
||||
lat: defaultLocation.latitude,
|
||||
lng: defaultLocation.longitude,
|
||||
}
|
||||
: null
|
||||
const defaultZoom = defaultLocation?.default_zoom ?? 3
|
||||
|
||||
return (
|
||||
<MapProvider apiKey={googleMapsApiKey} pageType="overview">
|
||||
<InputForm />
|
||||
<DynamicMap mapId={googleMapId} markers={markers}>
|
||||
<DynamicMap
|
||||
mapId={googleMapId}
|
||||
markers={markers}
|
||||
defaultCoordinates={defaultCoordinates}
|
||||
defaultZoom={defaultZoom}
|
||||
>
|
||||
<MapContent geojson={geoJson} />
|
||||
</DynamicMap>
|
||||
</MapProvider>
|
||||
|
||||
@@ -26,7 +26,9 @@ export default async function DestinationOverviewPage() {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.mapContainer}>
|
||||
<OverviewMapContainer />
|
||||
<OverviewMapContainer
|
||||
defaultLocation={destinationOverviewPage.location}
|
||||
/>
|
||||
</div>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.blocks}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useIntl } from "react-intl"
|
||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||
|
||||
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
@@ -16,6 +17,8 @@ import HotelListingSkeleton from "./HotelListingSkeleton"
|
||||
|
||||
import styles from "./hotelListing.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
export default function HotelListing() {
|
||||
const intl = useIntl()
|
||||
const scrollRef = useRef<HTMLElement>(null)
|
||||
@@ -60,23 +63,35 @@ export default function HotelListing() {
|
||||
listType="hotel"
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
{activeHotels.length === 0 ? (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({ id: "No matching hotels found" })}
|
||||
text={intl.formatMessage({
|
||||
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||
})}
|
||||
/>
|
||||
) : 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>
|
||||
)
|
||||
|
||||
@@ -14,17 +14,24 @@ import styles from "./dynamicMap.module.css"
|
||||
|
||||
import type { DestinationMarker } from "@/types/components/maps/destinationMarkers"
|
||||
|
||||
const BACKUP_COORDINATES = {
|
||||
lat: 59.3293,
|
||||
lng: 18.0686,
|
||||
}
|
||||
|
||||
interface DynamicMapProps {
|
||||
markers: DestinationMarker[]
|
||||
mapId: string
|
||||
onTilesLoaded?: () => void
|
||||
defaultCoordinates: google.maps.LatLngLiteral | null
|
||||
defaultZoom: number
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export default function DynamicMap({
|
||||
markers,
|
||||
mapId,
|
||||
onTilesLoaded,
|
||||
defaultCoordinates,
|
||||
defaultZoom,
|
||||
onClose,
|
||||
children,
|
||||
}: PropsWithChildren<DynamicMapProps>) {
|
||||
@@ -33,13 +40,17 @@ export default function DynamicMap({
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const bounds = new google.maps.LatLngBounds()
|
||||
markers.forEach((marker) => {
|
||||
bounds.extend(marker.coordinates)
|
||||
})
|
||||
map.fitBounds(bounds)
|
||||
if (markers.length) {
|
||||
const bounds = new google.maps.LatLngBounds()
|
||||
markers.forEach((marker) => {
|
||||
bounds.extend(marker.coordinates)
|
||||
})
|
||||
map.fitBounds(bounds, 100)
|
||||
} else if (defaultCoordinates) {
|
||||
map.setCenter(defaultCoordinates)
|
||||
}
|
||||
}
|
||||
}, [map, markers])
|
||||
}, [map, markers, defaultCoordinates])
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && onClose) {
|
||||
@@ -61,13 +72,10 @@ export default function DynamicMap({
|
||||
}
|
||||
|
||||
const mapOptions: MapProps = {
|
||||
defaultCenter: markers[0]?.coordinates || {
|
||||
lat: 59.3293,
|
||||
lng: 18.0686,
|
||||
}, // Default center will be overridden by the bounds
|
||||
defaultCenter: defaultCoordinates || BACKUP_COORDINATES, // Default center will be overridden by the bounds
|
||||
minZoom: 3,
|
||||
maxZoom: 18,
|
||||
defaultZoom: 5,
|
||||
defaultZoom,
|
||||
disableDefaultUI: true,
|
||||
clickableIcons: false,
|
||||
gestureHandling: "greedy",
|
||||
@@ -76,9 +84,7 @@ export default function DynamicMap({
|
||||
|
||||
return (
|
||||
<div className={styles.mapWrapper}>
|
||||
<Map {...mapOptions} onTilesLoaded={onTilesLoaded}>
|
||||
{children}
|
||||
</Map>
|
||||
<Map {...mapOptions}>{children}</Map>
|
||||
<div className={styles.ctaButtons}>
|
||||
{onClose && (
|
||||
<Button
|
||||
|
||||
@@ -20,6 +20,7 @@ import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "./utils"
|
||||
|
||||
import styles from "./map.module.css"
|
||||
|
||||
import type { MapLocation } from "@/types/components/mapLocation"
|
||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
|
||||
interface MapProps {
|
||||
@@ -27,14 +28,16 @@ interface MapProps {
|
||||
mapId: string
|
||||
apiKey: string
|
||||
pageType: "city" | "country"
|
||||
defaultLocation: MapLocation
|
||||
}
|
||||
|
||||
export default function Map({
|
||||
hotels,
|
||||
mapId,
|
||||
apiKey,
|
||||
children,
|
||||
defaultLocation,
|
||||
pageType,
|
||||
children,
|
||||
}: PropsWithChildren<MapProps>) {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
@@ -48,6 +51,14 @@ export default function Map({
|
||||
|
||||
const markers = getHotelMapMarkers(hotels)
|
||||
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)
|
||||
const handleMapHeight = useCallback(() => {
|
||||
@@ -112,7 +123,13 @@ export default function Map({
|
||||
aria-label={"Mapview"}
|
||||
>
|
||||
<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} />
|
||||
</DynamicMap>
|
||||
</Dialog>
|
||||
|
||||
@@ -5,29 +5,48 @@ import MapButton from "./MapButton"
|
||||
|
||||
import styles from "./staticMap.module.css"
|
||||
|
||||
import type { MapLocation } from "@/types/components/mapLocation"
|
||||
|
||||
interface StaticMapProps {
|
||||
country?: 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({
|
||||
country,
|
||||
city,
|
||||
location,
|
||||
}: StaticMapProps) {
|
||||
const intl = await getIntl()
|
||||
const altText = city
|
||||
? intl.formatMessage({ id: "Map of the city" })
|
||||
: intl.formatMessage({ id: "Map of the country" })
|
||||
const zoomLevel = city ? 10 : 3
|
||||
const coordinates = location
|
||||
? { lat: location.latitude, lng: location.longitude }
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div className={styles.mapWrapper}>
|
||||
<StaticMap
|
||||
country={country}
|
||||
city={city}
|
||||
coordinates={coordinates}
|
||||
width={320}
|
||||
height={200}
|
||||
zoomLevel={zoomLevel}
|
||||
zoomLevel={getZoomLevel(location?.default_zoom, !!country)}
|
||||
altText={altText}
|
||||
/>
|
||||
<MapButton className={styles.button} />
|
||||
|
||||
@@ -50,6 +50,15 @@
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -17,6 +17,7 @@ import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import Alert from "../TempDesignSystem/Alert"
|
||||
import Filter from "./Filter"
|
||||
import Sort from "./Sort"
|
||||
|
||||
@@ -26,6 +27,7 @@ import type {
|
||||
CategorizedFilters,
|
||||
SortItem,
|
||||
} from "@/types/components/destinationFilterAndSort"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
interface HotelFilterAndSortProps {
|
||||
filters: CategorizedFilters
|
||||
@@ -62,12 +64,24 @@ export default function DestinationFilterAndSort({
|
||||
resetPendingValues: state.actions.resetPendingValues,
|
||||
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) {
|
||||
setIsLoading(true)
|
||||
const sort = pendingSort
|
||||
const filters = pendingFilters
|
||||
|
||||
const parsedUrl = new URL(window.location.href)
|
||||
const searchParams = parsedUrl.searchParams
|
||||
if (sort === defaultSort && searchParams.has("sort")) {
|
||||
@@ -75,7 +89,6 @@ export default function DestinationFilterAndSort({
|
||||
} else if (sort !== defaultSort) {
|
||||
searchParams.set("sort", sort)
|
||||
}
|
||||
|
||||
const [firstFilter, ...remainingFilters] = filters
|
||||
|
||||
parsedUrl.pathname = basePath
|
||||
@@ -138,6 +151,16 @@ export default function DestinationFilterAndSort({
|
||||
<Divider color="subtle" />
|
||||
<Filter filters={filters} />
|
||||
</div>
|
||||
{pendingCount === 0 && (
|
||||
<div className={styles.alertWrapper}>
|
||||
<Alert
|
||||
type={AlertTypeEnum.Warning}
|
||||
heading={alertHeading}
|
||||
text={alertText}
|
||||
ariaRole="status"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<footer className={styles.footer}>
|
||||
<Button
|
||||
onClick={clearPendingFilters}
|
||||
@@ -151,6 +174,7 @@ export default function DestinationFilterAndSort({
|
||||
intent="tertiary"
|
||||
size="large"
|
||||
theme="base"
|
||||
disabled={pendingCount === 0}
|
||||
onClick={() => submitAndClose(close)}
|
||||
>
|
||||
{intl.formatMessage(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import type { AriaRole } from "react"
|
||||
|
||||
import type { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { SidepeekContent } from "@/types/trpc/routers/contentstack/siteConfig"
|
||||
@@ -21,4 +22,6 @@ export interface AlertProps extends VariantProps<typeof alertVariants> {
|
||||
title: string
|
||||
keepSearchParams?: boolean
|
||||
} | null
|
||||
ariaRole?: AriaRole
|
||||
ariaLive?: "off" | "assertive" | "polite"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
import Link from "../Link"
|
||||
@@ -19,6 +21,8 @@ export default function Alert({
|
||||
phoneContact,
|
||||
sidepeekCtaText,
|
||||
sidepeekContent,
|
||||
ariaLive,
|
||||
ariaRole,
|
||||
}: AlertProps) {
|
||||
const classNames = alertVariants({
|
||||
className,
|
||||
@@ -31,7 +35,7 @@ export default function Alert({
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<section className={classNames}>
|
||||
<section className={classNames} role={ariaRole} aria-live={ariaLive}>
|
||||
<div className={styles.content}>
|
||||
<span className={styles.iconWrapper}>
|
||||
<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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||
"Join at no cost": "Tilmeld dig uden omkostninger",
|
||||
@@ -426,7 +427,8 @@
|
||||
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
||||
"No content published": "Intet indhold offentliggjort",
|
||||
"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 prices available": "Ingen tilgængelige priser",
|
||||
"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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||
"Join at no cost": "Kostenlos beitreten",
|
||||
@@ -428,7 +429,8 @@
|
||||
"No charges were made.": "Es wurden keine Gebühren erhoben.",
|
||||
"No content published": "Kein Inhalt veröffentlicht",
|
||||
"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 prices available": "Keine Preise verfügbar",
|
||||
"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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Join Scandic Friends",
|
||||
"Join at no cost": "Join at no cost",
|
||||
@@ -429,7 +430,8 @@
|
||||
"No charges were made.": "No charges were made.",
|
||||
"No content published": "No content published",
|
||||
"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 prices available": "No prices available",
|
||||
"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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Liity jäseneksi",
|
||||
"Join at no cost": "Liity maksutta",
|
||||
@@ -427,7 +428,8 @@
|
||||
"No charges were made.": "Ei maksuja tehty.",
|
||||
"No content published": "Ei julkaistua sisältöä",
|
||||
"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 prices available": "Hintoja ei ole saatavilla",
|
||||
"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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Bli med i Scandic Friends",
|
||||
"Join at no cost": "Bli med uten kostnad",
|
||||
@@ -426,7 +427,8 @@
|
||||
"No charges were made.": "Ingen gebyrer blev opkrævet.",
|
||||
"No content published": "Ingen innhold publisert",
|
||||
"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 prices available": "Ingen priser tilgjengelig",
|
||||
"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?",
|
||||
"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 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",
|
||||
"Join Scandic Friends": "Gå med i Scandic Friends",
|
||||
"Join at no cost": "Gå med utan kostnad",
|
||||
@@ -426,7 +427,8 @@
|
||||
"No charges were made.": "Inga avgifter har debiterats.",
|
||||
"No content published": "Inget innehåll publicerat",
|
||||
"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 prices available": "Inga priser tillgängliga",
|
||||
"No results": "Inga resultat",
|
||||
|
||||
@@ -33,6 +33,11 @@ query GetDestinationCityPage($locale: String!, $uid: String!) {
|
||||
city_norway
|
||||
city_poland
|
||||
city_sweden
|
||||
location {
|
||||
longitude
|
||||
latitude
|
||||
default_zoom
|
||||
}
|
||||
}
|
||||
heading
|
||||
preamble
|
||||
|
||||
@@ -28,6 +28,11 @@ query GetDestinationCountryPage($locale: String!, $uid: String!) {
|
||||
title
|
||||
destination_settings {
|
||||
country
|
||||
location {
|
||||
longitude
|
||||
latitude
|
||||
default_zoom
|
||||
}
|
||||
}
|
||||
heading
|
||||
preamble
|
||||
|
||||
@@ -10,6 +10,11 @@ query GetDestinationOverviewPage($locale: String!, $uid: String!) {
|
||||
__typename
|
||||
...CardGallery_DestinationOverviewPage
|
||||
}
|
||||
location {
|
||||
longitude
|
||||
latitude
|
||||
default_zoom
|
||||
}
|
||||
system {
|
||||
...System
|
||||
created_at
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../schemas/blocks/accordion"
|
||||
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||
import {
|
||||
linkRefsUnionSchema,
|
||||
linkUnionSchema,
|
||||
@@ -24,6 +25,39 @@ import {
|
||||
} from "@/types/components/tracking"
|
||||
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
|
||||
.object({
|
||||
all_destination_city_page: z.object({
|
||||
@@ -31,36 +65,7 @@ export const destinationCityListDataSchema = z
|
||||
z
|
||||
.object({
|
||||
heading: z.string(),
|
||||
destination_settings: 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] }
|
||||
}
|
||||
),
|
||||
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||
sort_order: z.number().nullable(),
|
||||
preamble: z.string(),
|
||||
experiences: z
|
||||
@@ -112,71 +117,11 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
||||
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
|
||||
.object({
|
||||
destination_city_page: z.object({
|
||||
title: z.string(),
|
||||
destination_settings: 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] }
|
||||
}
|
||||
),
|
||||
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||
heading: z.string(),
|
||||
preamble: z.string(),
|
||||
experiences: z
|
||||
@@ -254,36 +199,7 @@ export const cityPageUrlsSchema = z
|
||||
z
|
||||
.object({
|
||||
url: z.string(),
|
||||
destination_settings: 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] }
|
||||
}
|
||||
),
|
||||
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||
system: systemSchema,
|
||||
})
|
||||
.transform((data) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../schemas/blocks/accordion"
|
||||
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
|
||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||
import {
|
||||
linkRefsUnionSchema,
|
||||
linkUnionSchema,
|
||||
@@ -52,6 +53,7 @@ export const destinationCountryPageSchema = z
|
||||
title: z.string(),
|
||||
destination_settings: z.object({
|
||||
country: z.nativeEnum(Country),
|
||||
location: mapLocationSchema,
|
||||
}),
|
||||
heading: z.string(),
|
||||
preamble: z.string(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
cardGalleryRefsSchema,
|
||||
cardGallerySchema,
|
||||
} from "../schemas/blocks/cardGallery"
|
||||
import { mapLocationSchema } from "../schemas/mapLocation"
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
import { DestinationOverviewPageEnum } from "@/types/enums/destinationOverviewPage"
|
||||
@@ -26,6 +27,7 @@ export const destinationOverviewPageSchema = z.object({
|
||||
destination_overview_page: z.object({
|
||||
title: z.string(),
|
||||
blocks: discriminatedUnionArray(blocksSchema.options),
|
||||
location: mapLocationSchema,
|
||||
system: systemSchema.merge(
|
||||
z.object({
|
||||
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