diff --git a/.env.local.example b/.env.local.example index 6d6af70cc..c747c3e1e 100644 --- a/.env.local.example +++ b/.env.local.example @@ -46,3 +46,5 @@ NEXTAUTH_URL="$PUBLIC_URL/api/web/auth" GOOGLE_STATIC_MAP_KEY="" GOOGLE_STATIC_MAP_SIGNATURE_SECRET="" +GOOGLE_STATIC_MAP_ID="" +GOOGLE_DYNAMIC_MAP_ID="" diff --git a/.env.test b/.env.test index 801c4336b..d2b538cc2 100644 --- a/.env.test +++ b/.env.test @@ -37,3 +37,7 @@ SEAMLESS_LOGOUT_NO="test" SEAMLESS_LOGOUT_SV="test" WEBVIEW_ENCRYPTION_KEY="test" BOOKING_ENCRYPTION_KEY="test" +GOOGLE_STATIC_MAP_KEY="test" +GOOGLE_STATIC_MAP_SIGNATURE_SECRET="test" +GOOGLE_STATIC_MAP_ID="test" +GOOGLE_DYNAMIC_MAP_ID="test" diff --git a/components/ContentType/HotelPage/IntroSection/index.tsx b/components/ContentType/HotelPage/IntroSection/index.tsx index d20486c60..cb62ca01c 100644 --- a/components/ContentType/HotelPage/IntroSection/index.tsx +++ b/components/ContentType/HotelPage/IntroSection/index.tsx @@ -73,6 +73,7 @@ export default async function IntroSection({ color="burgundy" variant="icon" href={`?s=${about[lang]}`} + scroll={false} > {intl.formatMessage({ id: "Read more about the hotel" })} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx b/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx deleted file mode 100644 index 451bb07e6..000000000 --- a/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client" -import { - AdvancedMarker, - Map, - type MapProps, - useMap, -} from "@vis.gl/react-google-maps" -import { useState } from "react" -import { useIntl } from "react-intl" - -import useHotelPageStore from "@/stores/hotel-page" - -import { CloseIcon, MinusIcon, PlusIcon } from "@/components/Icons" -import Button from "@/components/TempDesignSystem/Button" -import Divider from "@/components/TempDesignSystem/Divider" -import Title from "@/components/TempDesignSystem/Text/Title" -import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" -import useLang from "@/hooks/useLang" - -import ScandicMarker from "../Markers/Scandic" - -import styles from "./dynamicMap.module.css" - -import type { DynamicMapContentProps } from "@/types/components/hotelPage/map/dynamicMapContent" - -export default function DynamicMapContent({ - hotelName, - coordinates, -}: DynamicMapContentProps) { - const intl = useIntl() - const lang = useLang() - const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore() - const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false) - const map = useMap() - - const mapOptions: MapProps = { - defaultZoom: 15, - defaultCenter: coordinates, - disableDefaultUI: true, - clickableIcons: false, - mapId: `${hotelName}-${lang}-map`, - // As reference for future styles when adding POIs - // styles: [ - // { - // featureType: "poi", - // elementType: "all", - // stylers: [{ visibility: "off" }], - // }, - // ], - } - - useHandleKeyUp((event: KeyboardEvent) => { - if (event.key === "Escape" && isDynamicMapOpen) { - closeDynamicMap() - } - }) - - function zoomIn() { - const currentZoom = map && map.getZoom() - if (currentZoom) { - map.setZoom(currentZoom + 1) - } - } - function zoomOut() { - const currentZoom = map && map.getZoom() - if (currentZoom) { - map.setZoom(currentZoom - 1) - } - } - - function toggleFullScreenSidebar() { - setIsFullScreenSidebar((prev) => !prev) - } - - return ( - <> -
- -
- - -
-
- -
- - - - - -
- - ) -} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx new file mode 100644 index 000000000..a049e90c4 --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx @@ -0,0 +1,138 @@ +"use client" +import { + AdvancedMarker, + AdvancedMarkerAnchorPoint, + Map, + type MapProps, + useMap, +} from "@vis.gl/react-google-maps" +import { useIntl } from "react-intl" + +import useHotelPageStore from "@/stores/hotel-page" + +import { MinusIcon, PlusIcon } from "@/components/Icons" +import CloseLargeIcon from "@/components/Icons/CloseLarge" +import PoiMarker from "@/components/Maps/Markers/Poi" +import ScandicMarker from "@/components/Maps/Markers/Scandic" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import styles from "./map.module.css" + +import type { MapContentProps } from "@/types/components/hotelPage/map/mapContent" + +export default function MapContent({ + coordinates, + pointsOfInterest, + activePoi, + mapId, + onActivePoiChange, +}: MapContentProps) { + const intl = useIntl() + const { closeDynamicMap } = useHotelPageStore() + const map = useMap() + + const mapOptions: MapProps = { + defaultZoom: 14, + defaultCenter: coordinates, + disableDefaultUI: true, + clickableIcons: false, + mapId, + } + + function zoomIn() { + const currentZoom = map && map.getZoom() + if (currentZoom) { + map.setZoom(currentZoom + 1) + } + } + function zoomOut() { + const currentZoom = map && map.getZoom() + if (currentZoom) { + map.setZoom(currentZoom - 1) + } + } + + function toggleActivePoi(poiName: string) { + onActivePoiChange(activePoi === poiName ? null : poiName) + } + + return ( +
+ + + + + + {pointsOfInterest.map((poi) => ( + onActivePoiChange(poi.name)} + onMouseLeave={() => onActivePoiChange(null)} + onClick={() => toggleActivePoi(poi.name)} + > + + + + + {poi.name} + + {poi.distance} km + + + + + + ))} + +
+ +
+ + +
+
+
+ ) +} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Map/map.module.css b/components/ContentType/HotelPage/Map/DynamicMap/Map/map.module.css new file mode 100644 index 000000000..557730bfe --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/Map/map.module.css @@ -0,0 +1,108 @@ +.mapContainer { + --button-box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1); + width: 100%; + position: relative; + z-index: 0; +} + +.mapContainer::after { + content: ""; + position: absolute; + top: 0; + right: 0; + background: linear-gradient( + 43deg, + rgba(172, 172, 172, 0) 57.66%, + rgba(0, 0, 0, 0.25) 92.45% + ); + width: 100%; + height: 100%; + pointer-events: none; +} + +.ctaButtons { + position: absolute; + top: var(--Spacing-x2); + right: var(--Spacing-x2); + z-index: 1; + display: flex; + flex-direction: column; + gap: var(--Spacing-x7); + align-items: flex-end; + pointer-events: none; +} + +.zoomButtons { + display: grid; + gap: var(--Spacing-x1); +} + +.closeButton { + pointer-events: initial; + box-shadow: var(--button-box-shadow); + gap: var(--Spacing-x-half); +} + +.zoomButton { + width: var(--Spacing-x5); + height: var(--Spacing-x5); + padding: 0; + pointer-events: initial; + box-shadow: var(--button-box-shadow); +} + +.advancedMarker { + height: var(--Spacing-x4); + width: var( + --Spacing-x4 + ) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */ +} + +.advancedMarker.active { + height: var(--Spacing-x5); + width: var( + --Spacing-x5 + ) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */ +} + +.poi { + position: absolute; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + padding: var(--Spacing-x-half); + border-radius: var(--Corner-radius-Rounded); + background-color: var(--Base-Surface-Primary-light-Normal); + box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1); + gap: var(--Spacing-x1); +} + +.poi.active { + padding-right: var(--Spacing-x-one-and-half); +} + +.poiLabel { + display: none; +} + +.poi.active .poiLabel { + display: flex; + align-items: center; + gap: var(--Spacing-x2); + text-wrap: nowrap; +} + +@media screen and (min-width: 768px) { + .ctaButtons { + top: var(--Spacing-x4); + right: var(--Spacing-x4); + bottom: var(--Spacing-x4); + justify-content: space-between; + } + + .zoomButtons { + display: flex; + } +} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx new file mode 100644 index 000000000..217fa51bd --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx @@ -0,0 +1,102 @@ +"use client" + +import { useState } from "react" +import { useIntl } from "react-intl" + +import PoiMarker from "@/components/Maps/Markers/Poi" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./sidebar.module.css" + +import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar" + +export default function Sidebar({ + activePoi, + hotelName, + pointsOfInterest, + onActivePoiChange, +}: SidebarProps) { + const intl = useIntl() + const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false) + const poiCategories = new Set( + pointsOfInterest.map(({ category }) => category) + ) + const poisInCategories = Array.from(poiCategories).map((category) => ({ + category, + pois: pointsOfInterest.filter((poi) => poi.category === category), + })) + + function toggleFullScreenSidebar() { + setIsFullScreenSidebar((prev) => !prev) + } + + return ( + + ) +} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/sidebar.module.css b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/sidebar.module.css new file mode 100644 index 000000000..11f26b822 --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/sidebar.module.css @@ -0,0 +1,111 @@ +.sidebar { + --sidebar-max-width: 26.25rem; + --sidebar-mobile-toggle-height: 91px; + --sidebar-mobile-fullscreen-height: calc( + 100vh - var(--main-menu-mobile-height) - var(--sidebar-mobile-toggle-height) + ); + + position: absolute; + top: var(--sidebar-mobile-fullscreen-height); + height: 100%; + right: 0; + left: 0; + background-color: var(--Base-Surface-Primary-light-Normal); + z-index: 1; + transition: top 0.3s; +} + +.sidebar:not(.fullscreen) { + border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; +} + +.sidebar.fullscreen { + top: 0; +} + +.sidebarToggle { + position: relative; + margin: var(--Spacing-x4) 0 var(--Spacing-x2); + width: 100%; +} + +.sidebarToggle::before { + content: ""; + position: absolute; + display: block; + top: -0.5rem; + width: 100px; + height: 3px; + background-color: var(--UI-Text-High-contrast); +} + +.sidebarContent { + display: grid; + gap: var(--Spacing-x5); + align-content: start; + padding: var(--Spacing-x3) var(--Spacing-x2); + height: var(--sidebar-mobile-fullscreen-height); + overflow-y: auto; +} + +.poiGroup { + display: grid; + gap: var(--Spacing-x2); +} + +.poiHeading { + display: flex; + align-items: center; + gap: var(--Spacing-x1); +} + +.poiList { + list-style: none; +} + +.poiItem { + padding: var(--Spacing-x1) 0; + border-bottom: 1px solid var(--Base-Border-Subtle); +} + +.poiButton { + background-color: var(--Base-Surface-Primary-light-Normal); + border-width: 0; + font-family: var(--typography-Body-Regular-fontFamily); + font-size: var(--typography-Body-Regular-fontSize); + font-weight: var(--typography-Body-Regular-fontWeight); + color: var(--UI-Text-High-contrast); + width: 100%; + display: grid; + grid-template-columns: 1fr max-content; + gap: var(--Spacing-x2); + align-items: center; + padding: var(--Spacing-x-half) var(--Spacing-x1); + border-radius: var(--Corner-radius-Medium); + cursor: pointer; + text-align: left; + transition: background-color 0.3s; +} +.poiButton.active { + background-color: var(--Base-Surface-Primary-light-Hover); +} + +@media screen and (min-width: 768px) { + .sidebar { + position: static; + width: 40vw; + min-width: 10rem; + max-width: var(--sidebar-max-width); + background-color: var(--Base-Surface-Primary-light-Normal); + } + + .sidebarToggle { + display: none; + } + + .sidebarContent { + padding: var(--Spacing-x4) var(--Spacing-x5); + height: 100%; + position: relative; + } +} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css index 303ad4c1f..2a3e4ff78 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css +++ b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css @@ -1,5 +1,4 @@ .dynamicMap { - --sidebar-height: 88px; position: fixed; top: var(--main-menu-mobile-height); right: 0; @@ -10,143 +9,8 @@ background-color: var(--Base-Surface-Primary-light-Normal); } -.sidebar { - position: absolute; - top: calc(100vh - var(--main-menu-mobile-height) - var(--sidebar-height)); - height: 100%; - right: 0; - left: 0; - background-color: var(--Base-Surface-Primary-light-Normal); - z-index: 1; - transition: top 0.3s; -} - -.sidebar:not(.fullscreen) { - border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; -} - -.sidebar.fullscreen { - top: 0; -} - -.sidebarToggle { - display: grid; - gap: var(--Spacing-x1); - padding: var(--Spacing-x2); - height: var(--sidebar-height); -} - -.toggleButton { - position: relative; - display: flex; - justify-content: center; - background-color: transparent; - border-width: 0; - padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); - font-family: var(--typography-Body-Bold-fontFamily); - font-size: var(--typography-Body-Bold-fontSize); - font-weight: var(--typography-Body-Bold-fontWeight); - color: var(--UI-Text-Medium-contrast); - width: 100%; -} - -.toggleButton::before { - content: ""; - position: absolute; - display: block; - top: -0.5rem; - width: 100px; - height: 3px; - background-color: var(--UI-Text-High-contrast); -} - -.sidebarContent { - display: grid; - gap: var(--Spacing-x3); - align-content: start; - padding: var(--Spacing-x3) var(--Spacing-x2); - height: 100%; - overflow-y: auto; -} - -.mapContainer { - flex: 1; - position: relative; -} - -.mapContainer::after { - content: ""; - display: block; - position: absolute; - top: 0; - right: 0; - background: linear-gradient( - 43deg, - rgba(172, 172, 172, 0) 57.66%, - rgba(0, 0, 0, 0.25) 92.45% - ); - width: 100%; - height: 100%; - pointer-events: none; -} - -.ctaButtons { - position: absolute; - top: var(--Spacing-x2); - right: var(--Spacing-x2); - z-index: 1; - display: flex; - flex-direction: column; - gap: var(--Spacing-x7); - align-items: flex-end; - pointer-events: none; -} - -.zoomButtons { - display: grid; - gap: var(--Spacing-x1); -} - -.closeButton { - pointer-events: initial; - box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1); -} - -.zoomButton { - width: var(--Spacing-x5); - height: var(--Spacing-x5); - padding: 0; - pointer-events: initial; - box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1); -} - @media screen and (min-width: 768px) { .dynamicMap { top: var(--main-menu-desktop-height); } - .sidebar { - position: static; - width: 40vw; - min-width: 10rem; - max-width: 26.25rem; - background-color: var(--Base-Surface-Primary-light-Normal); - } - .sidebarToggle { - display: none; - } - - .sidebarContent { - padding: var(--Spacing-x4) var(--Spacing-x5); - } - - .ctaButtons { - top: var(--Spacing-x4); - right: var(--Spacing-x4); - bottom: var(--Spacing-x4); - justify-content: space-between; - } - - .zoomButtons { - display: flex; - } } diff --git a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx index 2eee0b6ae..785949be5 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx +++ b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx @@ -8,7 +8,8 @@ import useHotelPageStore from "@/stores/hotel-page" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" -import DynamicMapContent from "./Content" +import MapContent from "./Map" +import Sidebar from "./Sidebar" import styles from "./dynamicMap.module.css" @@ -18,11 +19,14 @@ export default function DynamicMap({ apiKey, hotelName, coordinates, + pointsOfInterest, + mapId, }: DynamicMapProps) { const intl = useIntl() const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore() const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0) const hasMounted = useRef(false) + const [activePoi, setActivePoi] = useState(null) useHandleKeyUp((event: KeyboardEvent) => { if (event.key === "Escape" && isDynamicMapOpen) { @@ -58,7 +62,19 @@ export default function DynamicMap({ { hotelName } )} > - + + diff --git a/components/ContentType/HotelPage/Map/MapCard/index.tsx b/components/ContentType/HotelPage/Map/MapCard/index.tsx index c3caca9ed..8764870bc 100644 --- a/components/ContentType/HotelPage/Map/MapCard/index.tsx +++ b/components/ContentType/HotelPage/Map/MapCard/index.tsx @@ -4,7 +4,9 @@ import { useIntl } from "react-intl" import useHotelPageStore from "@/stores/hotel-page" +import PoiMarker from "@/components/Maps/Markers/Poi" import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" @@ -12,7 +14,7 @@ import styles from "./mapCard.module.css" import type { MapCardProps } from "@/types/components/hotelPage/map/mapCard" -export default function MapCard({ hotelName }: MapCardProps) { +export default function MapCard({ hotelName, pois }: MapCardProps) { const intl = useIntl() const { openDynamicMap } = useHotelPageStore() @@ -34,6 +36,15 @@ export default function MapCard({ hotelName }: MapCardProps) { > {hotelName} +
    + {pois.map((poi) => ( +
  • + + {poi.name} + {poi.distance} km +
  • + ))} +
- - {children} - - ) : null -} - -export default SidePeekItem \ No newline at end of file diff --git a/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css b/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css deleted file mode 100644 index eb90ed60b..000000000 --- a/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.sidePeekItem { - display: grid; - grid-template-rows: min-content auto; - gap: var(--Spacing-x4); - height: 100%; -} - -.content>* { - padding: var(--Spacing-x3) var(--Spacing-x2); -} - -.header { - display: flex; - justify-content: flex-end; - border-bottom: 1px solid var(--Base-Border-Subtle); - align-items: center; -} - -.header:has(> h2) { - justify-content: space-between; -} - -@media screen and (min-width: 1367px) { - .content>* { - padding: var(--Spacing-x4); - } -} \ No newline at end of file diff --git a/components/TempDesignSystem/SidePeek/index.tsx b/components/TempDesignSystem/SidePeek/index.tsx index 271ad30e5..df0b8dc44 100644 --- a/components/TempDesignSystem/SidePeek/index.tsx +++ b/components/TempDesignSystem/SidePeek/index.tsx @@ -1,15 +1,20 @@ "use client" import { useIsSSR } from "@react-aria/ssr" -import React, { Children, cloneElement } from "react" +import { useContext } from "react" import { Dialog, DialogTrigger, Modal, ModalOverlay, } from "react-aria-components" +import { useIntl } from "react-intl" -import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types" +import { CloseIcon } from "@/components/Icons" +import { SidePeekContext } from "@/components/SidePeekProvider" + +import Button from "../Button" +import Title from "../Text/Title" import styles from "./sidePeek.module.css" @@ -17,33 +22,61 @@ import type { SidePeekProps } from "./sidePeek" function SidePeek({ children, + title, + contentKey, handleClose, - activeSidePeek, + isOpen, }: React.PropsWithChildren) { - const sidePeekChildren = Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child - } - return cloneElement(child as React.ReactElement, { - isActive: - (child.props.contentKey as SidePeekContentKey) === activeSidePeek, - onClose: handleClose, - }) - }) - const isSSR = useIsSSR() - return isSSR ? ( -
{children}
- ) : ( + const intl = useIntl() + const context = useContext(SidePeekContext) + function onClose() { + const closeHandler = handleClose || context?.handleClose + closeHandler && closeHandler(false) + } + + if (isSSR) { + return ( +
+

{title}

+ {children} +
+ ) + } + return ( - - {sidePeekChildren} + + + + diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css index 0ed9304c5..2d6de4f00 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.module.css +++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css @@ -1,38 +1,9 @@ -.sidePeek { - position: fixed; - top: var(--current-mobile-site-header-height); - right: auto; - bottom: 0; - width: 100%; - height: calc(100vh - var(--current-mobile-site-header-height)); - background-color: var(--Base-Background-Primary-Normal); - z-index: 100; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.85); -} - -.sidePeek[data-entering] { - animation: slide-up 300ms; -} - -.sidePeek[data-exiting] { - animation: slide-up 300ms reverse; -} - -.dialog { - height: 100%; -} - -.overlay { - position: absolute; - top: var(--current-mobile-site-header-height); - bottom: 0; - left: 0; - right: 0; - z-index: 99; +.modal { + --sidepeek-desktop-width: 600px; } @keyframes slide-in { from { - right: -600px; + right: calc(-1 * var(--sidepeek-desktop-width)); } to { @@ -46,24 +17,84 @@ } to { - top: var(--current-mobile-site-header-height); + top: 0; } } +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 99; +} + +.modal { + position: fixed; + top: 0; + right: auto; + bottom: 0; + width: 100%; + height: 100vh; + background-color: var(--Base-Background-Primary-Normal); + z-index: 100; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.85); +} + +.modal[data-entering] { + animation: slide-up 300ms; +} + +.modal[data-exiting] { + animation: slide-up 300ms reverse; +} + +.dialog { + height: 100%; +} + +.sidePeek { + display: grid; + grid-template-rows: min-content auto; + height: 100%; +} + +.header { + display: flex; + justify-content: flex-end; + border-bottom: 1px solid var(--Base-Border-Subtle); + align-items: center; + padding: var(--Spacing-x4); +} + +.header:has(> h2) { + justify-content: space-between; +} + +.closeButton { + padding: 0; +} + +.sidePeekContent { + padding: var(--Spacing-x4); +} @media screen and (min-width: 1367px) { - .sidePeek { + .modal { top: 0; right: 0px; - width: 600px; + width: var(--sidepeek-desktop-width); height: 100vh; } - .sidePeek[data-entering] { + + .modal[data-entering] { animation: slide-in 250ms; } - .sidePeek[data-exiting] { + .modal[data-exiting] { animation: slide-in 250ms reverse; } + .overlay { top: 0; } diff --git a/components/TempDesignSystem/SidePeek/sidePeek.ts b/components/TempDesignSystem/SidePeek/sidePeek.ts index 626fc640c..e1781f137 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.ts +++ b/components/TempDesignSystem/SidePeek/sidePeek.ts @@ -1,4 +1,6 @@ export interface SidePeekProps { - handleClose: (isOpen: boolean) => void - activeSidePeek: string | null + contentKey: string + title: string + isOpen?: boolean + handleClose?: (isOpen: boolean) => void } diff --git a/components/TempDesignSystem/SidePeek/types.ts b/components/TempDesignSystem/SidePeek/types.ts index f506fc6bf..e37554aff 100644 --- a/components/TempDesignSystem/SidePeek/types.ts +++ b/components/TempDesignSystem/SidePeek/types.ts @@ -1,5 +1,3 @@ -export type SidePeekContentKey = string - export type SidePeekProps = { activeContent: string | null onClose: (isOpen: boolean) => void @@ -7,7 +5,7 @@ export type SidePeekProps = { export type SidePeekContentProps = { title?: string - contentKey: SidePeekContentKey + contentKey: string isActive?: boolean onClose?: () => void } diff --git a/constants/poiCategories.ts b/constants/poiCategories.ts new file mode 100644 index 000000000..6153414e7 --- /dev/null +++ b/constants/poiCategories.ts @@ -0,0 +1,19 @@ +export enum PoiCategories { + "Airport" = "airport", + "Amusement park" = "amusementPark", + "Bus terminal" = "busTerminal", + "Fair" = "fair", + "Hospital" = "hospital", + "Hotel" = "hotel", + "Marketing city" = "marketingCity", + "Museum" = "museum", + "Nearby companies" = "nearbyCompanies", + "Parking / Garage" = "parkingGarage", + "Restaurant" = "restaurant", + "Shopping" = "shopping", + "Sports" = "sports", + "Theatre" = "theatre", + "Tourist" = "tourist", + "Transportations" = "transportations", + "Zoo" = "zoo", +} diff --git a/env/server.ts b/env/server.ts index 58f98067b..663de1ad1 100644 --- a/env/server.ts +++ b/env/server.ts @@ -61,8 +61,10 @@ export const env = createEnv({ SEAMLESS_LOGOUT_SV: z.string(), WEBVIEW_ENCRYPTION_KEY: z.string(), BOOKING_ENCRYPTION_KEY: z.string(), - GOOGLE_STATIC_MAP_KEY: z.string().optional(), - GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string().optional(), + GOOGLE_STATIC_MAP_KEY: z.string(), + GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string(), + GOOGLE_DYNAMIC_MAP_ID: z.string(), + GOOGLE_STATIC_MAP_ID: z.string(), }, emptyStringAsUndefined: true, runtimeEnv: { @@ -113,5 +115,7 @@ export const env = createEnv({ GOOGLE_STATIC_MAP_KEY: process.env.GOOGLE_STATIC_MAP_KEY, GOOGLE_STATIC_MAP_SIGNATURE_SECRET: process.env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET, + GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID, + GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, }, }) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 7b6206a20..367a43724 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -4,8 +4,10 @@ "Add code": "Tilføj kode", "Add new card": "Tilføj nyt kort", "Address": "Adresse", + "Airport": "Lufthavn", "Already a friend?": "Allerede en ven?", "Amenities": "Faciliteter", + "Amusement park": "Forlystelsespark", "An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.", "An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.", "Any changes you've made will be lost.": "Alle ændringer, du har foretaget, går tabt.", @@ -26,6 +28,7 @@ "Breakfast": "Morgenmad", "Breakfast excluded": "Morgenmad ikke inkluderet", "Breakfast included": "Morgenmad inkluderet", + "Bus terminal": "Busstation", "by": "inden", "Cancel": "Afbestille", "characters": "tegn", @@ -66,6 +69,7 @@ "Explore nearby": "Udforsk i nærheden", "Extras to your booking": "Tillæg til din booking", "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", + "Fair": "Messe", "Find booking": "Find booking", "Find hotels": "Find hotel", "Flexibility": "Fleksibilitet", @@ -78,6 +82,7 @@ "Go back to overview": "Gå tilbage til oversigten", "Hi": "Hei", "Highest level": "Højeste niveau", + "Hospital": "Hospital", "Hotel": "Hotel", "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", @@ -104,6 +109,7 @@ "Manage preferences": "Administrer præferencer", "Map": "Kort", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Marketing by", "Meetings & Conferences": "Møder & Konferencer", "Member price": "Medlemspris", "Member price from": "Medlemspris fra", @@ -114,6 +120,7 @@ "Menu": "Menu", "Modify": "Ændre", "Month": "Måned", + "Museum": "Museum", "My communication preferences": "Mine kommunikationspræferencer", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", @@ -121,6 +128,7 @@ "My payment cards": "Mine betalingskort", "My wishes": "Mine ønsker", "Nearby": "I nærheden", + "Nearby companies": "Nærliggende virksomheder", "New password": "Nyt kodeord", "Next": "Næste", "next level:": "Næste niveau:", @@ -142,6 +150,7 @@ "Open my pages menu": "Åbn mine sider menuen", "or": "eller", "Overview": "Oversigt", + "Parking / Garage": "Parkering / Garage", "Password": "Adgangskode", "Pay later": "Betal senere", "Pay now": "Betal nu", @@ -150,8 +159,8 @@ "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", - "points": "Point", "Points": "Point", + "points": "Point", "Points being calculated": "Point udregnes", "Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021", "Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.", @@ -162,6 +171,7 @@ "Read more": "Læs mere", "Read more about the hotel": "Læs mere om hotellet", "Remove card from member profile": "Fjern kortet fra medlemsprofilen", + "Restaurant": "Restaurant", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Gentag den nye adgangskode", "Room & Terms": "Værelse & Vilkår", @@ -179,6 +189,7 @@ "Select date of birth": "Vælg fødselsdato", "Select language": "Vælg sprog", "Select your language": "Vælg dit sprog", + "Shopping": "Shopping", "Show all amenities": "Vis alle faciliteter", "Show less": "Vis mindre", "Show map": "Vis kort", @@ -190,18 +201,22 @@ "Something went wrong!": "Noget gik galt!", "special character": "speciel karakter", "spendable points expiring by": "{points} Brugbare point udløber den {date}", + "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gade", "Successfully updated profile!": "Profilen er opdateret med succes!", "Summary": "Opsummering", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Thank you": "Tak", + "Theatre": "Teater", "There are no transactions to display": "Der er ingen transaktioner at vise", "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "to": "til", "Total Points": "Samlet antal point", + "Tourist": "Turist", "Transaction date": "Overførselsdato", "Transactions": "Transaktioner", + "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "TUI Points": "TUI Points", "Type of bed": "Sengtype", @@ -239,6 +254,7 @@ "Your level": "Dit niveau", "Your points to spend": "Dine brugbare point", "Zip code": "Postnummer", + "Zoo": "Zoo", "Zoom in": "Zoom ind", "Zoom out": "Zoom ud" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 6e1a4b147..70d527064 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -3,8 +3,10 @@ "Add code": "Code hinzufügen", "Add new card": "Neue Karte hinzufügen", "Address": "Adresse", + "Airport": "Flughafen", "Already a friend?": "Sind wir schon Freunde?", "Amenities": "Annehmlichkeiten", + "Amusement park": "Vergnügungspark", "An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.", "An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.", "Any changes you've made will be lost.": "Alle Änderungen, die Sie vorgenommen haben, gehen verloren.", @@ -24,6 +26,7 @@ "Breakfast": "Frühstück", "Breakfast excluded": "Frühstück nicht inbegriffen", "Breakfast included": "Frühstück inbegriffen", + "Bus terminal": "Busbahnhof", "by": "bis", "Cancel": "Stornieren", "characters": "figuren", @@ -64,6 +67,7 @@ "Explore nearby": "Erkunden Sie die Umgebung", "Extras to your booking": "Extras zu Ihrer Buchung", "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", + "Fair": "Messe", "Find booking": "Buchung finden", "Find hotels": "Hotels finden", "Flexibility": "Flexibilität", @@ -76,6 +80,7 @@ "Go back to overview": "Zurück zur Übersicht", "Hi": "Hallo", "Highest level": "Höchstes Level", + "Hospital": "Krankenhaus", "Hotel": "Hotel", "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", @@ -102,6 +107,7 @@ "Manage preferences": "Verwalten von Voreinstellungen", "Map": "Karte", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Marketingstadt", "Member price": "Mitgliederpreis", "Member price from": "Mitgliederpreis ab", "Members": "Mitglieder", @@ -111,6 +117,7 @@ "Menu": "Menu", "Modify": "Ändern", "Month": "Monat", + "Museum": "Museum", "My communication preferences": "Meine Kommunikationseinstellungen", "My membership cards": "Meine Mitgliedskarten", "My pages": "Meine Seiten", @@ -118,6 +125,7 @@ "My payment cards": "Meine Zahlungskarten", "My wishes": "Meine Wünsche", "Nearby": "In der Nähe", + "Nearby companies": "Nahe gelegene Unternehmen", "New password": "Neues Kennwort", "Next": "Nächste", "next level:": "Nächstes Level:", @@ -138,6 +146,7 @@ "Open menu": "Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen", "or": "oder", + "Parking / Garage": "Parken / Garage", "Password": "Passwort", "Pay later": "Später bezahlen", "Pay now": "Jetzt bezahlen", @@ -146,8 +155,8 @@ "Phone is required": "Telefon ist erforderlich", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", - "points": "Punkte", "Points": "Punkte", + "points": "Punkte", "Points being calculated": "Punkte werden berechnet", "Points earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021", "Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.", @@ -158,6 +167,7 @@ "Read more": "Mehr lesen", "Read more about the hotel": "Lesen Sie mehr über das Hotel", "Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen", + "Restaurant": "Restaurant", "Retype new password": "Neues Passwort erneut eingeben", "Room & Terms": "Zimmer & Bedingungen", "Room facilities": "Zimmerausstattung", @@ -173,6 +183,7 @@ "Select date of birth": "Geburtsdatum auswählen", "Select language": "Sprache auswählen", "Select your language": "Wählen Sie Ihre Sprache", + "Shopping": "Einkaufen", "Show all amenities": "Alle Annehmlichkeiten anzeigen", "Show less": "Weniger anzeigen", "Show map": "Karte anzeigen", @@ -184,18 +195,22 @@ "Something went wrong!": "Etwas ist schief gelaufen!", "special character": "sonderzeichen", "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", + "Sports": "Sport", "Standard price": "Standardpreis", "Street": "Straße", "Successfully updated profile!": "Profil erfolgreich aktualisiert!", "Summary": "Zusammenfassung", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Thank you": "Danke", + "Theatre": "Theater", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "to": "zu", "Total Points": "Gesamtpunktzahl", + "Tourist": "Tourist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktionen", + "Transportations": "Transportmittel", "Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)", "TUI Points": "TUI Points", "Type of bed": "Bettentyp", @@ -232,6 +247,7 @@ "Your level": "Dein level", "Your points to spend": "Meine Punkte", "Zip code": "PLZ", + "Zoo": "Zoo", "Zoom in": "Vergrößern", "Zoom out": "Verkleinern" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index bd73a9f0a..8a3937452 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -4,8 +4,10 @@ "Add code": "Add code", "Add new card": "Add new card", "Address": "Address", + "Airport": "Airport", "Already a friend?": "Already a friend?", "Amenities": "Amenities", + "Amusement park": "Amusement park", "An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.", "An error occurred when trying to update profile.": "An error occurred when trying to update profile.", "Any changes you've made will be lost.": "Any changes you've made will be lost.", @@ -25,6 +27,7 @@ "Breakfast": "Breakfast", "Breakfast excluded": "Breakfast excluded", "Breakfast included": "Breakfast included", + "Bus terminal": "Bus terminal", "by": "by", "Cancel": "Cancel", "characters": "characters", @@ -66,6 +69,7 @@ "Explore nearby": "Explore nearby", "Extras to your booking": "Extras to your booking", "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", + "Fair": "Fair", "FAQ": "FAQ", "Find booking": "Find booking", "Find hotels": "Find hotels", @@ -79,6 +83,7 @@ "Go back to overview": "Go back to overview", "Hi": "Hi", "Highest level": "Highest level", + "Hospital": "Hospital", "Hotel": "Hotel", "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", @@ -108,6 +113,7 @@ "Manage preferences": "Manage preferences", "Map": "Map", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Marketing city", "Meetings & Conferences": "Meetings & Conferences", "Member price": "Member price", "Member price from": "Member price from", @@ -118,6 +124,7 @@ "Menu": "Menu", "Modify": "Modify", "Month": "Month", + "Museum": "Museum", "My communication preferences": "My communication preferences", "My membership cards": "My membership cards", "My pages": "My pages", @@ -125,6 +132,7 @@ "My payment cards": "My payment cards", "My wishes": "My wishes", "Nearby": "Nearby", + "Nearby companies": "Nearby companies", "New password": "New password", "Next": "Next", "next level:": "next level:", @@ -146,6 +154,7 @@ "Open my pages menu": "Open my pages menu", "or": "or", "Overview": "Overview", + "Parking / Garage": "Parking / Garage", "Password": "Password", "Pay later": "Pay later", "Pay now": "Pay now", @@ -154,8 +163,8 @@ "Phone is required": "Phone is required", "Phone number": "Phone number", "Please enter a valid phone number": "Please enter a valid phone number", - "Points": "Points", "points": "Points", + "Points": "Points", "Points being calculated": "Points being calculated", "Points earned prior to May 1, 2021": "Points earned prior to May 1, 2021", "Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.", @@ -166,6 +175,7 @@ "Read more": "Read more", "Read more about the hotel": "Read more about the hotel", "Remove card from member profile": "Remove card from member profile", + "Restaurant": "Restaurant", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Retype new password", "Room & Terms": "Room & Terms", @@ -184,6 +194,7 @@ "Select date of birth": "Select date of birth", "Select language": "Select language", "Select your language": "Select your language", + "Shopping": "Shopping", "Show all amenities": "Show all amenities", "Show less": "Show less", "Show map": "Show map", @@ -195,18 +206,22 @@ "Something went wrong!": "Something went wrong!", "special character": "special character", "spendable points expiring by": "{points} spendable points expiring by {date}", + "Sports": "Sports", "Standard price": "Standard price", "Street": "Street", "Successfully updated profile!": "Successfully updated profile!", "Summary": "Summary", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Thank you": "Thank you", + "Theatre": "Theatre", "There are no transactions to display": "There are no transactions to display", "Things nearby HOTEL_NAME": "Things nearby {hotelName}", "to": "to", "Total Points": "Total Points", + "Tourist": "Tourist", "Transaction date": "Transaction date", "Transactions": "Transactions", + "Transportations": "Transportations", "Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)", "TUI Points": "TUI Points", "Type of bed": "Type of bed", @@ -244,6 +259,7 @@ "Your level": "Your level", "Your points to spend": "Your points to spend", "Zip code": "Zip code", + "Zoo": "Zoo", "Zoom in": "Zoom in", "Zoom out": "Zoom out" } diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index a97dea3fe..075552ddf 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -4,8 +4,10 @@ "Add code": "Lisää koodi", "Add new card": "Lisää uusi kortti", "Address": "Osoite", + "Airport": "Lentokenttä", "Already a friend?": "Oletko jo ystävä?", "Amenities": "Mukavuudet", + "Amusement park": "Huvipuisto", "An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.", "An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.", "Any changes you've made will be lost.": "Kaikki tekemäsi muutokset menetetään.", @@ -25,6 +27,7 @@ "Breakfast": "Aamiainen", "Breakfast excluded": "Aamiainen ei sisälly", "Breakfast included": "Aamiainen sisältyy", + "Bus terminal": "Bussiasema", "by": "mennessä", "Cancel": "Peruuttaa", "characters": "hahmoja", @@ -65,6 +68,7 @@ "Explore nearby": "Tutustu lähialueeseen", "Extras to your booking": "Varauksessa lisäpalveluita", "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", + "Fair": "Messukeskus", "Find booking": "Etsi varaus", "Find hotels": "Etsi hotelleja", "Flexibility": "Joustavuus", @@ -77,6 +81,7 @@ "Go back to overview": "Palaa yleiskatsaukseen", "Hi": "Hi", "Highest level": "Korkein taso", + "Hospital": "Sairaala", "Hotel": "Hotelli", "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", @@ -103,6 +108,7 @@ "Manage preferences": "Asetusten hallinta", "Map": "Kartta", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Markkinointikaupunki", "Meetings & Conferences": "Kokoukset & Konferenssit", "Member price": "Jäsenhinta", "Member price from": "Jäsenhinta alkaen", @@ -113,6 +119,7 @@ "Menu": "Valikko", "Modify": "Muokkaa", "Month": "Kuukausi", + "Museum": "Museo", "My communication preferences": "Viestintämieltymykseni", "My membership cards": "Jäsenkorttini", "My pages": "Omat sivut", @@ -120,6 +127,7 @@ "My payment cards": "Minun maksukortit", "My wishes": "Toiveeni", "Nearby": "Lähistöllä", + "Nearby companies": "Läheiset yritykset", "New password": "Uusi salasana", "Next": "Seuraava", "next level:": "pistettä tasolle:", @@ -141,6 +149,7 @@ "Open my pages menu": "Avaa omat sivut -valikko", "or": "tai", "Overview": "Yleiskatsaus", + "Parking / Garage": "Pysäköinti / Autotalli", "Password": "Salasana", "Pay later": "Maksa myöhemmin", "Pay now": "Maksa nyt", @@ -149,8 +158,8 @@ "Phone is required": "Puhelin vaaditaan", "Phone number": "Puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", - "points": "pistettä", "Points": "Pisteet", + "points": "pistettä", "Points being calculated": "Pisteitä lasketaan", "Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021", "Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.", @@ -161,6 +170,7 @@ "Read more": "Lue lisää", "Read more about the hotel": "Lue lisää hotellista", "Remove card from member profile": "Poista kortti jäsenprofiilista", + "Restaurant": "Ravintola", "Restaurant & Bar": "Ravintola & Baari", "Retype new password": "Kirjoita uusi salasana uudelleen", "Room & Terms": "Huone & Ehdot", @@ -178,6 +188,7 @@ "Select date of birth": "Valitse syntymäaika", "Select language": "Valitse kieli", "Select your language": "Valitse kieli", + "Shopping": "Ostokset", "Show all amenities": "Näytä kaikki mukavuudet", "Show less": "Näytä vähemmän", "Show map": "Näytä kartta", @@ -189,18 +200,22 @@ "Something went wrong!": "Jotain meni pieleen!", "special character": "erikoishahmo", "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", + "Sports": "Urheilu", "Standard price": "Normaali hinta", "Street": "Katu", "Successfully updated profile!": "Profiilin päivitys onnistui!", "Summary": "Yhteenveto", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Thank you": "Kiitos", + "Theatre": "Teatteri", "There are no transactions to display": "Näytettäviä tapahtumia ei ole", "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "to": "to", "Total Points": "Kokonaispisteet", + "Tourist": "Turisti", "Transaction date": "Tapahtuman päivämäärä", "Transactions": "Tapahtumat", + "Transportations": "Kuljetukset", "Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)", "TUI Points": "TUI Points", "Type of bed": "Vuodetyyppi", @@ -238,6 +253,7 @@ "Your level": "Tasosi", "Your points to spend": "Käytettävissä olevat pisteesi", "Zip code": "Postinumero", + "Zoo": "Eläintarha", "Zoom in": "Lähennä", "Zoom out": "Loitonna" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 81ef12cc2..ea318036f 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -4,8 +4,10 @@ "Add code": "Legg til kode", "Add new card": "Legg til nytt kort", "Address": "Adresse", + "Airport": "Flyplass", "Already a friend?": "Allerede Friend?", "Amenities": "Fasiliteter", + "Amusement park": "Tivoli", "An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.", "An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.", "Any changes you've made will be lost.": "Eventuelle endringer du har gjort, går tapt.", @@ -25,6 +27,7 @@ "Breakfast": "Frokost", "Breakfast excluded": "Frokost ekskludert", "Breakfast included": "Frokost inkludert", + "Bus terminal": "Bussterminal", "by": "innen", "Cancel": "Avbryt", "characters": "tegn", @@ -65,6 +68,7 @@ "Explore nearby": "Utforsk i nærheten", "Extras to your booking": "Tilvalg til bestillingen din", "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", + "Fair": "Messe", "Find booking": "Finn booking", "Find hotels": "Finn hotell", "Flexibility": "Fleksibilitet", @@ -77,6 +81,7 @@ "Go back to overview": "Gå tilbake til oversikten", "Hi": "Hei", "Highest level": "Høyeste nivå", + "Hospital": "Sykehus", "Hotel": "Hotel", "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", @@ -103,6 +108,7 @@ "Manage preferences": "Administrer preferanser", "Map": "Kart", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Markedsføringsby", "Meetings & Conferences": "Møter & Konferanser", "Member price": "Medlemspris", "Member price from": "Medlemspris fra", @@ -113,6 +119,7 @@ "Menu": "Menu", "Modify": "Endre", "Month": "Måned", + "Museum": "Museum", "My communication preferences": "Mine kommunikasjonspreferanser", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", @@ -120,6 +127,7 @@ "My payment cards": "Mine betalingskort", "My wishes": "Mine ønsker", "Nearby": "I nærheten", + "Nearby companies": "Nærliggende selskaper", "New password": "Nytt passord", "Next": "Neste", "next level:": "Neste nivå:", @@ -141,6 +149,7 @@ "Open my pages menu": "Åpne mine sider menyen", "or": "eller", "Overview": "Oversikt", + "Parking / Garage": "Parkering / Garasje", "Password": "Passord", "Pay later": "Betal senere", "Pay now": "Betal nå", @@ -149,8 +158,8 @@ "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", - "Points": "Poeng", "points": "poeng", + "Points": "Poeng", "Points being calculated": "Poeng beregnes", "Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021", "Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.", @@ -161,6 +170,7 @@ "Read more": "Les mer", "Read more about the hotel": "Les mer om hotellet", "Remove card from member profile": "Fjern kortet fra medlemsprofilen", + "Restaurant": "Restaurant", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Skriv inn nytt passord på nytt", "Room & Terms": "Rom & Vilkår", @@ -178,6 +188,7 @@ "Select date of birth": "Velg fødselsdato", "Select language": "Velg språk", "Select your language": "Velg språk", + "Shopping": "Shopping", "Show all amenities": "Vis alle fasiliteter", "Show less": "Vis mindre", "Show map": "Vis kart", @@ -189,18 +200,22 @@ "Something went wrong!": "Noe gikk galt!", "special character": "spesiell karakter", "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", + "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gate", "Successfully updated profile!": "Vellykket oppdatert profil!", "Summary": "Sammendrag", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Thank you": "Takk", + "Theatre": "Teater", "There are no transactions to display": "Det er ingen transaksjoner å vise", "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "to": "til", "Total Points": "Totale poeng", + "Tourist": "Turist", "Transaction date": "Transaksjonsdato", "Transactions": "Transaksjoner", + "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "TUI Points": "TUI Points", "Type of bed": "Sengtype", @@ -238,6 +253,7 @@ "Your level": "Ditt nivå", "Your points to spend": "Dine brukbare poeng", "Zip code": "Post kode", + "Zoo": "Dyrehage", "Zoom in": "Zoom inn", "Zoom out": "Zoom ut" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 0cf45ea96..4a6d5c3eb 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -4,8 +4,10 @@ "Add code": "Lägg till kod", "Add new card": "Lägg till nytt kort", "Address": "Adress", + "Airport": "Flygplats", "Already a friend?": "Är du redan en vän?", "Amenities": "Bekvämligheter", + "Amusement park": "Nöjespark", "An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.", "An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.", "Any changes you've made will be lost.": "Alla ändringar du har gjort kommer att gå förlorade.", @@ -25,6 +27,7 @@ "Breakfast": "Frukost", "Breakfast excluded": "Frukost ingår ej", "Breakfast included": "Frukost ingår", + "Bus terminal": "Bussterminal", "by": "innan", "Cancel": "Avbryt", "characters": "tecken", @@ -65,6 +68,7 @@ "Explore nearby": "Utforska i närheten", "Extras to your booking": "Extra tillval till din bokning", "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", + "Fair": "Mässa", "Find booking": "Hitta bokning", "Find hotels": "Hitta hotell", "Flexibility": "Flexibilitet", @@ -77,6 +81,7 @@ "Go back to overview": "Gå tillbaka till översikten", "Hi": "Hej", "Highest level": "Högsta nivå", + "Hospital": "Sjukhus", "Hotel": "Hotell", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", @@ -105,6 +110,7 @@ "Manage preferences": "Hantera inställningar", "Map": "Karta", "Map of HOTEL_NAME": "Map of {hotelName}", + "Marketing city": "Marknadsföringsstad", "Meetings & Conferences": "Möten & Konferenser", "Member price": "Medlemspris", "Member price from": "Medlemspris från", @@ -115,6 +121,7 @@ "Menu": "Meny", "Modify": "Ändra", "Month": "Månad", + "Museum": "Museum", "My communication preferences": "Mina kommunikationspreferenser", "My membership cards": "Mina medlemskort", "My pages": "Mina sidor", @@ -122,6 +129,7 @@ "My payment cards": "Mina betalningskort", "My wishes": "Mina önskningar", "Nearby": "I närheten", + "Nearby companies": "Närliggande företag", "New password": "Nytt lösenord", "Next": "Nästa", "next level:": "Nästa nivå:", @@ -143,6 +151,7 @@ "Open my pages menu": "Öppna mina sidor menyn", "or": "eller", "Overview": "Översikt", + "Parking / Garage": "Parkering / Garage", "Password": "Lösenord", "Pay later": "Betala senare", "Pay now": "Betala nu", @@ -151,8 +160,8 @@ "Phone is required": "Telefonnummer är obligatorisk", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", - "points": "poäng", "Points": "Poäng", + "points": "poäng", "Points being calculated": "Poäng beräknas", "Points earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021", "Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.", @@ -163,6 +172,7 @@ "Read more": "Läs mer", "Read more about the hotel": "Läs mer om hotellet", "Remove card from member profile": "Ta bort kortet från medlemsprofilen", + "Restaurant": "Restaurang", "Restaurant & Bar": "Restaurang & Bar", "Retype new password": "Upprepa nytt lösenord", "Room & Terms": "Rum & Villkor", @@ -181,6 +191,7 @@ "Select date of birth": "Välj födelsedatum", "Select language": "Välj språk", "Select your language": "Välj ditt språk", + "Shopping": "Shopping", "Show all amenities": "Visa alla bekvämligheter", "Show less": "Visa mindre", "Show map": "Visa karta", @@ -192,18 +203,22 @@ "Something went wrong!": "Något gick fel!", "special character": "speciell karaktär", "spendable points expiring by": "{points} poäng förfaller {date}", + "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gata", "Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!", "Summary": "Sammanfattning", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Thank you": "Tack", + "Theatre": "Teater", "There are no transactions to display": "Det finns inga transaktioner att visa", "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "to": "till", "Total Points": "Poäng totalt", + "Tourist": "Turist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktioner", + "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", "TUI Points": "TUI Points", "Type of bed": "Sängtyp", @@ -240,6 +255,7 @@ "Your level": "Din nivå", "Your points to spend": "Dina spenderbara poäng", "Zip code": "Postnummer", + "Zoo": "Djurpark", "Zoom in": "Zooma in", "Zoom out": "Zooma ut" } diff --git a/package-lock.json b/package-lock.json index 092398c1b..0ec69dde2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", "@vercel/otel": "^1.9.1", - "@vis.gl/react-google-maps": "^1.1.3", + "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", "clean-deep": "^3.4.0", "dayjs": "^1.11.10", @@ -6656,9 +6656,9 @@ } }, "node_modules/@vis.gl/react-google-maps": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.1.3.tgz", - "integrity": "sha512-jsjj5OpL5AyQDSfzBHU+r5UGAuZxv/7mw9gM2dP4EhqRXkXBpwDKKlml47/4l0m1WL4fSEXLzv85Mrh1VhPiFA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.2.0.tgz", + "integrity": "sha512-gKVE1Jb+FT+F8RGzFrsgB4GWbRq/vLJm2U5nMHiLJmRyaO6HcSfZJue8mUEDJCShsXE0ASphcoJxTQNrBhbFJg==", "dependencies": { "@types/google.maps": "^3.54.10", "fast-deep-equal": "^3.1.3" diff --git a/package.json b/package.json index 51a08a525..d84e0fb31 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", "@vercel/otel": "^1.9.1", - "@vis.gl/react-google-maps": "^1.1.3", + "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", "clean-deep": "^3.4.0", "dayjs": "^1.11.10", diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 1eba75ea0..f894e9aab 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -216,16 +216,46 @@ const rewardNightSchema = z.object({ }), }) -const pointsOfInterestSchema = z.object({ - name: z.string(), - distance: z.number(), - category: z.object({ +const poiCategories = z.enum([ + "Airport", + "Amusement park", + "Bus terminal", + "Fair", + "Hospital", + "Hotel", + "Marketing city", + "Museum", + "Nearby companies", + "Parking / Garage", + "Restaurant", + "Shopping", + "Sports", + "Theatre", + "Tourist", + "Transportations", + "Zoo", +]) + +export const pointOfInterestSchema = z + .object({ name: z.string(), - group: z.string(), - }), - location: locationSchema, - isHighlighted: z.boolean(), -}) + distance: z.number(), + category: z.object({ + name: poiCategories, + group: z.string(), + }), + location: locationSchema, + isHighlighted: z.boolean(), + }) + .transform((poi) => ({ + name: poi.name, + distance: poi.distance, + category: poi.category.name, + coordinates: { + lat: poi.location.latitude, + lng: poi.location.longitude, + }, + })) const parkingPricingSchema = z.object({ freeParking: z.boolean(), @@ -454,7 +484,9 @@ export const getHotelDataSchema = z.object({ detailedFacilities: z.array(detailedFacilitySchema), healthFacilities: z.array(healthFacilitySchema), rewardNight: rewardNightSchema, - pointsOfInterest: z.array(pointsOfInterestSchema), + pointsOfInterest: z + .array(pointOfInterestSchema) + .transform((pois) => pois.sort((a, b) => a.distance - b.distance)), parking: z.array(parkingSchema), specialNeedGroups: z.array(specialNeedGroupSchema), socialMedia: socialMediaSchema, diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index fe92fa109..ada0e96ca 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -251,6 +251,7 @@ export const hotelQueryRouter = router({ hotelRatings: hotelAttributes.ratings, hotelDetailedFacilities: hotelAttributes.detailedFacilities, hotelImages: images, + pointsOfInterest: hotelAttributes.pointsOfInterest, roomCategories, activitiesCard: activities, } diff --git a/server/routers/hotels/utils.ts b/server/routers/hotels/utils.ts new file mode 100644 index 000000000..3dddae69a --- /dev/null +++ b/server/routers/hotels/utils.ts @@ -0,0 +1,18 @@ +import { IconName } from "@/types/components/icon" + +export function getIconByPoiCategory(category: string) { + switch (category) { + case "Transportations": + return IconName.Train + case "Shopping": + return IconName.Shopping + case "Museum": + return IconName.Museum + case "Tourist": + return IconName.Cultural + case "Restaurant": + return IconName.Restaurant + default: + return null + } +} diff --git a/types/components/hotelPage/map/dynamicMap.ts b/types/components/hotelPage/map/dynamicMap.ts index f3aaccf4a..7b8595cc9 100644 --- a/types/components/hotelPage/map/dynamicMap.ts +++ b/types/components/hotelPage/map/dynamicMap.ts @@ -1,7 +1,10 @@ +import type { PointOfInterest } from "@/types/hotel" import type { Coordinates } from "../../maps/coordinates" export interface DynamicMapProps { apiKey: string hotelName: string coordinates: Coordinates + pointsOfInterest: PointOfInterest[] + mapId: string } diff --git a/types/components/hotelPage/map/dynamicMapContent.ts b/types/components/hotelPage/map/dynamicMapContent.ts deleted file mode 100644 index ae22a95b2..000000000 --- a/types/components/hotelPage/map/dynamicMapContent.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Coordinates } from "../../maps/coordinates" - -export interface DynamicMapContentProps { - hotelName: string - coordinates: Coordinates -} diff --git a/types/components/hotelPage/map/mapCard.ts b/types/components/hotelPage/map/mapCard.ts index 0ea8678e0..1ead43544 100644 --- a/types/components/hotelPage/map/mapCard.ts +++ b/types/components/hotelPage/map/mapCard.ts @@ -1,3 +1,6 @@ +import type { PointOfInterest } from "@/types/hotel" + export interface MapCardProps { hotelName: string + pois: PointOfInterest[] } diff --git a/types/components/hotelPage/map/mapContent.ts b/types/components/hotelPage/map/mapContent.ts new file mode 100644 index 000000000..98e7b4c81 --- /dev/null +++ b/types/components/hotelPage/map/mapContent.ts @@ -0,0 +1,10 @@ +import type { Coordinates } from "@/types/components/maps/coordinates" +import type { PointOfInterest } from "@/types/hotel" + +export interface MapContentProps { + coordinates: Coordinates + pointsOfInterest: PointOfInterest[] + activePoi: PointOfInterest["name"] | null + mapId: string + onActivePoiChange: (poi: PointOfInterest["name"] | null) => void +} diff --git a/types/components/hotelPage/map/sidebar.ts b/types/components/hotelPage/map/sidebar.ts new file mode 100644 index 000000000..6bde9f7dc --- /dev/null +++ b/types/components/hotelPage/map/sidebar.ts @@ -0,0 +1,8 @@ +import type { PointOfInterest } from "@/types/hotel" + +export interface SidebarProps { + hotelName: string + pointsOfInterest: PointOfInterest[] + activePoi: PointOfInterest["name"] | null + onActivePoiChange: (poi: PointOfInterest["name"] | null) => void +} diff --git a/types/components/hotelPage/roomCard.ts b/types/components/hotelPage/roomCard.ts index 4a555c621..4d07f170c 100644 --- a/types/components/hotelPage/roomCard.ts +++ b/types/components/hotelPage/roomCard.ts @@ -1,4 +1,4 @@ -import { RoomData } from "@/types/hotel" +import type { RoomData } from "@/types/hotel" export interface RoomCardProps { id: string diff --git a/types/components/icon.ts b/types/components/icon.ts index 781306998..8096ef0f7 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -25,6 +25,7 @@ export enum IconName { CloseLarge = "CloseLarge", Coffee = "Coffee", Concierge = "Concierge", + Cultural = "Cultural", DoorOpen = "DoorOpen", ElectricBike = "ElectricBike", Email = "Email", @@ -40,6 +41,7 @@ export enum IconName { Lock = "Lock", Map = "Map", Minus = "Minus", + Museum = "Museum", Parking = "Parking", Person = "Person", People2 = "People2", @@ -51,6 +53,9 @@ export enum IconName { Sauna = "Sauna", Search = "Search", Service = "Service", + Shopping = "Shopping", + StarFilled = "StarFilled", + Train = "Train", Tripadvisor = "Tripadvisor", TshirtWash = "TshirtWash", Wifi = "Wifi", diff --git a/types/components/maps/poiMarker.ts b/types/components/maps/poiMarker.ts new file mode 100644 index 000000000..89932fb51 --- /dev/null +++ b/types/components/maps/poiMarker.ts @@ -0,0 +1,8 @@ +import { poiVariants } from "@/components/Maps/Markers/Poi/variants" + +import type { VariantProps } from "class-variance-authority" + +export interface PoiMarkerProps extends VariantProps { + size?: number + className?: string +} diff --git a/types/components/maps/staticMap.ts b/types/components/maps/staticMap.ts index 40a73776c..8519f4101 100644 --- a/types/components/maps/staticMap.ts +++ b/types/components/maps/staticMap.ts @@ -1,4 +1,4 @@ -import { Coordinates } from "./coordinates" +import type { Coordinates } from "./coordinates" export type StaticMapProps = { city?: string @@ -8,4 +8,5 @@ export type StaticMapProps = { zoomLevel?: number mapType?: "roadmap" | "satellite" | "terrain" | "hybrid" altText: string + mapId?: string } diff --git a/types/hotel.ts b/types/hotel.ts index f01ea7983..a2656f2b0 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -1,8 +1,8 @@ import { z } from "zod" import { - getAvailabilitySchema, getHotelDataSchema, + pointOfInterestSchema, roomSchema, } from "@/server/routers/hotels/output" @@ -18,3 +18,6 @@ export type HotelTripAdvisor = | undefined export type RoomData = z.infer + +export type PointOfInterest = z.output +export type PointOfInterestCategory = PointOfInterest["category"]