Files
blackheart-website/src/components/ui/YandexMap.tsx
T
diana.dolgolyova a587736dd3 feat: mobile UX, admin polish, rate limiting, and media assets
- Mobile responsiveness improvements across admin and public sections
- Admin: bookings modal, open-day page, team page, layout polish
- Added rate limiting, CSRF hardening, auth-edge improvements
- Scroll reveal, floating contact, back-to-top, Yandex map fixes
- Schedule filters refactor, team profile/info component updates
- New useTrainerPhotos hook
- Added class, team, master-class, and news images
2026-04-10 18:42:54 +03:00

89 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
interface YandexMapProps {
addresses: string[];
height?: number;
}
function cleanAddress(addr: string): string {
return addr
.replace(/^г\.\s*/i, "")
.replace(/ул\.\s*/gi, "")
.replace(/пр-т\.?\s*/gi, "")
.replace(/пр\.\s*/gi, "")
.replace(/просп\.\s*/gi, "")
.replace(/,?\s*к\d+/gi, "")
.replace(/,+/g, " ")
.replace(/\s+/g, " ")
.trim();
}
export function YandexMap({ addresses, height = 380 }: YandexMapProps) {
const [mapSrc, setMapSrc] = useState<string | null>(null);
useEffect(() => {
if (!addresses.length) return;
let cancelled = false;
async function build() {
// Geocode all addresses in parallel
const results = await Promise.allSettled(
addresses.map(async (addr) => {
const cleaned = cleanAddress(addr);
const query = cleaned.toLowerCase().includes("минск") ? cleaned : `Минск ${cleaned}`;
const res = await fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1&countrycodes=by`,
{ signal: AbortSignal.timeout(5000) }
);
const data = await res.json();
if (data.length > 0) {
return { lat: parseFloat(data[0].lat), lon: parseFloat(data[0].lon) };
}
return null;
})
);
const points = results
.filter((r): r is PromiseFulfilledResult<{ lat: number; lon: number } | null> => r.status === "fulfilled")
.map((r) => r.value)
.filter((p): p is { lat: number; lon: number } => p !== null);
if (cancelled || points.length === 0) return;
const centerLat = points.reduce((s, p) => s + p.lat, 0) / points.length;
const centerLon = points.reduce((s, p) => s + p.lon, 0) / points.length;
const zoom = points.length === 1 ? 15 : 12;
const pts = points.map((p) => `${p.lon},${p.lat},pm2ntl`).join("~");
setMapSrc(`https://yandex.ru/map-widget/v1/?ll=${centerLon},${centerLat}&z=${zoom}&pt=${pts}&l=map&theme=dark`);
}
build();
return () => { cancelled = true; };
}, [addresses]);
if (!addresses.length) return null;
if (!mapSrc) {
return (
<div style={{ width: "100%", height }} className="flex items-center justify-center text-neutral-500 text-sm">
Загрузка карты...
</div>
);
}
return (
<iframe
src={mapSrc}
width="100%"
height={height}
style={{ border: 0 }}
allowFullScreen
loading="lazy"
title="Карта"
/>
);
}