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 */}
{shown.map((ev) => (
-
{formatDateRU(ev.dateISO)}
{ev.city} • {ev.venue}
{ev.minPrice} {ev.age}
Купить
))}
{shown.length === 0 && (
Ничего не найдено. Попробуй изменить запрос.
)}
{/* FAQ */}
FAQ
Как получить электронный билет?
После оплаты придёт письмо на почту и/или билет от билетного оператора в личном кабинете. Сохрани QR‑код.
Можно ли оформить возврат?
Возвраты регулируются правилами оператора и площадки. Ссылка на правила доступна на странице оплаты.
Есть ли фан‑зона?
Схема зала и типы билетов отображаются у билетного оператора. Выбирай подходящий сектор при покупке.
{/* Contacts */}
{/* Footer */}
);
}