diff --git a/components/ContentType/HotelPage/Map/MapCard/index.tsx b/components/ContentType/HotelPage/Map/MapCard/index.tsx
new file mode 100644
index 000000000..3b98bde9a
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/MapCard/index.tsx
@@ -0,0 +1,37 @@
+"use client"
+
+import Button from "@/components/TempDesignSystem/Button"
+import Caption from "@/components/TempDesignSystem/Text/Caption"
+import Title from "@/components/TempDesignSystem/Text/Title"
+
+import styles from "./mapCard.module.css"
+
+import { MapCardProps } from "@/types/components/hotelPage/mapCard"
+
+export default function MapCard({ hotelName }: MapCardProps) {
+ return (
+
+
+ Nearby
+
+
+ {hotelName}
+
+
+
+
+ )
+}
diff --git a/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css b/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css
new file mode 100644
index 000000000..7fb7919ec
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/MapCard/mapCard.module.css
@@ -0,0 +1,14 @@
+.mapCard {
+ position: absolute;
+ bottom: 15%;
+ 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 {
+ margin-top: var(--Spacing-x2);
+}
diff --git a/components/ContentType/HotelPage/Map/MobileToggle/index.tsx b/components/ContentType/HotelPage/Map/MobileToggle/index.tsx
new file mode 100644
index 000000000..bce32e387
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/MobileToggle/index.tsx
@@ -0,0 +1,51 @@
+"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 (
+
+
+
+
+ )
+}
diff --git a/components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css b/components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css
new file mode 100644
index 000000000..b93941151
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/MobileToggle/mobileToggle.module.css
@@ -0,0 +1,43 @@
+.mobileToggle {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--Spacing-x-half);
+ align-items: center;
+ border-radius: 4rem;
+ background-color: var(--Base-Surface-Primary-light-Normal);
+ box-shadow: 0 0 30px 2px rgba(0, 0, 0, 0.15);
+ padding: 0.375rem;
+}
+
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--Spacing-x-half);
+ padding: var(--Spacing-x1);
+ background-color: var(--Base-Surface-Primary-light-Normal);
+ border-width: 0;
+ cursor: pointer;
+ border-radius: 2.5rem;
+ color: var(--Base-Text-Accent);
+ font-family: var(--typography-Body-Bold-fontFamily);
+ font-size: var(--typography-Body-Bold-fontSize);
+ font-weight: 500;
+}
+.button:hover {
+ background-color: var(--Base-Surface-Primary-light-Hover);
+}
+
+.button.active {
+ background-color: var(--Primary-Strong-Surface-Normal);
+ color: var(--Base-Text-Inverted);
+}
+.button.active:hover {
+ background-color: var(--Primary-Strong-Surface-Hover);
+}
+
+@media screen and (min-width: 1367px) {
+ .mobileToggle {
+ display: none;
+ }
+}
diff --git a/components/ContentType/HotelPage/StaticMap/ReadMe.md b/components/ContentType/HotelPage/Map/StaticMap/ReadMe.md
similarity index 100%
rename from components/ContentType/HotelPage/StaticMap/ReadMe.md
rename to components/ContentType/HotelPage/Map/StaticMap/ReadMe.md
diff --git a/components/ContentType/HotelPage/Map/StaticMap/index.tsx b/components/ContentType/HotelPage/Map/StaticMap/index.tsx
new file mode 100644
index 000000000..7eeeed944
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/StaticMap/index.tsx
@@ -0,0 +1,37 @@
+/* eslint-disable @next/next/no-img-element */
+
+import { env } from "@/env/server"
+
+import { getIntl } from "@/i18n"
+
+import { calculateLatWithOffset, getUrlWithSignature } from "./util"
+
+import { StaticMapProps } from "@/types/components/hotelPage/staticMap"
+
+export default async function StaticMap({
+ coordinates,
+ hotelName,
+ zoomLevel = 16,
+}: StaticMapProps) {
+ const intl = await getIntl()
+ const key = env.GOOGLE_STATIC_MAP_KEY
+ const secret = env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET
+ const baseUrl = "https://maps.googleapis.com/maps/api/staticmap"
+ const { lng, lat } = coordinates
+ const mapHeight = 785
+ const size = `380x${mapHeight}`
+ const mapLatitude = calculateLatWithOffset(lat, mapHeight * 0.2, 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}`
+ // Google Maps Static API only supports images smaller than 640x640px. Read: https://developers.google.com/maps/documentation/maps-static/start#Largerimagesizes
+ 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}`
+ )
+
+ const src = getUrlWithSignature(url, secret)
+
+ return
+}
diff --git a/components/ContentType/HotelPage/Map/StaticMap/util.ts b/components/ContentType/HotelPage/Map/StaticMap/util.ts
new file mode 100644
index 000000000..d7c933209
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/StaticMap/util.ts
@@ -0,0 +1,88 @@
+import crypto from "node:crypto"
+
+// Helper function to calculate the latitude offset
+export function calculateLatWithOffset(
+ latitude: number,
+ offsetPx: number,
+ zoomLevel: number
+): number {
+ const earthCircumference = 40075017 // Earth's circumference in meters
+ const tileSize = 256 // Height of a tile in pixels (standard in Google Maps)
+
+ // Calculate ground resolution (meters per pixel) at the given latitude and zoom level
+ const groundResolution =
+ (earthCircumference * Math.cos((latitude * Math.PI) / 180)) /
+ (tileSize * Math.pow(2, zoomLevel))
+
+ // Calculate the number of meters for the given offset in pixels
+ const metersOffset = groundResolution * offsetPx
+
+ // Convert the meters offset into a latitude offset (1 degree latitude is ~111,320 meters)
+ const latOffset = metersOffset / 111320
+
+ // Return the new latitude by subtracting the offset
+ return latitude - latOffset
+}
+
+/**
+ * Util functions taken from https://developers.google.com/maps/documentation/maps-static/digital-signature#sample-code-for-url-signing
+ * Used to sign the URL for the Google Static Maps API.
+ */
+
+/**
+ * Convert from 'web safe' base64 to true base64.
+ *
+ * @param {string} safeEncodedString The code you want to translate
+ * from a web safe form.
+ * @return {string}
+ */
+function removeWebSafe(safeEncodedString: string) {
+ return safeEncodedString.replace(/-/g, "+").replace(/_/g, "/")
+}
+
+/**
+ * Convert from true base64 to 'web safe' base64
+ *
+ * @param {string} encodedString The code you want to translate to a
+ * web safe form.
+ * @return {string}
+ */
+function makeWebSafe(encodedString: string) {
+ return encodedString.replace(/\+/g, "-").replace(/\//g, "_")
+}
+
+/**
+ * Takes a base64 code and decodes it.
+ *
+ * @param {string} code The encoded data.
+ * @return {string}
+ */
+function decodeBase64Hash(code: string) {
+ return Buffer.from(code, "base64")
+}
+
+/**
+ * Takes a key and signs the data with it.
+ *
+ * @param {string} key Your unique secret key.
+ * @param {string} data The url to sign.
+ * @return {string}
+ */
+function encodeBase64Hash(key: Buffer, data: string) {
+ return crypto.createHmac("sha1", key).update(data).digest("base64")
+}
+
+/**
+ * Sign a URL using a secret key.
+ *
+ * @param {URL} url The url you want to sign.
+ * @param {string} secret Your unique secret key.
+ * @return {string}
+ */
+export function getUrlWithSignature(url: URL, secret = "") {
+ const path = url.pathname + url.search
+ const safeSecret = decodeBase64Hash(removeWebSafe(secret))
+ const hashedSignature = makeWebSafe(encodeBase64Hash(safeSecret, path))
+
+ return `${url.toString()}&signature=${hashedSignature}`
+}
diff --git a/components/ContentType/HotelPage/Map/index.tsx b/components/ContentType/HotelPage/Map/index.tsx
new file mode 100644
index 000000000..0b7823f90
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/index.tsx
@@ -0,0 +1,17 @@
+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 (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/components/ContentType/HotelPage/Map/map.module.css b/components/ContentType/HotelPage/Map/map.module.css
new file mode 100644
index 000000000..5fd435396
--- /dev/null
+++ b/components/ContentType/HotelPage/Map/map.module.css
@@ -0,0 +1,12 @@
+.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/StaticMap/index.tsx b/components/ContentType/HotelPage/StaticMap/index.tsx
deleted file mode 100644
index 9bd942222..000000000
--- a/components/ContentType/HotelPage/StaticMap/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { env } from "@/env/server"
-import { getIntl } from "@/i18n"
-import { StaticMapProps } from "@/types/components/hotelPage/staticMap"
-import crypto from "node:crypto"
-
-function removeWebSafe(safeEncodedString: string) {
- return safeEncodedString.replace(/-/g, "+").replace(/_/g, "/")
-}
-
-function makeWebSafe(encodedString: string) {
- return encodedString.replace(/\+/g, "-").replace(/\//g, "_")
-}
-
-function decodeBase64Hash(code: string) {
- return Buffer.from(code, "base64")
-}
-
-function encodeBase64Hash(key: Buffer, data: string) {
- return crypto.createHmac("sha1", key).update(data).digest("base64")
-}
-
-// Google Maps Static API only supports images smaller than 640x640px. Read: https://developers.google.com/maps/documentation/maps-static/start#Largerimagesizes
-export default async function StaticMap({
- coordinates,
- hotelName,
- zoomLevel = 14,
-}: StaticMapProps) {
- const intl = await getIntl()
- const key = env.GOOGLE_STATIC_MAP_KEY
- const secret = env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET
- const safeSecret = decodeBase64Hash(removeWebSafe(secret ?? ""))
- const alt = intl.formatMessage({ id: "Map of HOTEL_NAME" }, { hotelName })
-
- const url = new URL(
- `https://maps.googleapis.com/maps/api/staticmap?center=${coordinates.latitude},${coordinates.longitude}&zoom=${zoomLevel}&size=380x640&key=${key}`
- )
-
- const hashedSignature = makeWebSafe(
- encodeBase64Hash(safeSecret, url.pathname + url.search)
- )
-
- const src = url.toString() + "&signature=" + hashedSignature
-
- return
-}
diff --git a/components/ContentType/HotelPage/hotelPage.module.css b/components/ContentType/HotelPage/hotelPage.module.css
index 78f0cd686..a594f1902 100644
--- a/components/ContentType/HotelPage/hotelPage.module.css
+++ b/components/ContentType/HotelPage/hotelPage.module.css
@@ -22,8 +22,12 @@
}
.mapContainer {
- grid-area: mapContainer;
- display: none;
+ position: fixed;
+ bottom: var(--Spacing-x5);
+ display: flex;
+ justify-content: center;
+ width: 100vw;
+ z-index: 1;
}
.introContainer {
@@ -45,7 +49,12 @@
}
.mapContainer {
grid-area: mapContainer;
- display: block;
+ position: sticky;
+ top: 0;
+ align-self: start;
+ justify-content: initial;
+ width: 100%;
+ z-index: 0;
}
.pageContainer > nav {
diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx
index b1868cb32..6d55da887 100644
--- a/components/ContentType/HotelPage/index.tsx
+++ b/components/ContentType/HotelPage/index.tsx
@@ -1,20 +1,17 @@
import { serverClient } from "@/lib/trpc/server"
-<<<<<<< HEAD
-import { MOCK_FACILITIES } from "./Facilities/mockData"
-import { setActivityCard } from "./Facilities/utils"
-=======
->>>>>>> 491f385b (fix: wrong prop name)
import AmenitiesList from "./AmenitiesList"
import Facilities from "./Facilities"
+import { MOCK_FACILITIES } from "./Facilities/mockData"
+import { setActivityCard } from "./Facilities/utils"
import IntroSection from "./IntroSection"
+import Map from "./Map"
import PreviewImages from "./PreviewImages"
import { Rooms } from "./Rooms"
import SidePeeks from "./SidePeeks"
import TabNavigation from "./TabNavigation"
import styles from "./hotelPage.module.css"
-import StaticMap from "./StaticMap"
export default async function HotelPage() {
const hotelData = await serverClient().hotel.get({
@@ -38,10 +35,10 @@ export default async function HotelPage() {
const facilities = [...MOCK_FACILITIES]
activitiesCard && facilities.push(setActivityCard(activitiesCard))
-
+
const coordinates = {
- latitude: hotelLocation.latitude,
- longitude: hotelLocation.longitude,
+ lat: hotelLocation.latitude,
+ lng: hotelLocation.longitude,
}
return (
@@ -65,7 +62,7 @@ export default async function HotelPage() {
diff --git a/components/TempDesignSystem/Text/Caption/caption.module.css b/components/TempDesignSystem/Text/Caption/caption.module.css
index b27906dbf..d359c2a36 100644
--- a/components/TempDesignSystem/Text/Caption/caption.module.css
+++ b/components/TempDesignSystem/Text/Caption/caption.module.css
@@ -16,6 +16,16 @@
text-decoration: var(--typography-Caption-Bold-textDecoration);
}
+.uppercase {
+ font-family: var(--typography-Caption-Bold-fontFamily);
+ font-size: var(--typography-Caption-Bold-fontSize);
+ font-weight: var(--typography-Caption-Bold-fontWeight);
+ letter-spacing: var(--typography-Caption-Bold-letterSpacing);
+ line-height: var(--typography-Caption-Bold-lineHeight);
+ text-decoration: var(--typography-Caption-Bold-textDecoration);
+ text-transform: uppercase;
+}
+
.regular {
font-family: var(--typography-Caption-Regular-fontFamily);
font-size: var(--typography-Caption-Regular-fontSize);
diff --git a/components/TempDesignSystem/Text/Caption/variants.ts b/components/TempDesignSystem/Text/Caption/variants.ts
index f1dbafa37..b8a3ef6b1 100644
--- a/components/TempDesignSystem/Text/Caption/variants.ts
+++ b/components/TempDesignSystem/Text/Caption/variants.ts
@@ -15,6 +15,7 @@ const config = {
textTransform: {
bold: styles.bold,
regular: styles.regular,
+ uppercase: styles.uppercase,
},
textAlign: {
center: styles.center,
@@ -34,6 +35,7 @@ const fontOnlyConfig = {
textTransform: {
bold: styles.bold,
regular: styles.regular,
+ uppercase: styles.uppercase,
},
},
defaultVariants: {
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json
index fc8186ab8..a8b36f3f9 100644
--- a/i18n/dictionaries/da.json
+++ b/i18n/dictionaries/da.json
@@ -98,6 +98,7 @@
"Log in/Join": "Log på/Tilmeld dig",
"Log out": "Log ud",
"Manage preferences": "Administrer præferencer",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Meetings & Conferences": "Møder & Konferencer",
"Member price": "Medlemspris",
"Member price from": "Medlemspris fra",
diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json
index 9092608b6..f6b45875c 100644
--- a/i18n/dictionaries/de.json
+++ b/i18n/dictionaries/de.json
@@ -96,6 +96,7 @@
"Log in/Join": "Log in/Anmelden",
"Log out": "Ausloggen",
"Manage preferences": "Verwalten von Voreinstellungen",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Member price": "Mitgliederpreis",
"Member price from": "Mitgliederpreis ab",
"Members": "Mitglieder",
diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json
index d08f7fd8b..30038d804 100644
--- a/i18n/dictionaries/en.json
+++ b/i18n/dictionaries/en.json
@@ -102,6 +102,7 @@
"Log in/Join": "Log in/Join",
"Log out": "Log out",
"Manage preferences": "Manage preferences",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Meetings & Conferences": "Meetings & Conferences",
"Member price": "Member price",
"Member price from": "Member price from",
diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json
index b1d91abe3..11a670427 100644
--- a/i18n/dictionaries/fi.json
+++ b/i18n/dictionaries/fi.json
@@ -97,6 +97,7 @@
"Log in/Join": "Kirjaudu sisään/Liittyä",
"Log out": "Kirjaudu ulos",
"Manage preferences": "Asetusten hallinta",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Meetings & Conferences": "Kokoukset & Konferenssit",
"Member price": "Jäsenhinta",
"Member price from": "Jäsenhinta alkaen",
diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json
index 9d66f5df8..fd5a16bab 100644
--- a/i18n/dictionaries/no.json
+++ b/i18n/dictionaries/no.json
@@ -97,6 +97,7 @@
"Log in/Join": "Logg på/Bli med",
"Log out": "Logg ut",
"Manage preferences": "Administrer preferanser",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Meetings & Conferences": "Møter & Konferanser",
"Member price": "Medlemspris",
"Member price from": "Medlemspris fra",
diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json
index be1e56601..13bd7f748 100644
--- a/i18n/dictionaries/sv.json
+++ b/i18n/dictionaries/sv.json
@@ -99,6 +99,7 @@
"Log in/Join": "Logga in/Gå med",
"Log out": "Logga ut",
"Manage preferences": "Hantera inställningar",
+ "Map of HOTEL_NAME": "Map of {hotelName}",
"Meetings & Conferences": "Möten & Konferenser",
"Member price": "Medlemspris",
"Member price from": "Medlemspris från",
diff --git a/public/_static/icons/map-marker-pin.svg b/public/_static/icons/map-marker-pin.svg
new file mode 100644
index 000000000..93bcc1732
--- /dev/null
+++ b/public/_static/icons/map-marker-pin.svg
@@ -0,0 +1,41 @@
+
diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts
index 7f059aefa..192bab55c 100644
--- a/server/routers/hotels/output.ts
+++ b/server/routers/hotels/output.ts
@@ -283,7 +283,7 @@ const parkingPricingSchema = z.object({
const parkingSchema = z.object({
type: z.string(),
name: z.string(),
- address: z.string(),
+ address: z.string().optional(),
numberOfParkingSpots: z.number().optional(),
numberOfChargingSpaces: z.number().optional(),
distanceToHotel: z.number(),
diff --git a/types/components/hotelPage/mapCard.ts b/types/components/hotelPage/mapCard.ts
new file mode 100644
index 000000000..0ea8678e0
--- /dev/null
+++ b/types/components/hotelPage/mapCard.ts
@@ -0,0 +1,3 @@
+export interface MapCardProps {
+ hotelName: string
+}
diff --git a/types/components/hotelPage/staticMap.ts b/types/components/hotelPage/staticMap.ts
index b6694830b..7eb2a2a77 100644
--- a/types/components/hotelPage/staticMap.ts
+++ b/types/components/hotelPage/staticMap.ts
@@ -1,6 +1,6 @@
type Coordinates = {
- latitude: number
- longitude: number
+ lat: number
+ lng: number
}
export type StaticMapProps = {