import React, { useMemo, useState } from "react"; import { Calendar, MapPin, Ticket, Search, Download, ChevronDown } from "lucide-react"; // ── Settings ──────────────────────────────────────────────────────────────────── // 1) Replace HERO_IMAGE_DATA_URI with the contents of the file you can download // here after this message: /mnt/data/hero_data_uri.txt // (Or host the image anywhere and paste its URL.) // 2) Put real links to your билетницы in the events array below. const HERO_IMAGE_DATA_URI = "/* paste the long data URI here (starts with data:image/webp;base64,...) */"; // You can add more shows here later. Keep dates as ISO strings. const events = [ { id: "vk-stadium-2026-02-13", city: "Москва", venue: "VK Stadium", dateISO: "2026-02-13T20:00:00+03:00", // проверь время и год minPrice: "от 1 900 ₽", age: "16+", link: "https://your-ticket-link-here" // вставь ссылку на билеты } ]; // ── Utils ─────────────────────────────────────────────────────────────────────── const formatDateRU = (iso) => { try { const d = new Date(iso); return new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "long", year: "numeric", hour: undefined }).format(d); } catch (e) { return iso; } }; // Build ICS text for the event (kept separate so we can test it) function buildICS(ev) { const start = new Date(ev.dateISO); const end = new Date(start.getTime() + 2 * 60 * 60 * 1000); // 2 часа по умолчанию const pad = (n) => String(n).padStart(2, "0"); const dt = (d) => `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}T${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}Z`; return [ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//polnalyubvi//tickets//RU", "CALSCALE:GREGORIAN", "BEGIN:VEVENT", `UID:${ev.id}@polnalyubvi`, `DTSTAMP:${dt(new Date())}`, `DTSTART:${dt(start)}`, `DTEND:${dt(end)}`, `SUMMARY:polnalyubvi — ${ev.city} — ${ev.venue}`, `LOCATION:${ev.venue}, ${ev.city}`, `DESCRIPTION:Билеты: ${ev.link}`, "END:VEVENT", "END:VCALENDAR" ].join("\r\n"); } function downloadICS(ev) { const ics = buildICS(ev); const blob = new Blob([ics], { type: "text/calendar;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${ev.city.replaceAll(" ", "_")}_${ev.venue.replaceAll(" ", "_")}.ics`; a.click(); URL.revokeObjectURL(url); } // Lightweight dev self‑tests to catch regressions in ICS generation if (typeof window !== "undefined" && process.env && process.env.NODE_ENV !== "production") { try { const testEv = { id: "test-uid", city: "Тестоград", venue: "Test Hall", dateISO: new Date(Date.now() + 86400000).toISOString(), link: "https://example.org" }; const sample = buildICS(testEv); console.assert(sample.includes("BEGIN:VEVENT"), "ICS should include BEGIN:VEVENT"); console.assert(sample.includes("END:VCALENDAR"), "ICS should include END:VCALENDAR"); console.assert(/UID:test-uid@polnalyubvi/.test(sample), "ICS should include a UID line"); } catch (e) { console.error("ICS self-test failed:", e); } } // Schema.org JSON‑LD for SEO (Event) const useEventsJsonLd = (evs) => { return useMemo(() => { const json = { "@context": "https://schema.org", "@type": "ItemList", itemListElement: evs.map((ev, i) => ({ "@type": "ListItem", position: i + 1, item: { "@type": "Event", name: `polnalyubvi — ${ev.city}`, startDate: ev.dateISO, eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode", eventStatus: "https://schema.org/EventScheduled", location: { "@type": "Place", name: ev.venue, address: { "@type": "PostalAddress", addressLocality: ev.city, addressCountry: "RU" } }, image: HERO_IMAGE_DATA_URI && HERO_IMAGE_DATA_URI.startsWith("data:") ? [HERO_IMAGE_DATA_URI] : [], offers: { "@type": "Offer", url: ev.link, priceCurrency: "RUB", availability: "https://schema.org/InStock" }, performer: { "@type": "MusicGroup", name: "polnalyubvi" } } })) }; const tag = document.createElement("script"); tag.type = "application/ld+json"; tag.innerHTML = JSON.stringify(json); document.head.appendChild(tag); return () => { try { document.head.removeChild(tag); } catch (e) {} }; }, [evs, HERO_IMAGE_DATA_URI]); }; // ── UI ────────────────────────────────────────────────────────────────────────── export default function PolnalyubviTicketsSite() { const [search, setSearch] = useState(""); useEventsJsonLd(events); const shown = useMemo(() => { const q = search.trim().toLowerCase(); if (!q) return events; return events.filter((e) => [e.city, e.venue, formatDateRU(e.dateISO)].join(" ").toLowerCase().includes(q)); }, [search]); return (
{/* Header */}
{/* Hero */}

Тур — билеты на ближайшие концерты

Выбирай город и площадку, оформляй билет за пару кликов. Сайт адаптирован под телефон, планшет и десктоп.

{/* Shows */}

Ближайшие концерты

setSearch(e.target.value)} className="w-full bg-white/10 border border-white/15 rounded-2xl pl-10 pr-4 py-2.5 outline-none placeholder:text-white/50" placeholder="Искать город, площадку, дату" />
    {shown.map((ev) => (
  • {formatDateRU(ev.dateISO)}
    {ev.city} • {ev.venue}
    {ev.minPrice} {ev.age}
    Купить
  • ))}
{shown.length === 0 && (

Ничего не найдено. Попробуй изменить запрос.

)}
{/* FAQ */}

FAQ

Как получить электронный билет?

После оплаты придёт письмо на почту и/или билет от билетного оператора в личном кабинете. Сохрани QR‑код.

Можно ли оформить возврат?

Возвраты регулируются правилами оператора и площадки. Ссылка на правила доступна на странице оплаты.

Есть ли фан‑зона?

Схема зала и типы билетов отображаются у билетного оператора. Выбирай подходящий сектор при покупке.

{/* Contacts */}

Контакты

Вопросы по концертам и организации — team@polnalyubvi.ru

e.preventDefault()} className="flex items-center gap-3">
{/* Footer */}
© {new Date().getFullYear()} polnalyubvi — все права защищены • Политика конфиденциальности
); }