"use client";
import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
import { groupMapLocations } from "@/lib/user-location-map";
import type { UserMapLocation } from "./user-world-map";
export function MapViewToggle({ locations, children }: { locations: UserMapLocation[]; children: ReactNode }) {
const [view, setView] = useState<"overview" | "interactive">("overview");
return (
setView("overview")} type="button">World overview
setView("interactive")} type="button">Interactive OpenStreetMap
Selecting the interactive view requests map tiles from OpenStreetMap, which receives your IP address, the portal origin, and the geographic area being viewed.
{children}
{view === "interactive" && }
);
}
function InteractiveMap({ locations }: { locations: UserMapLocation[] }) {
const container = useRef(null);
useEffect(() => {
if (!container.current) return;
let cancelled = false;
let cleanup = () => {};
void import("leaflet").then((leaflet) => {
if (cancelled || !container.current) return;
const map = leaflet.map(container.current, { minZoom: 1, worldCopyJump: true }).setView([20, 0], 2);
leaflet.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '© OpenStreetMap contributors ',
maxZoom: 19,
referrerPolicy: "strict-origin-when-cross-origin",
}).addTo(map);
const bounds: [number, number][] = [];
for (const group of groupMapLocations(locations)) {
const firstUser = group.locations[0]!;
const isGrouped = group.count > 1;
const marker = isGrouped
? leaflet.marker([group.latitude, group.longitude], {
icon: leaflet.divIcon({
className: "map-user-cluster",
html: `${group.count} `,
iconAnchor: [18, 18],
iconSize: [36, 36],
}),
keyboard: true,
}).addTo(map)
: leaflet.circleMarker([group.latitude, group.longitude], {
radius: 8,
color: "#eee8d8",
weight: 3,
fillColor: "#a32f1b",
fillOpacity: 1,
}).addTo(map);
const tooltip = document.createElement("span");
tooltip.textContent = isGrouped
? `${group.count} users · ${group.nicknames.join(" · ")}`
: `${firstUser.nickname} · ${firstUser.location}`;
marker.bindTooltip(tooltip, { direction: "top" });
if (isGrouped) {
const popup = document.createElement("div");
const heading = document.createElement("strong");
heading.textContent = `${group.count} users near ${firstUser.location}`;
popup.append(heading);
const list = document.createElement("ul");
for (const user of group.locations) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = `/admin/users/${user.userId}`;
link.textContent = user.nickname;
item.append(link);
list.append(item);
}
popup.append(list);
marker.bindPopup(popup);
} else {
marker.on("click", () => window.location.assign(`/admin/users/${firstUser.userId}`));
}
const element = marker.getElement();
const label = isGrouped
? `${group.count} users near ${firstUser.location}: ${group.nicknames.join(", ")}`
: `${firstUser.nickname}, ${firstUser.location}`;
element?.setAttribute("aria-label", label);
element?.setAttribute("role", isGrouped ? "button" : "link");
element?.setAttribute("tabindex", "0");
if (isGrouped) {
element?.setAttribute("aria-haspopup", "dialog");
element?.setAttribute("aria-expanded", "false");
marker.on("popupopen", () => element?.setAttribute("aria-expanded", "true"));
marker.on("popupclose", () => element?.setAttribute("aria-expanded", "false"));
}
element?.addEventListener("focus", () => marker.openTooltip());
element?.addEventListener("blur", () => marker.closeTooltip());
element?.addEventListener("keydown", (event) => {
const keyboardEvent = event as KeyboardEvent;
if (keyboardEvent.key === "Enter" || keyboardEvent.key === " ") {
keyboardEvent.preventDefault();
if (isGrouped) marker.openPopup();
else window.location.assign(`/admin/users/${firstUser.userId}`);
}
});
bounds.push([group.latitude, group.longitude]);
}
if (bounds.length) map.fitBounds(bounds, { padding: [40, 40], maxZoom: 6 });
cleanup = () => map.remove();
});
return () => {
cancelled = true;
cleanup();
};
}, [locations]);
return
;
}