Match Angular pixel-for-pixel: error page, filter default, breadcrumbs, feedback button
Error page: add search input bar, align flex/spacing to Angular SCSS mixins, match button display and illustration flex. Online board filter: default to "route" tab expanded (Angular defaults to route, not flight number). Start pages: remove extra breadcrumb items — Angular start pages show only "Главная", not the page title. PageLayout: hide FeedbackButton — Angular gates it behind FEEDBACK_BUTTON_AVAILABLE feature flag (off by default).
This commit is contained in:
@@ -44,7 +44,7 @@ export const OnlineBoardFilter: FC = () => {
|
||||
const routeParams = useParams<{ lang: string }>();
|
||||
const lang = routeParams.lang ?? "ru";
|
||||
|
||||
const [activeTab, setActiveTab] = useState<AccordionTab>("flight");
|
||||
const [activeTab, setActiveTab] = useState<AccordionTab>("route");
|
||||
|
||||
// Flight number fields
|
||||
const [flightNumber, setFlightNumber] = useState("");
|
||||
|
||||
@@ -41,9 +41,7 @@ export const OnlineBoardStartPage: FC = () => {
|
||||
{t("BOARD.TITLE")}
|
||||
</h1>
|
||||
}
|
||||
breadcrumbs={[
|
||||
{ label: t("BOARD.TITLE") },
|
||||
]}
|
||||
breadcrumbs={[]}
|
||||
contentLeft={
|
||||
<>
|
||||
<OnlineBoardFilter />
|
||||
|
||||
@@ -295,9 +295,7 @@ export const ScheduleStartPage: FC = () => {
|
||||
{t("SCHEDULE.TITLE")}
|
||||
</h1>
|
||||
}
|
||||
breadcrumbs={[
|
||||
{ label: t("SCHEDULE.TITLE"), url: `/${lang}/schedule` },
|
||||
]}
|
||||
breadcrumbs={[]}
|
||||
contentLeft={
|
||||
<>
|
||||
{scheduleFilter}
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
display: flex;
|
||||
padding: 2rem;
|
||||
|
||||
& > * + * {
|
||||
margin-left: 2rem;
|
||||
// Match Angular: v-spacing(2rem) + h-spacing(2rem)
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@include screen.desktop {
|
||||
@@ -25,16 +27,16 @@
|
||||
@include screen.mobile {
|
||||
flex-direction: column;
|
||||
|
||||
& > * + * {
|
||||
margin-left: 0;
|
||||
margin-top: 2rem;
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__illustration {
|
||||
flex: 0 0 30%;
|
||||
min-height: 300px;
|
||||
// Match Angular: flex: 30% (equivalent to flex: 0 1 30%)
|
||||
flex: 30%;
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
@@ -48,21 +50,19 @@
|
||||
&__code {
|
||||
font-size: 8rem;
|
||||
color: #a3a3a3;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 3rem;
|
||||
color: colors.$text-color;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1 1 70%;
|
||||
// Match Angular: flex: 70% + v-spacing(1rem)
|
||||
flex: 70%;
|
||||
|
||||
& > * + * {
|
||||
margin-top: 1rem;
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@include screen.mobile {
|
||||
@@ -76,20 +76,85 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// Search input — matches Angular error-page-search
|
||||
&__search {
|
||||
width: 100%;
|
||||
|
||||
@include screen.desktop {
|
||||
width: 17rem;
|
||||
}
|
||||
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: no-repeat;
|
||||
background-image: url("/assets/img/icon--search.svg");
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-image: url("/assets/img/icon--search-blue.svg");
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 0.875rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 2.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #dfdfdf;
|
||||
background: #fff;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
transition: all 0.2s;
|
||||
outline: 0 solid transparent;
|
||||
border-radius: 0.1875rem;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
&:focus {
|
||||
border: 1px solid #b7d3f3;
|
||||
box-shadow: 0 0.0625rem 0.1875rem #cad6e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
padding: 2rem 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
|
||||
// Match Angular: h-spacing(1rem) on gt-mobile
|
||||
@include screen.desktop {
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include screen.mobile {
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__btn {
|
||||
height: 35px;
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1.5rem;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams } from "@modern-js/runtime/router";
|
||||
import { createI18nInstance } from "@/i18n/config.js";
|
||||
import { resolveLocaleFromPath, type Language } from "@/i18n/resolver.js";
|
||||
@@ -52,9 +52,11 @@ const FALLBACK_CONFIG: ErrorConfig = {
|
||||
export default function ErrorPage(): JSX.Element {
|
||||
const { code } = useParams<{ code: string }>();
|
||||
const config = (code ? ERROR_CONFIG[code] : undefined) ?? FALLBACK_CONFIG;
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Attempt to detect locale from referrer or default to "ru"
|
||||
const [translations, setTranslations] = useState<Record<string, string> | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const pathname = typeof window !== "undefined" ? window.location.pathname : "";
|
||||
@@ -73,6 +75,15 @@ export default function ErrorPage(): JSX.Element {
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const handleSearch = () => {
|
||||
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;
|
||||
@@ -92,6 +103,21 @@ export default function ErrorPage(): JSX.Element {
|
||||
<div className="error-page__code">{code ?? "?"}</div>
|
||||
<div className="error-page__title">{title}</div>
|
||||
<div className="error-page__description">{description}</div>
|
||||
<div className="error-page__search">
|
||||
<div className="error-page__search-control">
|
||||
<input
|
||||
ref={searchRef}
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||
/>
|
||||
<div
|
||||
className="error-page__search-icon"
|
||||
onClick={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="error-page__actions">
|
||||
<a
|
||||
className="error-page__btn error-page__btn--primary"
|
||||
|
||||
@@ -47,7 +47,7 @@ export const PageLayout: FC<PageLayoutProps> = ({
|
||||
<div className="page-layout__title">
|
||||
{title}
|
||||
</div>
|
||||
<FeedbackButton />
|
||||
{/* FeedbackButton hidden: Angular has it behind FEEDBACK_BUTTON_AVAILABLE feature flag (off by default) */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user