Add IFlyWarning component shown on details pages for SU5801-5948 flights (Angular parity)

This commit is contained in:
2026-04-20 20:02:45 +03:00
parent 28c88873a5
commit d942cb55bc
6 changed files with 124 additions and 0 deletions
@@ -12,6 +12,7 @@ import { useNavigate, useSearchParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import "./OnlineBoardDetailsPage.scss";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { IFlyWarning } from "@/ui/flights/IFlyWarning.js";
import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { useAppSettings } from "@/shared/hooks/useAppSettings.js";
@@ -561,6 +562,11 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
<BoardDetailsHeader flight={displayFlight} locale={locale} />
{/* iFly operator warning (SU5801-5948) — Angular embeds
this inside flight-schedule-details.component.html via
a flightNumber range guard. */}
<IFlyWarning flightNumber={displayFlight.flightId.flightNumber} />
{displayFlight.routeType === "MultiLeg" && (
<FullRouteTimeline legs={displayFlight.legs} viewType="Onlineboard" />
)}
@@ -15,6 +15,7 @@ import { useTranslation } from "@/i18n/provider.js";
import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js";
import { FlightCard } from "@/ui/flights/FlightCard.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { IFlyWarning } from "@/ui/flights/IFlyWarning.js";
import { SeoHead } from "@/ui/seo/SeoHead.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { PageTabs } from "@/ui/layout/PageTabs.js";
@@ -230,6 +231,10 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
{/* Collapsed summary row, then the rich per-leg body. */}
<FlightCard flight={flight} direction="schedule" />
{/* iFly operator warning (SU5801-5948) — Angular
renders this inside flight-schedule-details between
the header and the route strip. */}
<IFlyWarning flightNumber={flight.flightId.flightNumber} />
{renderBody(flight)}
{/* Angular's flight-schedule-details renders the weekly
+31
View File
@@ -0,0 +1,31 @@
@use "../../styles/colors" as colors;
@use "../../styles/variables" as vars;
@use "../../styles/fonts" as fonts;
// Mirrors Angular `warning-ifly-carrier-detail.component.scss`:
// a narrow orange vertical bar + highlighted leading caption, used
// on details pages for flights in the iFly-operated SU58015948 range.
.ifly-warning {
display: flex;
align-items: center;
padding: vars.$space-m vars.$space-xl;
&__bar {
width: 0.3rem;
height: 80px;
background-color: colors.$orange--hover;
flex-shrink: 0;
}
&__text {
text-align: left;
padding: vars.$space-xl;
font-size: fonts.$font-size-m;
line-height: 1.5;
}
&__highlight {
font-weight: fonts.$font-bold;
color: colors.$orange--hover;
}
}
+46
View File
@@ -0,0 +1,46 @@
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { IFlyWarning } from "./IFlyWarning.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({
t: (key: string) => key,
i18n: { language: "ru" },
}),
}));
describe("IFlyWarning", () => {
it("renders warning for flights in the iFly range (5801-5948)", () => {
const { container } = render(<IFlyWarning flightNumber="5825" />);
expect(container.querySelector(".ifly-warning")).toBeTruthy();
expect(screen.getByText("WARNING.IFLY_HIGHLIGHT")).toBeTruthy();
});
it("accepts numeric flight numbers", () => {
const { container } = render(<IFlyWarning flightNumber={5900} />);
expect(container.querySelector(".ifly-warning")).toBeTruthy();
});
it("does not render for flights below the iFly range", () => {
const { container } = render(<IFlyWarning flightNumber="5800" />);
expect(container.querySelector(".ifly-warning")).toBeNull();
});
it("does not render for flights above the iFly range", () => {
const { container } = render(<IFlyWarning flightNumber="5949" />);
expect(container.querySelector(".ifly-warning")).toBeNull();
});
it("does not render for regular flights (SU100)", () => {
const { container } = render(<IFlyWarning flightNumber="100" />);
expect(container.querySelector(".ifly-warning")).toBeNull();
});
it("does not render for non-numeric input", () => {
const { container } = render(<IFlyWarning flightNumber="abc" />);
expect(container.querySelector(".ifly-warning")).toBeNull();
});
});
+33
View File
@@ -0,0 +1,33 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import "./IFlyWarning.scss";
export interface IFlyWarningProps {
flightNumber: string | number;
}
function isIFlyRange(num: string | number): boolean {
const n = typeof num === "string" ? parseInt(num, 10) : num;
if (!Number.isFinite(n)) return false;
return n > 5800 && n < 5949;
}
export const IFlyWarning: FC<IFlyWarningProps> = ({ flightNumber }) => {
const { t } = useTranslation();
if (!isIFlyRange(flightNumber)) return null;
return (
<div className="ifly-warning" data-testid="ifly-warning">
<div className="ifly-warning__bar" aria-hidden="true" />
<div className="ifly-warning__text">
<span className="ifly-warning__highlight">
{t("WARNING.IFLY_HIGHLIGHT")}
</span>{" "}
{t("WARNING.IFLY_BODY")}
<br />
<span
dangerouslySetInnerHTML={{ __html: t("WARNING.IFLY_INFO") }}
/>
</div>
</div>
);
};
+3
View File
@@ -21,3 +21,6 @@ export type { FlightListSkeletonProps } from "./FlightListSkeleton.js";
export { FlightList } from "./FlightList.js";
export type { FlightListProps } from "./FlightList.js";
export { IFlyWarning } from "./IFlyWarning.js";
export type { IFlyWarningProps } from "./IFlyWarning.js";