/** * Reusable error-page component. * * Rendered by the `/error/[code]` route AND directly inline by any * feature route that has to reject an invalid URL (bad flight number, * missing date, double-slash path, etc.). Matches Angular's global * 404/500 pages: illustration + code + title + description + search * box + three action buttons. */ import { useEffect, useRef, useState, type JSX, } from "react"; import { createI18nInstance } from "@/i18n/config.js"; import { resolveLocaleFromPath, localeToLanguage, type Language, } from "@/i18n/resolver.js"; import "./ErrorPage.scss"; interface ErrorConfig { titleKey: string; descriptionKey: string; image: string; buyTicketKey: string; homeKey: string; supportKey: string; /** Present only for 5xx pages — triggers a refresh CTA button. */ refreshKey?: string; } const ERROR_CONFIG: Record = { "404": { titleKey: "PAGE404.HEADER", descriptionKey: "PAGE404.DESCRIPTION", image: "/assets/img/lady404.png", buyTicketKey: "PAGE404.BUY-TICKET", homeKey: "PAGE404.TO-HOME", supportKey: "PAGE404.SUPPORT", }, "500": { titleKey: "PAGE500.HEADER", descriptionKey: "PAGE500.DESCRIPTION", image: "/assets/img/lady500.png", buyTicketKey: "PAGE500.BUY-TICKET", homeKey: "PAGE500.TO-HOME", supportKey: "PAGE500.SUPPORT", refreshKey: "PAGE500.REFRESH", }, "503": { titleKey: "PAGE500.HEADER", descriptionKey: "PAGE500.DESCRIPTION", image: "/assets/img/lady500.png", buyTicketKey: "PAGE500.BUY-TICKET", homeKey: "PAGE500.TO-HOME", supportKey: "PAGE500.SUPPORT", refreshKey: "PAGE500.REFRESH", }, }; const FALLBACK_CONFIG: ErrorConfig = { titleKey: "PAGE500.HEADER", descriptionKey: "PAGE500.DESCRIPTION", image: "/assets/img/lady500.png", buyTicketKey: "PAGE500.BUY-TICKET", homeKey: "PAGE500.TO-HOME", supportKey: "PAGE500.SUPPORT", refreshKey: "PAGE500.REFRESH", }; export interface ErrorPageProps { /** HTTP status code ("404", "500", "503"). Unknown codes fall back to 500. */ code?: string; } export function ErrorPage({ code }: ErrorPageProps): JSX.Element { const config = (code ? ERROR_CONFIG[code] : undefined) ?? FALLBACK_CONFIG; const searchRef = useRef(null); const [translations, setTranslations] = useState | null>(null); const [searchTerm, setSearchTerm] = useState(""); useEffect(() => { const pathname = typeof window !== "undefined" ? window.location.pathname : ""; const detected = resolveLocaleFromPath(pathname); const locale: Language = detected ? localeToLanguage(detected) : "ru"; void createI18nInstance({ locale }).then((i18n) => { const t = (key: string): string => i18n.t(key) as string; setTranslations({ title: t(config.titleKey), description: t(config.descriptionKey), buyTicket: t(config.buyTicketKey), home: t(config.homeKey), support: t(config.supportKey), refresh: config.refreshKey ? t(config.refreshKey) : undefined, }); }); }, [config]); const handleSearch = (): void => { if (searchTerm) { window.open( `https://www.aeroflot.ru/search?text=${encodeURIComponent(searchTerm)}`, "_blank", ); } }; // Show content immediately (with hardcoded Russian fallback for SSR), // then replace with i18n translations on the client. const title = translations?.title ?? config.titleKey; const description = translations?.description ?? config.descriptionKey; const buyTicket = translations?.buyTicket ?? "Купить билет"; const home = translations?.home ?? "На главную"; const support = translations?.support ?? "Поддержка"; const refresh = translations?.refresh ?? (config.refreshKey ? "Обновить страницу" : undefined); const displayCode = code ?? "?"; const documentTitle = `${displayCode} — ${title}`; // React 18 doesn't hoist inside body to document.head, so set // document.title imperatively on the client once translations resolve. useEffect(() => { if (typeof document !== "undefined" && translations) { document.title = documentTitle; } }, [documentTitle, translations]); return ( <> <title>{documentTitle} {/* noindex: error pages must not be indexed (TZ §4.1.21) */}
{displayCode}
{title}
{description}
setSearchTerm(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSearch()} />
{buyTicket} {home} {/* 5xx pages: refresh CTA (TZ §4.1.21) */} {refresh && ( )} {support}
); }