Google map with custom markers

This commit is contained in:
2025-07-23 17:57:58 +05:00
parent 0b9741e90f
commit 6a415ec165
10 changed files with 180 additions and 22 deletions
+1
View File
@@ -3,3 +3,4 @@
# VITE_API_URL=http://194.26.138.94:4002
# VITE_API_URL=https://irthtest.online/api
VITE_API_URL=https://irth.graff.estate/api
GOOGLE_MAP_API_KEY=AIzaSyD1aCnh8qEIh9ACrZWeHddYJLyHMX4KsoE
+18
View File
@@ -4,11 +4,13 @@
"": {
"name": "irth-new",
"dependencies": {
"@googlemaps/markerclusterer": "^2.6.2",
"@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-devtools": "^5.74.7",
"@tweenjs/tween.js": "^25.0.0",
"@uidotdev/usehooks": "^2.4.1",
"@vis.gl/react-google-maps": "^1.5.4",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
@@ -111,6 +113,8 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.8", "", { "dependencies": { "@eslint/core": "^0.13.0", "levn": "^0.4.1" } }, "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA=="],
"@googlemaps/markerclusterer": ["@googlemaps/markerclusterer@2.6.2", "", { "dependencies": { "@types/supercluster": "^7.1.3", "fast-equals": "^5.2.2", "supercluster": "^8.0.1" } }, "sha512-U6uVhq8iWhiIckA89sgRu8OK35mjd6/3CuoZKWakKEf0QmRRWpatlsPb3kqXkoWSmbcZkopRiI4dnW6DQSd7bQ=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
@@ -237,6 +241,10 @@
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/google.maps": ["@types/google.maps@3.58.1", "", {}, "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.14.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA=="],
@@ -245,6 +253,8 @@
"@types/react-dom": ["@types/react-dom@19.1.1", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w=="],
"@types/supercluster": ["@types/supercluster@7.1.3", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/type-utils": "8.29.0", "@typescript-eslint/utils": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ=="],
@@ -265,6 +275,8 @@
"@uidotdev/usehooks": ["@uidotdev/usehooks@2.4.1", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg=="],
"@vis.gl/react-google-maps": ["@vis.gl/react-google-maps@1.5.4", "", { "dependencies": { "@types/google.maps": "^3.54.10", "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "react": ">=16.8.0 || ^19.0 || ^19.0.0-rc", "react-dom": ">=16.8.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-pD3e2wDtOfd439mamkacRgrM6I2B/lue61QCR0pGQT8MVaG9pz9/LajHbsjZW2lms8Ao8mf2PQJeiGC2FxI0Fw=="],
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.8.1", "", { "dependencies": { "@swc/core": "^1.11.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" } }, "sha512-aEUPCckHDcFyxpwFm0AIkbtv6PpUp3xTb9wYGFjtABynXjCYKkWoxX0AOK9NT9XCrdk6mBBUOeHQS+RKdcNO1A=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
@@ -343,6 +355,8 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@@ -403,6 +417,8 @@
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"kdbush": ["kdbush@4.0.2", "", {}, "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"ky": ["ky@1.8.1", "", {}, "sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw=="],
@@ -513,6 +529,8 @@
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="],
+2
View File
@@ -10,11 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"@googlemaps/markerclusterer": "^2.6.2",
"@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-devtools": "^5.74.7",
"@tweenjs/tween.js": "^25.0.0",
"@uidotdev/usehooks": "^2.4.1",
"@vis.gl/react-google-maps": "^1.5.4",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
@@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="16" fill="#0D1922"/>
<path d="M13 11.6869H11C10.4477 11.6869 10 12.1346 10 12.6869V22C10 22.5523 10.4477 23 11 23H20.9993C21.5516 23 21.9993 22.5523 21.9993 22.0001L21.9999 12.687C22 12.1346 21.5522 11.6869 20.9999 11.6869H19M13 11.6869V11C13 9.34315 14.3431 8 16 8V8C17.6569 8 19 9.34315 19 11V11.6869M13 11.6869H19" stroke="white" stroke-width="1.5" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

+27 -15
View File
@@ -12,10 +12,11 @@ import TextBox from "./ui/TextBox";
import { useRef } from "react";
import { useScroll } from "motion/react";
import MarasiDriveMapCard from "./MarasiDriveMapCard";
import GoogleMap from "./GoogleMap";
import MarasiDriveNeighboursSliderTablet from "./MarasiDriveNeighboursSliderTablet";
import CustomScrollBar from "./ui/ScrollBar";
import BrochureButton from "./ui/BrochureButton";
import { marasiDriveMapData } from "../data/mapMarasiDrive";
function AboutMarasiDrive() {
const target = useRef<HTMLDivElement>(null);
@@ -98,19 +99,32 @@ function AboutMarasiDrive() {
destination for artful inspiration and cleverly activated spaces.`}
</p>
</div>
<div ref={homeSliderRef} className="flex max-2xl:flex-wrap max-2xl:justify-center 2xl:gap-[0.556vw] gap-2 md:max-2xl:w-[93.75vw] max-md:flex-row max-md:flex-nowrap max-md:overflow-x-scroll max-md:overflow-y-hidden max-md:justify-start max-md:snap-x max-md:snap-mandatory [&::-webkit-scrollbar]:h-[1.111vw] [&::-webkit-scrollbar]:w-[none] [&::-webkit-scrollbar-thumb]:bg-transparent [&::-webkit-scrollbar-thumb]:w-4 [&::-webkit-scrollbar-thumb]:rounded-full max-md:-mx-4 max-md:px-4">
<div
ref={homeSliderRef}
className="flex max-2xl:flex-wrap max-2xl:justify-center 2xl:gap-[0.556vw] gap-2 md:max-2xl:w-[93.75vw] max-md:flex-row max-md:flex-nowrap max-md:overflow-x-scroll max-md:overflow-y-hidden max-md:justify-start max-md:snap-x max-md:snap-mandatory [&::-webkit-scrollbar]:h-[1.111vw] [&::-webkit-scrollbar]:w-[none] [&::-webkit-scrollbar-thumb]:bg-transparent [&::-webkit-scrollbar-thumb]:w-4 [&::-webkit-scrollbar-thumb]:rounded-full max-md:-mx-4 max-md:px-4"
>
{marasiDriveFeatures.map(({ image, name }) => (
<div key={name} className="relative md:max-2xl:w-[30.208vw] max-md:w-full max-md:max-w-[530px] max-md:flex-shrink-0 max-md:snap-center">
<div
key={name}
className="relative md:max-2xl:w-[30.208vw] max-md:w-full max-md:max-w-[530px] max-md:flex-shrink-0 max-md:snap-center"
>
<img
src={image}
alt={name}
className="object-cover object-center 2xl:rounded-[1.667vw] max-md:!aspect-[328/287] max-md:rounded-2xl md:max-2xl:rounded-[3.125vw]"
/>
<span className="md:hidden absolute bottom-[16px] text-white text-h5 left-1/2 -translate-x-1/2 text-center text-nowrap">{name}</span>
<span className="md:hidden absolute bottom-[16px] text-white text-h5 left-1/2 -translate-x-1/2 text-center text-nowrap">
{name}
</span>
</div>
))}
</div>
<CustomScrollBar containerRef={homeSliderRef} inlinePadding={16} trackStyle="min-md:hidden" thumbStyle="min-md:hidden" />
<CustomScrollBar
containerRef={homeSliderRef}
inlinePadding={16}
trackStyle="min-md:hidden"
thumbStyle="min-md:hidden"
/>
</section>
<section className="bg-white w-full overflow-clip flex flex-col 2xl:gap-[4.444vw] gap-12 2xl:pt-[9.444vw] 2xl:px-[2.222vw] 2xl:pb-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 md:max-2xl:pb-8 pt-16 px-4 pb-8">
<div className="flex flex-col 2xl:gap-[2.222vw] md:max-2xl:gap-8 gap-6 items-center">
@@ -165,7 +179,7 @@ function AboutMarasiDrive() {
</p>
</div>
<MarasiDriveInteriorsSlider />
<MarasiDriveInteriorsSliderMobile/>
<MarasiDriveInteriorsSliderMobile />
</section>
</div>
<div className="2xl:sticky relative">
@@ -224,18 +238,14 @@ function AboutMarasiDrive() {
{marasiDriveMapCards.map((card) => (
<MarasiDriveMapCard {...card} key={card.title} />
))}
<div className="col-start-3 col-span-full row-start-1 row-span-full">
<img
src="/images/about-complex/marasi-drive/map/map.png"
alt=""
className="object-cover size-full 2xl:rounded-[1.667vw]"
/>
<div className="col-start-3 col-span-full row-start-1 row-span-full rounded-[1.667vw] overflow-hidden">
<GoogleMap mapCenter={marasiDriveMapData.mapCenter} />
</div>
</div>
<MarasiDriveMapMobile/>
<MarasiDriveMapMobile />
</section>
<section className="bg-white w-full overflow-clip flex items-stretch justify-center gap-[1.111vw] 2xl:px-[10.278vw] 2xl:pt-[9.444vw] 2xl:pb-[15vw] md:max-2xl:py-[104px] md:mx-2xl:px-6 max-md:pt-20 max-md:pb-12 px-4 max-2xl:flex-col-reverse md:max-2xl:gap-[6.25vw] md:max-2xl:px-6">
<div >
<div>
<img
src="/images/about-complex/marasi-drive/podium.png"
className="object-cover size-full rounded-[1.667vw] max-md:mt-[48px] max-md:aspect-[328/400] max-md:rounded-[6.667vw] md:max-2xl:rounded-[3.125vw] md:max-2xl:aspect-[720/400]"
@@ -254,7 +264,9 @@ function AboutMarasiDrive() {
</p>
</div>
<div className="space-y-[1.111vw]">
<h5 className="text-h5 font-medium max-md:mb-[16px] md:max-2xl:mb-[2.083vw]">Download our brochures</h5>
<h5 className="text-h5 font-medium max-md:mb-[16px] md:max-2xl:mb-[2.083vw]">
Download our brochures
</h5>
<div className="space-y-[0.833vw] max-md:space-y-[3.333vw] md:max-2xl:space-y-[1.563vw]">
<BrochureButton
title={"Main Brochure"}
+88
View File
@@ -0,0 +1,88 @@
/* eslint-disable no-loss-of-precision */
import { Map } from "@vis.gl/react-google-maps";
import MapMarker from "./ui/MapMarker";
import IGMapPoi from "../types/IGMapPoi";
import MarasiMarker from "../../public/images/map/markers/marasi-drive.png";
import MarasiPopup from "../../public/images/map/markers/popups/marasi-drive.png";
import MarinaMarker from "../../public/images/map/markers/dubai-marina.png";
import MarinaPopup from "../../public/images/map/markers/popups/dubai-marina.png";
import ShopIcon from "../../public/images/map/markers/points/shop-point.svg";
interface IGMapProps {
mapCenter: google.maps.LatLngLiteral;
defaultZoom?: number;
markers?: IGMapPoi[];
minZoom?: number;
disableUI?: boolean;
}
export default function GoogleMap({
mapCenter,
defaultZoom = 14,
minZoom = 10,
disableUI = true,
}: IGMapProps) {
return (
<Map
mapId={"30ff24bdc133c941ee0d0608"}
defaultCenter={mapCenter}
defaultZoom={defaultZoom}
disableDefaultUI={disableUI}
minZoom={minZoom}
gestureHandling={"greedy"}
>
<MapMarkers />
</Map>
);
}
function MapMarkers() {
const marasiDriveMarker = (
<div className="flex items-end hover:cursor-pointer">
<img src={MarasiMarker} alt="" />
<img className="mb-3" src={MarasiPopup} alt="" />
</div>
);
const DubaiMarinaMarker = (
<div className="flex items-end hover:cursor-pointer">
<img src={MarinaMarker} alt="" />
<img className="mb-3" src={MarinaPopup} alt="" />
</div>
);
const shopMarker = (
<div>
<img src={ShopIcon} alt="" />
</div>
);
const templateMarkers: IGMapPoi[] = [
{
location: { lat: 25.181504160790247, lng: 55.27565159760525 },
customMarker: marasiDriveMarker,
},
{
location: { lat: 25.069466431595334, lng: 55.128736429300375 },
customMarker: DubaiMarinaMarker,
},
{
location: { lat: 25.193476007744233, lng: 55.274782084720286 },
customMarker: shopMarker,
},
{
location: { lat: 25.193476007744233, lng: 55.244782084720286 },
customMarker: shopMarker,
},
];
return (
<>
{templateMarkers.map((poi: IGMapPoi, index: number) => (
<MapMarker key={String(index)} location={poi.location}>
{poi.customMarker}
</MapMarker>
))}
</>
);
}
+21
View File
@@ -0,0 +1,21 @@
import { AdvancedMarker, Pin } from "@vis.gl/react-google-maps";
interface IGMapMarker {
key: string;
location: google.maps.LatLngLiteral;
children: React.ReactNode;
}
export default function MapMarker({ key, location, children }: IGMapMarker) {
return (
<AdvancedMarker key={key} position={location}>
<Pin
background={"transparent"}
glyphColor={"transparent"}
borderColor={"transparent"}
>
{children}
</Pin>
</AdvancedMarker>
);
}
+5
View File
@@ -0,0 +1,5 @@
export const marasiDriveMapData = {
mapCenter: { lat: 25.183476007744233, lng: 55.274782084720286 }
}
+9 -6
View File
@@ -1,6 +1,6 @@
// Initialize Eruda for mobile debugging in development
if (import.meta.env.DEV) {
import('eruda').then(eruda => eruda.default.init());
import("eruda").then((eruda) => eruda.default.init());
}
import "./index.css";
@@ -24,6 +24,7 @@ import TestPage from "./pages/TestPage.tsx";
import UnitPage from "./pages/UnitPage.tsx";
import PopupContainer from "./components/PopupContainer.tsx";
import VirtualTourPage from "./pages/VirtualTourPage.tsx";
import { APIProvider } from "@vis.gl/react-google-maps";
const route = createBrowserRouter([
{
@@ -88,10 +89,12 @@ const route = createBrowserRouter([
createRoot(document.getElementById("root")!).render(
<>
<QueryClientProvider client={queryClient}>
<RouterProvider router={route} />
<PopupContainer />
<ModalContainer />
</QueryClientProvider>
<APIProvider apiKey={"AIzaSyD1aCnh8qEIh9ACrZWeHddYJLyHMX4KsoE"}>
<QueryClientProvider client={queryClient}>
<RouterProvider router={route} />
<PopupContainer />
<ModalContainer />
</QueryClientProvider>
</APIProvider>
</>
);
+4
View File
@@ -0,0 +1,4 @@
export default interface IGMapPoi {
location: google.maps.LatLngLiteral;
customMarker?: React.ReactNode;
}