feat: contact page improvements, Yandex map from addresses
- Instagram field: @username input with API validation (like team page) - Phone validation: blocks auto-save when incomplete, shows warning - SectionEditor: validate prop to conditionally block saves - Yandex Map: auto-generated from addresses via Nominatim geocoding, dark theme, no API key needed - Schedule: address hint linking to Contacts - Renamed "Всплывающие окна" → "Формы записи", moved after Записи
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"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() {
|
||||
const points: { lat: number; lon: number }[] = [];
|
||||
|
||||
for (const addr of addresses) {
|
||||
try {
|
||||
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`
|
||||
);
|
||||
const data = await res.json();
|
||||
if (data.length > 0) {
|
||||
points.push({ lat: parseFloat(data[0].lat), lon: parseFloat(data[0].lon) });
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
|
||||
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="Карта"
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user