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
This commit is contained in:
2026-04-10 18:42:54 +03:00
parent bbe485d8fc
commit a587736dd3
74 changed files with 724 additions and 298 deletions
+14 -8
View File
@@ -28,21 +28,27 @@ export function YandexMap({ addresses, height = 380 }: YandexMapProps) {
let cancelled = false;
async function build() {
const points: { lat: number; lon: number }[] = [];
for (const addr of addresses) {
try {
// 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`
`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) {
points.push({ lat: parseFloat(data[0].lat), lon: parseFloat(data[0].lon) });
return { lat: parseFloat(data[0].lat), lon: parseFloat(data[0].lon) };
}
} catch { /* skip */ }
}
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;