diff --git a/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.test.tsx b/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.test.tsx new file mode 100644 index 00000000..612e6834 --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.test.tsx @@ -0,0 +1,95 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { BuyTicketButton } from "./BuyTicketButton.js"; +import type { ISimpleFlight } from "../../types.js"; + +vi.mock("@/i18n/provider.js", () => ({ + useTranslation: () => ({ t: (k: string) => k }), +})); + +function makeFlight( + depUtc = "2026-04-20T10:00:00Z", + dep = "SVO", + arr = "LED", +): ISimpleFlight { + return { + id: "X", + routeType: "Direct", + flyingTime: "1h", + status: "Scheduled", + flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" }, + operatingBy: { carrier: "SU", flightNumber: "0022" }, + leg: { + arrival: { + scheduled: { airport: "", airportCode: arr, city: "", cityCode: "", countryCode: "" }, + latest: { airport: "", airportCode: arr, city: "", cityCode: "", countryCode: "" }, + dispatch: "", + gate: "", + terminal: "", + times: { + scheduledArrival: { + dayChange: { value: 0, title: "" }, + local: "", + localTime: "", + tzOffset: 0, + utc: "", + }, + }, + }, + dayChange: 0, + departure: { + scheduled: { airport: "", airportCode: dep, city: "", cityCode: "", countryCode: "" }, + latest: { airport: "", airportCode: dep, city: "", cityCode: "", countryCode: "" }, + dispatch: "", + gate: "", + terminal: "", + checkingStatus: "Scheduled", + parkingStand: "", + times: { + scheduledDeparture: { + dayChange: { value: 0, title: "" }, + local: "", + localTime: "", + tzOffset: 0, + utc: depUtc, + }, + }, + }, + equipment: {}, + flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false }, + flyingTime: "1h", + index: 0, + operatingBy: {}, + status: "Scheduled", + updated: "", + }, + } as ISimpleFlight; +} + +describe("BuyTicketButton", () => { + beforeEach(() => { + Object.defineProperty(window, "open", { value: vi.fn(), writable: true }); + }); + + it("renders label SHARED.BUY-TICKET", () => { + render(); + expect(screen.getByText("SHARED.BUY-TICKET")).toBeTruthy(); + }); + + it("has data-testid buy-ticket-button", () => { + render(); + expect(screen.getByTestId("buy-ticket-button")).toBeTruthy(); + }); + + it("click opens aeroflot buy ticket URL with locale, routes, and autosearch", () => { + render(); + fireEvent.click(screen.getByTestId("buy-ticket-button")); + const openMock = window.open as ReturnType; + expect(openMock).toHaveBeenCalledTimes(1); + const url = openMock.mock.calls[0]![0] as string; + expect(url).toContain("aeroflot.ru/sb/app/ru-ru"); + expect(url).toContain("routes=SVO.20260420.LED"); + expect(url).toContain("autosearch=Y"); + }); +}); diff --git a/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.tsx b/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.tsx new file mode 100644 index 00000000..691e429c --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/BuyTicketButton.tsx @@ -0,0 +1,41 @@ +import type { FC } from "react"; +import { parseISO, format } from "date-fns"; +import { useTranslation } from "@/i18n/provider.js"; +import type { ISimpleFlight } from "../../types.js"; +import "./actions.scss"; + +export interface BuyTicketButtonProps { + flight: ISimpleFlight; + locale: string; +} + +function buildBuyTicketUrl(flight: ISimpleFlight, locale: string): string { + const legs = flight.routeType === "Direct" ? [flight.leg] : flight.legs; + const firstLeg = legs[0]!; + const lastLeg = legs[legs.length - 1]!; + const dep = firstLeg.departure.scheduled.airportCode; + const arr = lastLeg.arrival.scheduled.airportCode; + const depDate = parseISO(firstLeg.departure.times.scheduledDeparture.utc); + const date = format(depDate, "yyyyMMdd"); + return `https://www.aeroflot.ru/sb/app/${locale}-${locale}#/search?adults=1&cabin=economy&children=0&infants=0&routes=${dep}.${date}.${arr}&autosearch=Y`; +} + +export const BuyTicketButton: FC = ({ flight, locale }) => { + const { t } = useTranslation(); + + const handleClick = () => { + const url = buildBuyTicketUrl(flight, locale); + window.open(url, "_blank", "noopener,noreferrer"); + }; + + return ( + + ); +};