ScheduleFlightBody "Купить" button: wire onBuy → Aeroflot booking URL

DayGroupedFlightList gains an optional `onBuy` prop that forwards to
ScheduleFlightBody. ScheduleSearchPage implements handleBuy — matches
BoardDetailsHeader.BuyTicketButton: opens
`aeroflot.ru/sb/app/{lang}-{lang}#/search?routes={dep}.{yyyyMMdd}.{arr}`
in a new tab, using the first leg's airportCode + scheduled-departure
UTC for direct and multi-leg flights.

Previously the Buy button rendered but its click was `onBuy?.()` with
no handler wired, so nothing happened. The button text + wiring now
mirror Angular's `buy-ticket-button.component`.
This commit is contained in:
2026-04-20 16:51:06 +03:00
parent 4e8934f0c9
commit e694ccf42b
2 changed files with 31 additions and 1 deletions
@@ -32,6 +32,8 @@ export interface DayGroupedFlightListProps {
flights: ISimpleFlight[];
loading?: boolean;
onFlightClick?: (flight: ISimpleFlight) => void;
/** Click handler for the `Купить` button inside each expanded flight body. */
onBuy?: (flight: ISimpleFlight) => void;
initialCurrentFlightId?: string | null;
}
@@ -125,6 +127,7 @@ export const DayGroupedFlightList: FC<DayGroupedFlightListProps> = ({
flights,
loading,
onFlightClick,
onBuy,
initialCurrentFlightId,
}) => {
const { language } = useLocale();
@@ -236,9 +239,10 @@ export const DayGroupedFlightList: FC<DayGroupedFlightListProps> = ({
<ScheduleFlightBody
flight={f}
{...(onFlightClick ? { onStatus: () => onFlightClick(f) } : {})}
{...(onBuy ? { onBuy: () => onBuy(f) } : {})}
/>
),
[onFlightClick],
[onFlightClick, onBuy],
);
if (loading) return <FlightListSkeleton count={5} />;
@@ -142,6 +142,30 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
[locale, navigate],
);
// `Купить` button — opens Aeroflot's booking flow in a new tab, same
// URL shape as BoardDetailsHeader's BuyTicketButton:
// https://www.aeroflot.ru/sb/app/{lang}-{lang}#/search?…&routes={dep}.{yyyyMMdd}.{arr}
const handleBuy = useCallback(
(flight: ISimpleFlight) => {
const legs = flight.routeType === "Direct" ? [flight.leg] : flight.legs;
const firstLeg = legs[0];
const lastLeg = legs[legs.length - 1];
if (!firstLeg || !lastLeg) return;
const dep = firstLeg.departure.scheduled.airportCode;
const arr = lastLeg.arrival.scheduled.airportCode;
const depUtc = firstLeg.departure.times.scheduledDeparture.utc;
const depDate = new Date(depUtc);
const yyyy = depDate.getFullYear().toString();
const mm = (depDate.getMonth() + 1).toString().padStart(2, "0");
const dd = depDate.getDate().toString().padStart(2, "0");
const url = `https://www.aeroflot.ru/sb/app/${language}-${language}#/search?adults=1&cabin=economy&children=0&infants=0&routes=${dep}.${yyyy}${mm}${dd}.${arr}&autosearch=Y`;
if (typeof window !== "undefined") {
window.open(url, "_blank", "noopener,noreferrer");
}
},
[language],
);
// Round-trip schedules render only one direction at a time, with a
// `Туда / Обратно` switcher in the sticky header — matches Angular's
// `schedule-direction-switch` (one-of-two button group). Default to
@@ -394,6 +418,7 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
flights={outboundSimple}
loading={outboundLoading}
onFlightClick={handleFlightClick}
onBuy={handleBuy}
/>
</div>
) : (
@@ -402,6 +427,7 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
flights={inboundSimple}
loading={inboundLoading}
onFlightClick={handleFlightClick}
onBuy={handleBuy}
/>
</div>
)}