diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.module.css b/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.module.css index 03a7eb926..edd0b9f96 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.module.css +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.module.css @@ -4,4 +4,5 @@ font-family: var(--typography-Body-Regular-fontFamily); gap: var(--Spacing-x3); grid-template-rows: auto 1fr; + position: relative; } diff --git a/app/globals.css b/app/globals.css index 480f72fc9..67624cbcf 100644 --- a/app/globals.css +++ b/app/globals.css @@ -106,11 +106,13 @@ --max-width-navigation: 89.5rem; --main-menu-mobile-height: 75px; + --main-menu-desktop-height: 118px; + --hotel-page-map-desktop-width: 23.75rem; + /* Z-INDEX */ --header-z-index: 10; --menu-overlay-z-index: 10; - - --hotel-page-map-desktop-width: 23.75rem; + --dialog-z-index: 9; } * { diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx b/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx new file mode 100644 index 000000000..c1b04f275 --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/Content.tsx @@ -0,0 +1,133 @@ +"use client" +import { AdvancedMarker, Map, useMap } from "@vis.gl/react-google-maps" +import { useState } from "react" + +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 ScandicMarker from "../Markers/Scandic" + +import styles from "./dynamicMap.module.css" + +export default function DynamicMapContent({ + hotelName, + coordinates, +}: { + hotelName: string + coordinates: { lat: number; lng: number } +}) { + const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore() + const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false) + const map = useMap() + + const mapOptions = { + defaultZoom: 15, + defaultCenter: coordinates, + disableDefaultUI: true, + clickableIcons: false, + mapId: `${hotelName}-map`, + // 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/dynamicMap.module.css b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css new file mode 100644 index 000000000..d9711e1a5 --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css @@ -0,0 +1,156 @@ +.dynamicMap { + --sidebar-height: 88px; + position: fixed; + top: var(--main-menu-mobile-height); + right: 0; + bottom: 0; + left: 0; + z-index: var(--dialog-z-index); + display: flex; +} + +.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: 500; + 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-items: 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: 40px; + padding: 0; + pointer-events: initial; + box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1); +} + +/* @media screen and (max-width: 767px) { + .sidebar:not(.fullscreen) .sidebarContent { + display: none; + } +} */ + +@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 new file mode 100644 index 000000000..8471f5801 --- /dev/null +++ b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx @@ -0,0 +1,42 @@ +"use client" +import { APIProvider } from "@vis.gl/react-google-maps" +import { Dialog, Modal } from "react-aria-components" + +import useHotelPageStore from "@/stores/hotel-page" + +import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" + +import DynamicMapContent from "./Content" + +import styles from "./dynamicMap.module.css" + +export default function DynamicMap({ + apiKey, + hotelName, + coordinates, +}: { + apiKey: string + hotelName: string + coordinates: { lat: number; lng: number } +}) { + const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore() + + useHandleKeyUp((event: KeyboardEvent) => { + if (event.key === "Escape" && isDynamicMapOpen) { + closeDynamicMap() + } + }) + + return ( + + + + + + + + ) +} diff --git a/components/ContentType/HotelPage/Map/MapCard/index.tsx b/components/ContentType/HotelPage/Map/MapCard/index.tsx index 3b98bde9a..e43cf963e 100644 --- a/components/ContentType/HotelPage/Map/MapCard/index.tsx +++ b/components/ContentType/HotelPage/Map/MapCard/index.tsx @@ -1,5 +1,7 @@ "use client" +import useHotelPageStore from "@/stores/hotel-page" + import Button from "@/components/TempDesignSystem/Button" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" @@ -9,9 +11,15 @@ import styles from "./mapCard.module.css" import { MapCardProps } from "@/types/components/hotelPage/mapCard" export default function MapCard({ hotelName }: MapCardProps) { + const { openDynamicMap } = useHotelPageStore() + return (
- + Nearby Explore nearby </Button> diff --git a/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css b/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css index 7fb7919ec..0dc97db88 100644 --- a/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css +++ b/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css @@ -1,12 +1,13 @@ .mapCard { + display: grid; position: absolute; bottom: 15%; + left: var(--Spacing-x2); + right: var(--Spacing-x2); background-color: var(--Base-Surface-Primary-light-Normal); - margin: 0 var(--Spacing-x4); padding: var(--Spacing-x2); box-shadow: 0 0 2.5rem 0 rgba(0, 0, 0, 0.12); border-radius: var(--Corner-radius-Medium); - display: grid; } .ctaButton { diff --git a/components/ContentType/HotelPage/Map/Markers/Scandic.tsx b/components/ContentType/HotelPage/Map/Markers/Scandic.tsx new file mode 100644 index 000000000..0028b37d7 --- /dev/null +++ b/components/ContentType/HotelPage/Map/Markers/Scandic.tsx @@ -0,0 +1,153 @@ +export default function ScandicMarker({ + className, + ...props +}: React.SVGAttributes<HTMLOrSVGElement>) { + return ( + <svg + className={className} + height="100" + viewBox="0 0 85 108" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <g filter="url(#filter0_d_1623_21496)"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M52.842 72.1048C64.7996 67.7006 73.3378 56.108 73.3378 42.5C73.3378 25.103 59.383 11 42.1689 11C24.9548 11 11 25.103 11 42.5C11 56.6689 20.2565 68.6528 32.9914 72.6123L41.236 86.0475C42.0252 87.3336 43.9 87.3158 44.6647 86.015L52.842 72.1048Z" + fill="white" + /> + <circle cx="42" cy="43" r="27" fill="#CD0921" /> + </g> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M34.1848 46.7474C33.592 46.8953 32.9991 46.9446 32.4062 46.9446C30.2818 46.9446 28.8984 45.9587 28.8984 43.6418C28.8984 41.4234 30.3312 40.4868 32.3568 40.4868C32.9497 40.4868 33.5426 40.5361 34.1354 40.6347V41.7192C33.6908 41.6699 33.3449 41.6206 32.9497 41.6206C31.3193 41.6206 30.5288 42.1136 30.5288 43.7404C30.5288 45.2685 31.2699 45.8108 32.9497 45.8108C33.3944 45.8108 33.7402 45.7615 34.1354 45.7122V46.7474H34.1848Z" + fill="white" + /> + <path + d="M56.0547 46.7961H57.6357V40.5848H56.0547V46.7961ZM56.0547 39.3524H57.6357V37.9229H56.0547V39.3524Z" + fill="white" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M54.7828 46.6483C54.4863 46.6975 53.3994 46.8947 52.3125 46.8947C50.2869 46.8947 48.8047 45.9581 48.8047 43.6905C48.8047 41.5215 50.1386 40.437 52.1643 40.437C52.4607 40.437 52.9548 40.4862 53.2018 40.4862V37.9229H54.7828C54.7828 38.2186 54.7828 46.5004 54.7828 46.6483ZM53.2512 45.7116V41.6694C53.0536 41.6694 52.7077 41.6201 52.3619 41.6201C50.9291 41.6201 50.3857 42.3102 50.3857 43.7398C50.3857 45.0215 50.8797 45.8102 52.3125 45.8102C52.6089 45.8102 52.9054 45.7609 53.2512 45.7116Z" + fill="white" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M47.7727 46.7967H46.2411V42.7051C46.2411 42.015 45.9446 41.6206 44.9565 41.6206C44.2648 41.6206 43.5238 41.7685 43.5238 41.7685V46.7967H41.9922V40.7826C42.091 40.7826 44.0672 40.4868 45.2036 40.4868C46.5869 40.4868 47.8221 40.7333 47.8221 42.6065V46.7967H47.7727Z" + fill="white" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M40.6998 46.7954C39.6129 46.894 38.773 46.9432 37.8343 46.9432C36.2533 46.9432 34.9688 46.6968 34.9688 44.9221C34.9688 43.1968 36.5497 43.0982 37.9825 43.0489C38.3283 43.0489 38.7236 43.0489 39.1188 43.0489C39.1188 41.9643 39.0694 41.57 37.4884 41.57C36.6979 41.57 35.8581 41.7179 35.364 41.8165V40.6827C35.9569 40.5348 36.8956 40.4855 37.5873 40.4362C39.3659 40.4362 40.6998 40.6334 40.6998 42.5066V46.7954ZM39.1188 45.8587V44.0841C38.8224 44.0841 38.2789 44.0841 38.0813 44.0841C37.1426 44.0841 36.5497 44.1334 36.5497 45.0207C36.5497 45.908 37.2908 45.9573 38.0319 45.9573C38.4766 45.908 38.7236 45.908 39.1188 45.8587Z" + fill="white" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M63.927 46.7474C63.3342 46.8953 62.7413 46.9446 62.1484 46.9446C60.024 46.9446 58.6406 45.9587 58.6406 43.6418C58.6406 41.4234 60.0734 40.4868 62.099 40.4868C62.6919 40.4868 63.2848 40.5361 63.8776 40.6347V41.7192C63.433 41.6699 63.0871 41.6206 62.6919 41.6206C61.0615 41.6206 60.271 42.1136 60.271 43.7404C60.271 45.2685 61.0121 45.8108 62.6919 45.8108C63.1365 45.8108 63.4824 45.7615 63.8776 45.7122V46.7474H63.927Z" + fill="white" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M28.1054 44.3303C28.1054 46.1542 26.6726 46.943 24.5482 46.943C23.1648 46.943 22.1767 46.6965 22.0285 46.6472V45.3655C22.5226 45.4641 23.5601 45.7106 24.4494 45.7106C25.3387 45.7106 26.475 45.5134 26.475 44.5275C26.475 43.8866 25.981 43.6401 25.0917 43.3936C24.7458 43.2951 24.4 43.1965 24.0541 43.0979C22.8684 42.7528 21.9297 42.2598 21.9297 40.8796C21.9297 38.9077 23.5107 38.4147 25.3387 38.4147C26.4256 38.4147 27.3149 38.6119 27.4631 38.6612V39.8443C27.2655 39.795 26.4256 39.5979 25.4869 39.5979C24.5976 39.5979 23.5601 39.6964 23.5601 40.6824C23.5601 41.4218 24.1529 41.619 24.9928 41.8162C25.3881 41.9641 25.7833 42.0134 26.1786 42.1612C27.2161 42.457 28.1054 42.95 28.1054 44.3303Z" + fill="white" + /> + <g filter="url(#filter1_d_1623_21496)"> + <rect x="37" y="93" width="12" height="12" rx="6" fill="white" /> + <circle cx="43.1172" cy="99" r="4" fill="#CD0921" /> + </g> + <defs> + <filter + id="filter0_d_1623_21496" + x="0" + y="0" + width="84.3379" + height="98.0014" + filterUnits="userSpaceOnUse" + colorInterpolationFilters="sRGB" + > + <feFlood floodOpacity="0" result="BackgroundImageFix" /> + <feColorMatrix + in="SourceAlpha" + type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" + result="hardAlpha" + /> + <feMorphology + radius="3" + operator="dilate" + in="SourceAlpha" + result="effect1_dropShadow_1623_21496" + /> + <feOffset /> + <feGaussianBlur stdDeviation="4" /> + <feComposite in2="hardAlpha" operator="out" /> + <feColorMatrix + type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" + /> + <feBlend + mode="normal" + in2="BackgroundImageFix" + result="effect1_dropShadow_1623_21496" + /> + <feBlend + mode="normal" + in="SourceGraphic" + in2="effect1_dropShadow_1623_21496" + result="shape" + /> + </filter> + <filter + id="filter1_d_1623_21496" + x="34" + y="90" + width="18" + height="18" + filterUnits="userSpaceOnUse" + colorInterpolationFilters="sRGB" + > + <feFlood floodOpacity="0" result="BackgroundImageFix" /> + <feColorMatrix + in="SourceAlpha" + type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" + result="hardAlpha" + /> + <feMorphology + radius="1" + operator="dilate" + in="SourceAlpha" + result="effect1_dropShadow_1623_21496" + /> + <feOffset /> + <feGaussianBlur stdDeviation="1" /> + <feComposite in2="hardAlpha" operator="out" /> + <feColorMatrix + type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" + /> + <feBlend + mode="normal" + in2="BackgroundImageFix" + result="effect1_dropShadow_1623_21496" + /> + <feBlend + mode="normal" + in="SourceGraphic" + in2="effect1_dropShadow_1623_21496" + result="shape" + /> + </filter> + </defs> + </svg> + ) +} diff --git a/components/ContentType/HotelPage/Map/MobileMapToggle/index.tsx b/components/ContentType/HotelPage/Map/MobileMapToggle/index.tsx new file mode 100644 index 000000000..bf2ef3b8e --- /dev/null +++ b/components/ContentType/HotelPage/Map/MobileMapToggle/index.tsx @@ -0,0 +1,43 @@ +"use client" + +import useHotelPageStore from "@/stores/hotel-page" + +import { HouseIcon, LocationIcon } from "@/components/Icons" + +import styles from "./mobileToggle.module.css" + +export default function MobileMapToggle() { + const { isDynamicMapOpen, openDynamicMap, closeDynamicMap } = + useHotelPageStore() + + return ( + <div className={styles.mobileToggle}> + <button + type="button" + className={`${styles.button} ${!isDynamicMapOpen ? styles.active : ""}`} + onClick={closeDynamicMap} + > + <HouseIcon + className={styles.icon} + color={!isDynamicMapOpen ? "white" : "red"} + height={24} + width={24} + /> + <span>Hotel</span> + </button> + <button + type="button" + className={`${styles.button} ${isDynamicMapOpen ? styles.active : ""}`} + onClick={openDynamicMap} + > + <LocationIcon + className={styles.icon} + color={isDynamicMapOpen ? "white" : "red"} + height={24} + width={24} + /> + <span>Nearby</span> + </button> + </div> + ) +} diff --git a/components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css b/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css similarity index 90% rename from components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css rename to components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css index b93941151..2d36929cf 100644 --- a/components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css +++ b/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css @@ -1,4 +1,9 @@ .mobileToggle { + position: fixed; + bottom: var(--Spacing-x5); + left: 50%; + transform: translateX(-50%); + z-index: 1; display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--Spacing-x-half); diff --git a/components/ContentType/HotelPage/Map/MobileToggle/index.tsx b/components/ContentType/HotelPage/Map/MobileToggle/index.tsx deleted file mode 100644 index bce32e387..000000000 --- a/components/ContentType/HotelPage/Map/MobileToggle/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -"use client" - -import { HouseIcon, LocationIcon } from "@/components/Icons" - -import styles from "./mobileToggle.module.css" - -import { IconName } from "@/types/components/icon" - -export default function MobileToggle() { - const options: { text: string; icon: IconName }[] = [ - { - text: "Hotel", - icon: IconName.House, - }, - { - text: "Nearby", - icon: IconName.Location, - }, - ] - - function onToggle() { - console.log("Toggle mobile map") - } - - return ( - <div className={styles.mobileToggle}> - <button - type="button" - className={`${styles.button} ${styles.active}`} - onClick={onToggle} - > - <HouseIcon - className={styles.icon} - color={"white"} - height={24} - width={24} - /> - <span>Hotel</span> - </button> - <button type="button" className={styles.button} onClick={onToggle}> - <LocationIcon - className={styles.icon} - color={"red"} - height={24} - width={24} - /> - <span>Nearby</span> - </button> - </div> - ) -} diff --git a/components/ContentType/HotelPage/Map/StaticMap/index.tsx b/components/ContentType/HotelPage/Map/StaticMap/index.tsx index 7eeeed944..359d38bb9 100644 --- a/components/ContentType/HotelPage/Map/StaticMap/index.tsx +++ b/components/ContentType/HotelPage/Map/StaticMap/index.tsx @@ -4,14 +4,17 @@ import { env } from "@/env/server" import { getIntl } from "@/i18n" +import ScandicMarker from "../Markers/Scandic" import { calculateLatWithOffset, getUrlWithSignature } from "./util" +import styles from "./staticMap.module.css" + import { StaticMapProps } from "@/types/components/hotelPage/staticMap" export default async function StaticMap({ coordinates, hotelName, - zoomLevel = 16, + zoomLevel = 14, }: StaticMapProps) { const intl = await getIntl() const key = env.GOOGLE_STATIC_MAP_KEY @@ -19,8 +22,11 @@ export default async function StaticMap({ const baseUrl = "https://maps.googleapis.com/maps/api/staticmap" const { lng, lat } = coordinates const mapHeight = 785 + const markerHeight = 100 + const mapLatitudeInPx = mapHeight * 0.2 const size = `380x${mapHeight}` - const mapLatitude = calculateLatWithOffset(lat, mapHeight * 0.2, zoomLevel) + + const mapLatitude = calculateLatWithOffset(lat, mapLatitudeInPx, zoomLevel) // Custom Icon should be available from a public URL accessible by Google Static Maps API. At the moment, we don't have a public URL for the custom icon. // const marker = `icon:https://IMAGE_URL|${lat},${lng}` const marker = `${lat},${lng}` @@ -28,10 +34,22 @@ export default async function StaticMap({ const alt = intl.formatMessage({ id: "Map of HOTEL_NAME" }, { hotelName }) const url = new URL( - `${baseUrl}?zoom=${zoomLevel}¢er=${mapLatitude},${lng}&size=${size}&markers=${marker}&key=${key}` + `${baseUrl}?zoom=${zoomLevel}¢er=${mapLatitude},${lng}&size=${size}&key=${key}` ) + // const url = new URL( + // `${baseUrl}?zoom=${zoomLevel}¢er=${mapLatitude},${lng}&size=${size}&markers=${marker}&key=${key}` + // ) const src = getUrlWithSignature(url, secret) - return <img src={src} alt={alt} /> + return ( + <div className={styles.staticMap}> + <img src={src} alt={alt} /> + <ScandicMarker + className={styles.mapMarker} + height={markerHeight} + style={{ top: `${mapLatitudeInPx - markerHeight}px` }} + /> + </div> + ) } diff --git a/components/ContentType/HotelPage/Map/StaticMap/staticMap.module.css b/components/ContentType/HotelPage/Map/StaticMap/staticMap.module.css new file mode 100644 index 000000000..57f45a428 --- /dev/null +++ b/components/ContentType/HotelPage/Map/StaticMap/staticMap.module.css @@ -0,0 +1,9 @@ +.staticMap { + position: relative; +} + +.mapMarker { + position: absolute; + left: 50%; + transform: translateX(-50%); +} diff --git a/components/ContentType/HotelPage/Map/index.tsx b/components/ContentType/HotelPage/Map/index.tsx deleted file mode 100644 index 0b7823f90..000000000 --- a/components/ContentType/HotelPage/Map/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import MapCard from "./MapCard" -import MobileToggle from "./MobileToggle" -import StaticMap from "./StaticMap" - -import styles from "./map.module.css" - -export default function Map({ coordinates, hotelName }: any) { - return ( - <> - <div className={styles.desktopContent}> - <StaticMap coordinates={coordinates} hotelName={hotelName} /> - <MapCard hotelName={hotelName} /> - </div> - <MobileToggle /> - </> - ) -} diff --git a/components/ContentType/HotelPage/Map/map.module.css b/components/ContentType/HotelPage/Map/map.module.css deleted file mode 100644 index 5fd435396..000000000 --- a/components/ContentType/HotelPage/Map/map.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.desktopContent { - display: none; -} - -@media screen and (min-width: 1367px) { - .desktopContent { - align-self: start; - position: relative; - display: grid; - justify-items: center; - } -} diff --git a/components/ContentType/HotelPage/TabNavigation/tabNavigation.module.css b/components/ContentType/HotelPage/TabNavigation/tabNavigation.module.css index 9e5ee8f5a..679d9f2a0 100644 --- a/components/ContentType/HotelPage/TabNavigation/tabNavigation.module.css +++ b/components/ContentType/HotelPage/TabNavigation/tabNavigation.module.css @@ -1,7 +1,7 @@ .stickyWrapper { position: sticky; top: 0; - z-index: 10; + z-index: 1; background-color: var(--Base-Surface-Subtle-Normal); border-bottom: 1px solid var(--Base-Border-Subtle); overflow-x: auto; diff --git a/components/ContentType/HotelPage/hotelPage.module.css b/components/ContentType/HotelPage/hotelPage.module.css index a594f1902..0c4820ef0 100644 --- a/components/ContentType/HotelPage/hotelPage.module.css +++ b/components/ContentType/HotelPage/hotelPage.module.css @@ -22,12 +22,7 @@ } .mapContainer { - position: fixed; - bottom: var(--Spacing-x5); - display: flex; - justify-content: center; - width: 100vw; - z-index: 1; + display: none; } .introContainer { @@ -48,13 +43,13 @@ padding: var(--Spacing-x6) 0; } .mapContainer { + display: flex; grid-area: mapContainer; + align-self: start; position: sticky; top: 0; - align-self: start; - justify-content: initial; + justify-content: center; width: 100%; - z-index: 0; } .pageContainer > nav { diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 6d55da887..4c905004e 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -1,3 +1,4 @@ +import { env } from "@/env/server" import { serverClient } from "@/lib/trpc/server" import AmenitiesList from "./AmenitiesList" @@ -5,7 +6,10 @@ import Facilities from "./Facilities" import { MOCK_FACILITIES } from "./Facilities/mockData" import { setActivityCard } from "./Facilities/utils" import IntroSection from "./IntroSection" -import Map from "./Map" +import DynamicMap from "./Map/DynamicMap" +import MapCard from "./Map/MapCard" +import MobileMapToggle from "./Map/MobileMapToggle" +import StaticMap from "./Map/StaticMap" import PreviewImages from "./PreviewImages" import { Rooms } from "./Rooms" import SidePeeks from "./SidePeeks" @@ -14,6 +18,7 @@ import TabNavigation from "./TabNavigation" import styles from "./hotelPage.module.css" export default async function HotelPage() { + const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY const hotelData = await serverClient().hotel.get({ include: ["RoomCategories"], }) @@ -61,9 +66,21 @@ export default async function HotelPage() { <Rooms rooms={roomCategories} /> <Facilities facilities={facilities} /> </main> - <aside className={styles.mapContainer}> - <Map coordinates={coordinates} hotelName={hotelName} /> - </aside> + {googleMapsApiKey ? ( + <> + <aside className={styles.mapContainer}> + {/* <Map coordinates={coordinates} hotelName={hotelName} /> */} + <StaticMap coordinates={coordinates} hotelName={hotelName} /> + <MapCard hotelName={hotelName} /> + </aside> + <MobileMapToggle /> + <DynamicMap + apiKey={googleMapsApiKey} + hotelName={hotelName} + coordinates={coordinates} + /> + </> + ) : null} <SidePeeks /> </div> ) diff --git a/components/Icons/Minus.tsx b/components/Icons/Minus.tsx new file mode 100644 index 000000000..84ada8943 --- /dev/null +++ b/components/Icons/Minus.tsx @@ -0,0 +1,23 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function MinusIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + <svg + className={classNames} + xmlns="http://www.w3.org/2000/svg" + width="14" + height="2" + viewBox="0 0 14 2" + fill="none" + {...props} + > + <path + d="M1.125 1.86249C0.877083 1.86249 0.669267 1.77842 0.50155 1.61029C0.33385 1.44217 0.25 1.23384 0.25 0.985287C0.25 0.736754 0.33385 0.529154 0.50155 0.362488C0.669267 0.195821 0.877083 0.112488 1.125 0.112488H12.875C13.1229 0.112488 13.3307 0.196555 13.4985 0.364688C13.6662 0.532805 13.75 0.741138 13.75 0.989688C13.75 1.23822 13.6662 1.44582 13.4985 1.61249C13.3307 1.77915 13.1229 1.86249 12.875 1.86249H1.125Z" + fill="#4D001B" + /> + </svg> + ) +} diff --git a/components/Icons/Plus.tsx b/components/Icons/Plus.tsx new file mode 100644 index 000000000..b8826bb2f --- /dev/null +++ b/components/Icons/Plus.tsx @@ -0,0 +1,23 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function PlusIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + <svg + className={classNames} + xmlns="http://www.w3.org/2000/svg" + width="14" + height="14" + viewBox="0 0 14 14" + fill="none" + {...props} + > + <path + d="M6.125 7.86249H1.125C0.877083 7.86249 0.669267 7.77842 0.50155 7.61029C0.33385 7.44217 0.25 7.23384 0.25 6.98529C0.25 6.73675 0.33385 6.52915 0.50155 6.36249C0.669267 6.19582 0.877083 6.11249 1.125 6.11249H6.125V1.11249C6.125 0.864571 6.20907 0.656755 6.3772 0.489038C6.54532 0.321338 6.75365 0.237488 7.0022 0.237488C7.25073 0.237488 7.45833 0.321338 7.625 0.489038C7.79167 0.656755 7.875 0.864571 7.875 1.11249V6.11249H12.875C13.1229 6.11249 13.3307 6.19656 13.4985 6.36469C13.6662 6.5328 13.75 6.74114 13.75 6.98969C13.75 7.23822 13.6662 7.44582 13.4985 7.61249C13.3307 7.77915 13.1229 7.86249 12.875 7.86249H7.875V12.8625C7.875 13.1104 7.79093 13.3182 7.6228 13.4859C7.45468 13.6536 7.24635 13.7375 6.9978 13.7375C6.74927 13.7375 6.54167 13.6536 6.375 13.4859C6.20833 13.3182 6.125 13.1104 6.125 12.8625V7.86249Z" + fill="#26201E" + /> + </svg> + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index d7508b83a..6fbc26c0f 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -33,12 +33,14 @@ import { InfoCircleIcon, LocationIcon, LockIcon, + MinusIcon, ParkingIcon, People2Icon, PersonIcon, PetsIcon, PhoneIcon, PlusCircleIcon, + PlusIcon, RestaurantIcon, SaunaIcon, SearchIcon, @@ -114,6 +116,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null { return LocationIcon case IconName.Lock: return LockIcon + case IconName.Minus: + return MinusIcon case IconName.Parking: return ParkingIcon case IconName.Person: @@ -124,6 +128,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null { return PetsIcon case IconName.Phone: return PhoneIcon + case IconName.Plus: + return PlusIcon case IconName.PlusCircle: return PlusCircleIcon case IconName.Restaurant: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 9ed34a098..80f978546 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -29,11 +29,13 @@ export { default as ImageIcon } from "./Image" export { default as InfoCircleIcon } from "./InfoCircle" export { default as LocationIcon } from "./Location" export { default as LockIcon } from "./Lock" +export { default as MinusIcon } from "./Minus" export { default as ParkingIcon } from "./Parking" export { default as People2Icon } from "./People2" export { default as PersonIcon } from "./Person" export { default as PetsIcon } from "./Pets" export { default as PhoneIcon } from "./Phone" +export { default as PlusIcon } from "./Plus" export { default as PlusCircleIcon } from "./PlusCircle" export { default as PriceTagIcon } from "./PriceTag" export { default as RestaurantIcon } from "./Restaurant" diff --git a/components/TempDesignSystem/Text/Caption/caption.module.css b/components/TempDesignSystem/Text/Caption/caption.module.css index d359c2a36..d2dcd9208 100644 --- a/components/TempDesignSystem/Text/Caption/caption.module.css +++ b/components/TempDesignSystem/Text/Caption/caption.module.css @@ -48,7 +48,7 @@ } .textMediumContrast { - color: var(--UI-Text-Medium-contrast); + color: var(--Base-Text-Medium-contrast); } .red { diff --git a/package-lock.json b/package-lock.json index cae0b620f..092398c1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +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", "class-variance-authority": "^0.7.0", "clean-deep": "^3.4.0", "dayjs": "^1.11.10", @@ -6268,6 +6269,11 @@ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "license": "MIT" }, + "node_modules/@types/google.maps": { + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.0.tgz", + "integrity": "sha512-rZPrbNHoGxeY70uuQYFLGQqcz5mLd3pZy0u286GSugvN7PLFsHNRF2wN2QXtUgNiC33IC0LX+MD3LGAC3wN7Eg==" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -6649,6 +6655,19 @@ "@opentelemetry/sdk-trace-base": "^1.19.0" } }, + "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==", + "dependencies": { + "@types/google.maps": "^3.54.10", + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -9968,8 +9987,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", diff --git a/package.json b/package.json index 963a2a49a..51a08a525 100644 --- a/package.json +++ b/package.json @@ -43,6 +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", "class-variance-authority": "^0.7.0", "clean-deep": "^3.4.0", "dayjs": "^1.11.10", diff --git a/public/_static/icons/map-marker-pin.svg b/public/_static/icons/map-marker-pin.svg deleted file mode 100644 index 93bcc1732..000000000 --- a/public/_static/icons/map-marker-pin.svg +++ /dev/null @@ -1,41 +0,0 @@ -<svg width="85" height="108" viewBox="0 0 85 108" fill="none" xmlns="http://www.w3.org/2000/svg"> -<g filter="url(#filter0_d_1623_21496)"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M52.842 72.1048C64.7996 67.7006 73.3378 56.108 73.3378 42.5C73.3378 25.103 59.383 11 42.1689 11C24.9548 11 11 25.103 11 42.5C11 56.6689 20.2565 68.6528 32.9914 72.6123L41.236 86.0475C42.0252 87.3336 43.9 87.3158 44.6647 86.015L52.842 72.1048Z" fill="white"/> -<circle cx="42" cy="43" r="27" fill="#CD0921"/> -</g> -<path fill-rule="evenodd" clip-rule="evenodd" d="M34.1848 46.7474C33.592 46.8953 32.9991 46.9446 32.4062 46.9446C30.2818 46.9446 28.8984 45.9587 28.8984 43.6418C28.8984 41.4234 30.3312 40.4868 32.3568 40.4868C32.9497 40.4868 33.5426 40.5361 34.1354 40.6347V41.7192C33.6908 41.6699 33.3449 41.6206 32.9497 41.6206C31.3193 41.6206 30.5288 42.1136 30.5288 43.7404C30.5288 45.2685 31.2699 45.8108 32.9497 45.8108C33.3944 45.8108 33.7402 45.7615 34.1354 45.7122V46.7474H34.1848Z" fill="white"/> -<path d="M56.0547 46.7961H57.6357V40.5848H56.0547V46.7961ZM56.0547 39.3524H57.6357V37.9229H56.0547V39.3524Z" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M54.7828 46.6483C54.4863 46.6975 53.3994 46.8947 52.3125 46.8947C50.2869 46.8947 48.8047 45.9581 48.8047 43.6905C48.8047 41.5215 50.1386 40.437 52.1643 40.437C52.4607 40.437 52.9548 40.4862 53.2018 40.4862V37.9229H54.7828C54.7828 38.2186 54.7828 46.5004 54.7828 46.6483ZM53.2512 45.7116V41.6694C53.0536 41.6694 52.7077 41.6201 52.3619 41.6201C50.9291 41.6201 50.3857 42.3102 50.3857 43.7398C50.3857 45.0215 50.8797 45.8102 52.3125 45.8102C52.6089 45.8102 52.9054 45.7609 53.2512 45.7116Z" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M47.7727 46.7967H46.2411V42.7051C46.2411 42.015 45.9446 41.6206 44.9565 41.6206C44.2648 41.6206 43.5238 41.7685 43.5238 41.7685V46.7967H41.9922V40.7826C42.091 40.7826 44.0672 40.4868 45.2036 40.4868C46.5869 40.4868 47.8221 40.7333 47.8221 42.6065V46.7967H47.7727Z" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M40.6998 46.7954C39.6129 46.894 38.773 46.9432 37.8343 46.9432C36.2533 46.9432 34.9688 46.6968 34.9688 44.9221C34.9688 43.1968 36.5497 43.0982 37.9825 43.0489C38.3283 43.0489 38.7236 43.0489 39.1188 43.0489C39.1188 41.9643 39.0694 41.57 37.4884 41.57C36.6979 41.57 35.8581 41.7179 35.364 41.8165V40.6827C35.9569 40.5348 36.8956 40.4855 37.5873 40.4362C39.3659 40.4362 40.6998 40.6334 40.6998 42.5066V46.7954ZM39.1188 45.8587V44.0841C38.8224 44.0841 38.2789 44.0841 38.0813 44.0841C37.1426 44.0841 36.5497 44.1334 36.5497 45.0207C36.5497 45.908 37.2908 45.9573 38.0319 45.9573C38.4766 45.908 38.7236 45.908 39.1188 45.8587Z" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M63.927 46.7474C63.3342 46.8953 62.7413 46.9446 62.1484 46.9446C60.024 46.9446 58.6406 45.9587 58.6406 43.6418C58.6406 41.4234 60.0734 40.4868 62.099 40.4868C62.6919 40.4868 63.2848 40.5361 63.8776 40.6347V41.7192C63.433 41.6699 63.0871 41.6206 62.6919 41.6206C61.0615 41.6206 60.271 42.1136 60.271 43.7404C60.271 45.2685 61.0121 45.8108 62.6919 45.8108C63.1365 45.8108 63.4824 45.7615 63.8776 45.7122V46.7474H63.927Z" fill="white"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M28.1054 44.3303C28.1054 46.1542 26.6726 46.943 24.5482 46.943C23.1648 46.943 22.1767 46.6965 22.0285 46.6472V45.3655C22.5226 45.4641 23.5601 45.7106 24.4494 45.7106C25.3387 45.7106 26.475 45.5134 26.475 44.5275C26.475 43.8866 25.981 43.6401 25.0917 43.3936C24.7458 43.2951 24.4 43.1965 24.0541 43.0979C22.8684 42.7528 21.9297 42.2598 21.9297 40.8796C21.9297 38.9077 23.5107 38.4147 25.3387 38.4147C26.4256 38.4147 27.3149 38.6119 27.4631 38.6612V39.8443C27.2655 39.795 26.4256 39.5979 25.4869 39.5979C24.5976 39.5979 23.5601 39.6964 23.5601 40.6824C23.5601 41.4218 24.1529 41.619 24.9928 41.8162C25.3881 41.9641 25.7833 42.0134 26.1786 42.1612C27.2161 42.457 28.1054 42.95 28.1054 44.3303Z" fill="white"/> -<g filter="url(#filter1_d_1623_21496)"> -<rect x="37" y="93" width="12" height="12" rx="6" fill="white"/> -<circle cx="43.1172" cy="99" r="4" fill="#CD0921"/> -</g> -<defs> -<filter id="filter0_d_1623_21496" x="0" y="0" width="84.3379" height="98.0014" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> -<feFlood flood-opacity="0" result="BackgroundImageFix"/> -<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> -<feMorphology radius="3" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_1623_21496"/> -<feOffset/> -<feGaussianBlur stdDeviation="4"/> -<feComposite in2="hardAlpha" operator="out"/> -<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/> -<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1623_21496"/> -<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1623_21496" result="shape"/> -</filter> -<filter id="filter1_d_1623_21496" x="34" y="90" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> -<feFlood flood-opacity="0" result="BackgroundImageFix"/> -<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> -<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_1623_21496"/> -<feOffset/> -<feGaussianBlur stdDeviation="1"/> -<feComposite in2="hardAlpha" operator="out"/> -<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/> -<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1623_21496"/> -<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1623_21496" result="shape"/> -</filter> -</defs> -</svg> diff --git a/stores/hotel-page.ts b/stores/hotel-page.ts new file mode 100644 index 000000000..162af53b8 --- /dev/null +++ b/stores/hotel-page.ts @@ -0,0 +1,15 @@ +import { create } from "zustand" + +interface HotelPageState { + isDynamicMapOpen: boolean + openDynamicMap: () => void + closeDynamicMap: () => void +} + +const useHotelPageStore = create<HotelPageState>((set) => ({ + isDynamicMapOpen: false, + openDynamicMap: () => set({ isDynamicMapOpen: true }), + closeDynamicMap: () => set({ isDynamicMapOpen: false }), +})) + +export default useHotelPageStore diff --git a/types/components/icon.ts b/types/components/icon.ts index b0f6359d8..a1d679c43 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -38,11 +38,13 @@ export enum IconName { Instagram = "Instagram", Location = "Location", Lock = "Lock", + Minus = "Minus", Parking = "Parking", Person = "Person", People2 = "People2", Pets = "Pets", Phone = "Phone", + Plus = "Plus", PlusCircle = "PlusCircle", Restaurant = "Restaurant", Sauna = "Sauna",