"use client"; /** * OfficeMap * --------------------------------------------------------------------------- * Client-only Leaflet satellite map (Esri World Imagery) rendering the three * Doormile office markers, plus a row of "jump to office" buttons that fly the * map to a selected office's coordinates and open its popup. * * The map centers with a North latitude offset to keep markers lower in the viewport, * avoiding collisions with the navigation tabs. Popups are compact and styled like * clean business cards. */ import "leaflet/dist/leaflet.css"; import styles from "./OfficeMap.module.css"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import L from "leaflet"; import { MapContainer, Marker, Popup, TileLayer, ZoomControl, useMap } from "react-leaflet"; import { LatLng, ESRI_WORLD_IMAGERY, HQ_OFFICE, MAP_FOCUS_ZOOM, MAP_INITIAL_CENTER, MAP_INITIAL_ZOOM, OFFICE_LOCATIONS, } from "./offices"; type MapStatus = "loading" | "ready" | "error"; /** A request to focus a specific office. `nonce` lets the same office be re-selected. */ type FocusTarget = { id: string; nonce: number }; /** * Build a branded SVG pin. The headquarters pin is larger, carries a red glow * and a soft pulse ring, and sits above every other marker. */ function createMarkerIcon(isHeadquarters: boolean): L.DivIcon { if (isHeadquarters) { return L.divIcon({ className: styles.markerIconHq, html: ` `, iconSize: [40, 52], iconAnchor: [20, 52], popupAnchor: [0, -52], // Anchored to top tip of pin }); } return L.divIcon({ className: styles.markerIcon, html: ` `, iconSize: [30, 40], iconAnchor: [15, 40], popupAnchor: [0, -40], // Anchored to top tip of pin }); } /** * Imperative map effects that need the Leaflet instance: * - keep the viewport sized correctly across resizes / lazy reveals * - snap to the offset HQ on first paint, fly thereafter and open popups. */ function MapController({ focus, markerRefs, }: { focus: FocusTarget | null; markerRefs: React.RefObject>; }) { const map = useMap(); const didInit = useRef(false); // Keep the map correctly sized on container resize / lazy reveal. useEffect(() => { const container = map.getContainer(); let raf = 0; const observer = new ResizeObserver(() => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => map.invalidateSize()); }); observer.observe(container); return () => { cancelAnimationFrame(raf); observer.disconnect(); }; }, [map]); // React to the focused office: snap on first paint, fly thereafter. useEffect(() => { if (!focus) return; const office = OFFICE_LOCATIONS.find((item) => item.id === focus.id); if (!office) return; const openPopup = () => markerRefs.current[office.id]?.openPopup(); // Offset the center slightly North so the marker sits lower in the viewport, // preventing the popup card from colliding with the top controls. const offsetLatitude = 0.022; const centeredPosition: LatLng = [office.position[0] + offsetLatitude, office.position[1]]; if (!didInit.current) { didInit.current = true; map.invalidateSize(); map.setView(centeredPosition, MAP_FOCUS_ZOOM, { animate: false }); const raf = requestAnimationFrame(openPopup); return () => cancelAnimationFrame(raf); } map.flyTo(centeredPosition, MAP_FOCUS_ZOOM, { duration: 1.1 }); map.once("moveend", openPopup); return () => { map.off("moveend", openPopup); }; }, [map, focus, markerRefs]); return null; } export default function OfficeMap() { const icon = useMemo(() => createMarkerIcon(false), []); const hqIcon = useMemo(() => createMarkerIcon(true), []); const markerRefs = useRef>({}); // Default to the headquarters. const [focus, setFocus] = useState({ id: HQ_OFFICE.id, nonce: 0 }); const focusOffice = useCallback((id: string) => { setFocus((prev) => ({ id, nonce: (prev?.nonce ?? 0) + 1 })); }, []); const [hoveredOfficeId, setHoveredOfficeId] = useState(null); // Restore the focused office popup when mouse leaves other markers. useEffect(() => { if (hoveredOfficeId === null && focus?.id) { markerRefs.current[focus.id]?.openPopup(); } }, [hoveredOfficeId, focus]); const [status, setStatus] = useState("loading"); const loadedRef = useRef(false); const errorCountRef = useRef(0); const handleTileLoad = useCallback(() => { loadedRef.current = true; setStatus("ready"); }, []); const handleTileError = useCallback(() => { errorCountRef.current += 1; if (!loadedRef.current && errorCountRef.current >= 6) setStatus("error"); }, []); useEffect(() => { const timeout = window.setTimeout(() => { if (!loadedRef.current) setStatus("error"); }, 12_000); return () => window.clearTimeout(timeout); }, []); return ( {/* Semantic, always-available fallback for assistive tech + no-JS/SEO. */} {OFFICE_LOCATIONS.map((office) => ( {office.name} — latitude {office.position[0]}, longitude {office.position[1]} ))} {/* Jump-to-office navigation buttons. */} {OFFICE_LOCATIONS.map((office) => { const isActive = focus?.id === office.id; return ( focusOffice(office.id)} > {office.city} ); })} {/* Keep zoom controls clear of the top-row buttons. */} {OFFICE_LOCATIONS.map((office) => { const isFocused = focus?.id === office.id; return ( focusOffice(office.id), mouseover: (event) => { setHoveredOfficeId(office.id); event.target.openPopup(); }, mouseout: (event) => { setHoveredOfficeId(null); if (focus?.id !== office.id) { event.target.closePopup(); } }, }} ref={(instance) => { if (instance) markerRefs.current[office.id] = instance; }} > 📍 {office.city} Office {office.address.map((line, idx) => ( {line} ))} ); })} {status === "error" && ( Map could not be loaded Please check your connection. Our offices are located in: {OFFICE_LOCATIONS.map((office) => ( {office.name} ))} )} ); }
{line}
Map could not be loaded
Please check your connection. Our offices are located in: