179 lines
6.7 KiB
TypeScript
179 lines
6.7 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import Preloader from "./components/Preloader";
|
|
import AgeGate from "./components/AgeGate";
|
|
import Header from "./components/Header";
|
|
import Hero from "./components/Hero";
|
|
import AtmosphereCarousel from "./components/AtmosphereCarousel";
|
|
import MenuCarousel from "./components/MenuCarousel";
|
|
import VipCalculator from "./components/VipCalculator";
|
|
import Features from "./components/Features";
|
|
import Contacts from "./components/Contacts";
|
|
import Footer from "./components/Footer";
|
|
import FlavorsPage from "./pages/FlavorsPage";
|
|
import GamesPage from "./pages/GamesPage";
|
|
import MixesPage from "./pages/MixesPage";
|
|
import AiMixPage from "./pages/AiMixPage";
|
|
import BoardGamesPage from "./pages/BoardGamesPage";
|
|
import MenuPage from "./pages/MenuPage";
|
|
import TrackOrderPage from "./pages/TrackOrderPage";
|
|
import { useHashLocation } from "./hooks/useHashLocation";
|
|
import { isPrerender } from "./lib/prerender";
|
|
|
|
type CatalogPage = "flavors" | "games" | "mixes" | "ai-mix" | "board-games" | "menu" | "track" | null;
|
|
|
|
function getCatalogPage(hash: string): CatalogPage {
|
|
if (hash === "#/flavors") return "flavors";
|
|
if (hash === "#/games") return "games";
|
|
if (hash === "#/mixes") return "mixes";
|
|
if (hash === "#/ai-mix" || hash === "#/mix-builder") return "ai-mix";
|
|
if (hash.startsWith("#mix=")) return "ai-mix"; // share-ссылка на генерацию
|
|
if (hash === "#/board-games") return "board-games";
|
|
if (hash === "#/menu" || hash === "#/tea") return "menu";
|
|
if (hash.startsWith("#/track")) return "track"; // #/track и #/track?code=XXX
|
|
return null;
|
|
}
|
|
|
|
export default function App() {
|
|
const [loaded, setLoaded] = useState(false);
|
|
const hash = useHashLocation();
|
|
const catalogPage = getCatalogPage(hash);
|
|
const isCatalog = catalogPage !== null;
|
|
const wasCatalog = useRef(isCatalog);
|
|
const homeTitle = useRef(document.title);
|
|
// в пререндер-снапшоте рендерим финальное видимое состояние страницы
|
|
const prerender = isPrerender();
|
|
const started = loaded || prerender;
|
|
|
|
useEffect(() => {
|
|
const previousOverflow = document.body.style.overflow;
|
|
document.body.style.overflow = "hidden";
|
|
const t = setTimeout(() => {
|
|
document.body.style.overflow = previousOverflow;
|
|
setLoaded(true);
|
|
}, 2450);
|
|
return () => {
|
|
clearTimeout(t);
|
|
document.body.style.overflow = previousOverflow;
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (catalogPage === "flavors") {
|
|
document.title = "Вкусы табака | НЕОН, Нягань";
|
|
} else if (catalogPage === "games") {
|
|
document.title = "Игры PS5 | НЕОН, Нягань";
|
|
} else if (catalogPage === "mixes") {
|
|
document.title = "Миксы кальянов | НЕОН, Нягань";
|
|
} else if (catalogPage === "ai-mix") {
|
|
document.title = "Автоподбор микса | НЕОН, Нягань";
|
|
} else if (catalogPage === "board-games") {
|
|
document.title = "Настольные игры | НЕОН, Нягань";
|
|
} else if (catalogPage === "menu") {
|
|
document.title = "Меню бара: Авторский чай и Пивная карта | НЕОН, Нягань";
|
|
} else if (catalogPage === "track") {
|
|
document.title = "Статус заказа | НЕОН, Нягань";
|
|
} else {
|
|
document.title = homeTitle.current;
|
|
}
|
|
}, [catalogPage]);
|
|
|
|
useEffect(() => {
|
|
if (!loaded) return;
|
|
const returningToHome = wasCatalog.current && !isCatalog;
|
|
wasCatalog.current = isCatalog;
|
|
|
|
if (catalogPage === "flavors") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("flavors-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "games") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("games-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "mixes") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("mixes-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "ai-mix") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("ai-mix-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "board-games") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("board-games-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "menu") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("menu-page-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
if (catalogPage === "track") {
|
|
window.scrollTo({ top: 0, behavior: "instant" });
|
|
document.getElementById("track-title")?.focus({ preventScroll: true });
|
|
return;
|
|
}
|
|
|
|
// The landing section mounts after the hash changes when leaving the catalog.
|
|
const section = document.getElementById(hash.slice(1) || "home");
|
|
if (!section) return;
|
|
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
section.scrollIntoView({
|
|
block: "start",
|
|
behavior: returningToHome || reducedMotion ? "instant" : "smooth",
|
|
});
|
|
if (returningToHome) {
|
|
section.tabIndex = -1;
|
|
section.focus({ preventScroll: true });
|
|
}
|
|
}, [hash, catalogPage, isCatalog, loaded]);
|
|
|
|
return (
|
|
<div className="relative min-h-screen w-full max-w-full overflow-x-hidden bg-void font-sans text-mist antialiased">
|
|
<div className="noise" aria-hidden />
|
|
{/* в пререндер-снапшоте прелоадер и age-gate не попадают в HTML:
|
|
бот должен видеть финальную страницу, а не оверлеи */}
|
|
{!prerender && <Preloader done={loaded} />}
|
|
{!prerender && <AgeGate ready={loaded} />}
|
|
<Header started={started} />
|
|
<main className="w-full max-w-full overflow-x-hidden">
|
|
{catalogPage === "flavors" ? (
|
|
<FlavorsPage />
|
|
) : catalogPage === "games" ? (
|
|
<GamesPage />
|
|
) : catalogPage === "mixes" ? (
|
|
<MixesPage />
|
|
) : catalogPage === "ai-mix" ? (
|
|
<AiMixPage />
|
|
) : catalogPage === "board-games" ? (
|
|
<BoardGamesPage />
|
|
) : catalogPage === "menu" ? (
|
|
<MenuPage />
|
|
) : catalogPage === "track" ? (
|
|
<TrackOrderPage />
|
|
) : (
|
|
<>
|
|
<Hero started={started} />
|
|
<AtmosphereCarousel />
|
|
<MenuCarousel />
|
|
<Features />
|
|
<VipCalculator />
|
|
<Contacts />
|
|
</>
|
|
)}
|
|
</main>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|