feat(SW-189): added static map to hotel page
This commit is contained in:
37
components/ContentType/HotelPage/Map/MapCard/index.tsx
Normal file
37
components/ContentType/HotelPage/Map/MapCard/index.tsx
Normal file
@@ -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 (
|
||||
<div className={styles.mapCard}>
|
||||
<Caption color="burgundy" textTransform="uppercase" textAlign="center">
|
||||
Nearby
|
||||
</Caption>
|
||||
<Title
|
||||
color="burgundy"
|
||||
level="h3"
|
||||
as="h4"
|
||||
textAlign="center"
|
||||
textTransform="capitalize"
|
||||
>
|
||||
{hotelName}
|
||||
</Title>
|
||||
|
||||
<Button
|
||||
theme="base"
|
||||
intent="secondary"
|
||||
size="small"
|
||||
className={styles.ctaButton}
|
||||
>
|
||||
Explore nearby
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
51
components/ContentType/HotelPage/Map/MobileToggle/index.tsx
Normal file
51
components/ContentType/HotelPage/Map/MobileToggle/index.tsx
Normal file
@@ -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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
37
components/ContentType/HotelPage/Map/StaticMap/index.tsx
Normal file
37
components/ContentType/HotelPage/Map/StaticMap/index.tsx
Normal file
@@ -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 <img src={src} alt={alt} />
|
||||
}
|
||||
88
components/ContentType/HotelPage/Map/StaticMap/util.ts
Normal file
88
components/ContentType/HotelPage/Map/StaticMap/util.ts
Normal file
@@ -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}`
|
||||
}
|
||||
17
components/ContentType/HotelPage/Map/index.tsx
Normal file
17
components/ContentType/HotelPage/Map/index.tsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<div className={styles.desktopContent}>
|
||||
<StaticMap coordinates={coordinates} hotelName={hotelName} />
|
||||
<MapCard hotelName={hotelName} />
|
||||
</div>
|
||||
<MobileToggle />
|
||||
</>
|
||||
)
|
||||
}
|
||||
12
components/ContentType/HotelPage/Map/map.module.css
Normal file
12
components/ContentType/HotelPage/Map/map.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.desktopContent {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.desktopContent {
|
||||
align-self: start;
|
||||
position: relative;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
}
|
||||
}
|
||||
@@ -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 <img src={src} alt={alt} />
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
<Facilities facilities={facilities} />
|
||||
</main>
|
||||
<aside className={styles.mapContainer}>
|
||||
<StaticMap coordinates={coordinates} hotelName={hotelName} />
|
||||
<Map coordinates={coordinates} hotelName={hotelName} />
|
||||
</aside>
|
||||
<SidePeeks />
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
41
public/_static/icons/map-marker-pin.svg
Normal file
41
public/_static/icons/map-marker-pin.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -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(),
|
||||
|
||||
3
types/components/hotelPage/mapCard.ts
Normal file
3
types/components/hotelPage/mapCard.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface MapCardProps {
|
||||
hotelName: string
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
type Coordinates = {
|
||||
latitude: number
|
||||
longitude: number
|
||||
lat: number
|
||||
lng: number
|
||||
}
|
||||
|
||||
export type StaticMapProps = {
|
||||
|
||||
Reference in New Issue
Block a user