diff --git a/apps/scandic-web/components/ContentType/DestinationPage/CityListing/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/CityListing/index.tsx index d36aca2d3..641745d77 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/CityListing/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/CityListing/index.tsx @@ -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(null) @@ -60,23 +63,35 @@ export default function CityListing() { listType="city" /> - - {activeCities.length > 5 ? ( - - ) : null} - {showBackToTop && ( - + ) : ( + <> +
    + {activeCities.map((city) => ( +
  • + +
  • + ))} +
+ {activeCities.length > 5 ? ( + + ) : null} + {showBackToTop && ( + + )} + )} ) diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelList/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelList/index.tsx index b6d3e3c7e..84a2bdf24 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelList/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelList/index.tsx @@ -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" /> -
    - {visibleHotels.map(({ hotel, url }) => ( -
  • - -
  • - ))} -
+ {activeHotels.length === 0 ? ( + + ) : ( +
    + {visibleHotels.map(({ hotel, url }) => ( +
  • + +
  • + ))} +
+ )} ) } diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/index.tsx index a2fe2ab52..6006f6cea 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/index.tsx @@ -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 ( - + + <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> diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/CityList/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/CityList/index.tsx index 16129d480..8dff20877 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/CityList/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/CityList/index.tsx @@ -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> ) } diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/index.tsx index 6a90b6df3..c552e5856 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/CountryMap/index.tsx @@ -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" diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx index 0015b9f26..ad1a738eb 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx @@ -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} /> </> ) } diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx index 75a5d2da5..2da8a46af 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx @@ -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> diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/index.tsx index 18439851b..18f7c9f65 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/index.tsx @@ -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}> diff --git a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx index 0fe559632..59d052368 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx @@ -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> ) diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Map/DynamicMap/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Map/DynamicMap/index.tsx index 6a800adc4..0321597b4 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Map/DynamicMap/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Map/DynamicMap/index.tsx @@ -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 diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx index 8f25cfdf5..6eae690cb 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx @@ -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> diff --git a/apps/scandic-web/components/ContentType/DestinationPage/StaticMap/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/StaticMap/index.tsx index ab5c86a31..527ecd69d 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/StaticMap/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/StaticMap/index.tsx @@ -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} /> diff --git a/apps/scandic-web/components/DestinationFilterAndSort/destinationFilterAndSort.module.css b/apps/scandic-web/components/DestinationFilterAndSort/destinationFilterAndSort.module.css index dacef209d..c2f78e91b 100644 --- a/apps/scandic-web/components/DestinationFilterAndSort/destinationFilterAndSort.module.css +++ b/apps/scandic-web/components/DestinationFilterAndSort/destinationFilterAndSort.module.css @@ -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; diff --git a/apps/scandic-web/components/DestinationFilterAndSort/index.tsx b/apps/scandic-web/components/DestinationFilterAndSort/index.tsx index a11992b28..2d1ae4c8d 100644 --- a/apps/scandic-web/components/DestinationFilterAndSort/index.tsx +++ b/apps/scandic-web/components/DestinationFilterAndSort/index.tsx @@ -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( diff --git a/apps/scandic-web/components/TempDesignSystem/Alert/alert.ts b/apps/scandic-web/components/TempDesignSystem/Alert/alert.ts index ef866060b..8a9fe37d5 100644 --- a/apps/scandic-web/components/TempDesignSystem/Alert/alert.ts +++ b/apps/scandic-web/components/TempDesignSystem/Alert/alert.ts @@ -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" } diff --git a/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx b/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx index ce383d533..48bc91975 100644 --- a/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx @@ -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} /> diff --git a/apps/scandic-web/i18n/dictionaries/da.json b/apps/scandic-web/i18n/dictionaries/da.json index 4de2f3b9b..1374f39c5 100644 --- a/apps/scandic-web/i18n/dictionaries/da.json +++ b/apps/scandic-web/i18n/dictionaries/da.json @@ -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", diff --git a/apps/scandic-web/i18n/dictionaries/de.json b/apps/scandic-web/i18n/dictionaries/de.json index 6382c4177..4374fc22c 100644 --- a/apps/scandic-web/i18n/dictionaries/de.json +++ b/apps/scandic-web/i18n/dictionaries/de.json @@ -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", diff --git a/apps/scandic-web/i18n/dictionaries/en.json b/apps/scandic-web/i18n/dictionaries/en.json index 58436e5c7..97cb66a89 100644 --- a/apps/scandic-web/i18n/dictionaries/en.json +++ b/apps/scandic-web/i18n/dictionaries/en.json @@ -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", diff --git a/apps/scandic-web/i18n/dictionaries/fi.json b/apps/scandic-web/i18n/dictionaries/fi.json index 79782c504..39cb0e7a1 100644 --- a/apps/scandic-web/i18n/dictionaries/fi.json +++ b/apps/scandic-web/i18n/dictionaries/fi.json @@ -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", diff --git a/apps/scandic-web/i18n/dictionaries/no.json b/apps/scandic-web/i18n/dictionaries/no.json index 63a179a5b..7f0ff4462 100644 --- a/apps/scandic-web/i18n/dictionaries/no.json +++ b/apps/scandic-web/i18n/dictionaries/no.json @@ -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", diff --git a/apps/scandic-web/i18n/dictionaries/sv.json b/apps/scandic-web/i18n/dictionaries/sv.json index 5e154a75a..a41633fa5 100644 --- a/apps/scandic-web/i18n/dictionaries/sv.json +++ b/apps/scandic-web/i18n/dictionaries/sv.json @@ -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", diff --git a/apps/scandic-web/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql b/apps/scandic-web/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql index 179e8041d..ea60f6fd5 100644 --- a/apps/scandic-web/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql +++ b/apps/scandic-web/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql @@ -33,6 +33,11 @@ query GetDestinationCityPage($locale: String!, $uid: String!) { city_norway city_poland city_sweden + location { + longitude + latitude + default_zoom + } } heading preamble diff --git a/apps/scandic-web/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql b/apps/scandic-web/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql index 49c4d13e7..4ca649f08 100644 --- a/apps/scandic-web/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql +++ b/apps/scandic-web/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql @@ -28,6 +28,11 @@ query GetDestinationCountryPage($locale: String!, $uid: String!) { title destination_settings { country + location { + longitude + latitude + default_zoom + } } heading preamble diff --git a/apps/scandic-web/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql b/apps/scandic-web/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql index d9cbf41f3..9fba6e74f 100644 --- a/apps/scandic-web/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql +++ b/apps/scandic-web/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql @@ -10,6 +10,11 @@ query GetDestinationOverviewPage($locale: String!, $uid: String!) { __typename ...CardGallery_DestinationOverviewPage } + location { + longitude + latitude + default_zoom + } system { ...System created_at diff --git a/apps/scandic-web/server/routers/contentstack/destinationCityPage/output.ts b/apps/scandic-web/server/routers/contentstack/destinationCityPage/output.ts index 79b575bf0..a8c8f6d58 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCityPage/output.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCityPage/output.ts @@ -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) => { diff --git a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/output.ts b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/output.ts index a77aeb627..97c6600a2 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/output.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/output.ts @@ -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(), diff --git a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/output.ts b/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/output.ts index 88a883843..49df3b7c4 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/output.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/output.ts @@ -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(), diff --git a/apps/scandic-web/server/routers/contentstack/schemas/mapLocation.ts b/apps/scandic-web/server/routers/contentstack/schemas/mapLocation.ts new file mode 100644 index 000000000..f283ac9c5 --- /dev/null +++ b/apps/scandic-web/server/routers/contentstack/schemas/mapLocation.ts @@ -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 + }) diff --git a/apps/scandic-web/types/components/mapLocation.ts b/apps/scandic-web/types/components/mapLocation.ts new file mode 100644 index 000000000..b78ceb325 --- /dev/null +++ b/apps/scandic-web/types/components/mapLocation.ts @@ -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>