@voyantjs/flights-ui 0.35.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/airport-combobox.d.ts.map +1 -1
- package/dist/components/airport-combobox.js +4 -2
- package/dist/components/flight-baggage-step.d.ts.map +1 -1
- package/dist/components/flight-baggage-step.js +29 -18
- package/dist/components/flight-billing-step.d.ts.map +1 -1
- package/dist/components/flight-billing-step.js +30 -24
- package/dist/components/flight-booking-journey.d.ts.map +1 -1
- package/dist/components/flight-booking-journey.js +15 -26
- package/dist/components/flight-booking-ledger.d.ts.map +1 -1
- package/dist/components/flight-booking-ledger.js +22 -12
- package/dist/components/flight-booking-page.js +12 -12
- package/dist/components/flight-booking-shell.d.ts.map +1 -1
- package/dist/components/flight-booking-shell.js +54 -29
- package/dist/components/flight-contact-form.d.ts.map +1 -1
- package/dist/components/flight-contact-form.js +7 -3
- package/dist/components/flight-fare-upsell-step.d.ts.map +1 -1
- package/dist/components/flight-fare-upsell-step.js +58 -30
- package/dist/components/flight-filters-bar.d.ts.map +1 -1
- package/dist/components/flight-filters-bar.js +21 -13
- package/dist/components/flight-itinerary.d.ts.map +1 -1
- package/dist/components/flight-itinerary.js +26 -6
- package/dist/components/flight-offer-detail.d.ts.map +1 -1
- package/dist/components/flight-offer-detail.js +23 -35
- package/dist/components/flight-offer-row.d.ts.map +1 -1
- package/dist/components/flight-offer-row.js +19 -15
- package/dist/components/flight-order-confirmation.d.ts.map +1 -1
- package/dist/components/flight-order-confirmation.js +20 -24
- package/dist/components/flight-passenger-form.d.ts.map +1 -1
- package/dist/components/flight-passenger-form.js +18 -14
- package/dist/components/flight-payment-selector.d.ts.map +1 -1
- package/dist/components/flight-payment-selector.js +4 -8
- package/dist/components/flight-payment-step.d.ts.map +1 -1
- package/dist/components/flight-payment-step.js +14 -15
- package/dist/components/flight-search-form.d.ts.map +1 -1
- package/dist/components/flight-search-form.js +4 -2
- package/dist/components/flight-seat-map.d.ts.map +1 -1
- package/dist/components/flight-seat-map.js +24 -19
- package/dist/components/flight-seats-step.js +26 -24
- package/dist/components/flight-services-step.d.ts.map +1 -1
- package/dist/components/flight-services-step.js +29 -16
- package/dist/components/pax-cabin-popover.d.ts.map +1 -1
- package/dist/components/pax-cabin-popover.js +8 -11
- package/dist/components/popular-routes.d.ts +0 -5
- package/dist/components/popular-routes.d.ts.map +1 -1
- package/dist/components/popular-routes.js +25 -43
- package/dist/i18n/en.d.ts +404 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +460 -0
- package/dist/i18n/messages.d.ts +331 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +808 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +404 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +460 -0
- package/package.json +15 -15
|
@@ -3,6 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { PaymentStep, } from "@voyantjs/checkout-ui";
|
|
4
4
|
import { Landmark } from "lucide-react";
|
|
5
5
|
import { useMemo } from "react";
|
|
6
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
7
|
/**
|
|
7
8
|
* Flight-vertical wrapper around `<PaymentStep>` from `@voyantjs/checkout-ui`.
|
|
8
9
|
* Maps the universal `PaymentChoice` event into the flight contract's
|
|
@@ -10,7 +11,15 @@ import { useMemo } from "react";
|
|
|
10
11
|
* credit" extra option (flight-specific).
|
|
11
12
|
*/
|
|
12
13
|
export function FlightPaymentStep({ value, onChange, savedMethods, loadingSavedMethods, selectedSavedId, onSelectSaved, capabilities, }) {
|
|
13
|
-
const
|
|
14
|
+
const messages = useFlightsUiMessagesOrDefault().flightPaymentStep;
|
|
15
|
+
const extraAgencyCredit = useMemo(() => ({
|
|
16
|
+
id: "ticket_on_credit",
|
|
17
|
+
label: messages.agencyCreditLabel,
|
|
18
|
+
description: messages.agencyCreditDescription,
|
|
19
|
+
icon: _jsx(Landmark, { className: "h-4 w-4 text-muted-foreground" }),
|
|
20
|
+
}), [messages]);
|
|
21
|
+
const extraOptions = useMemo(() => [extraAgencyCredit], [extraAgencyCredit]);
|
|
22
|
+
const choice = useMemo(() => intentToChoice(value, savedMethods, selectedSavedId, extraAgencyCredit.id), [value, savedMethods, selectedSavedId, extraAgencyCredit.id]);
|
|
14
23
|
return (_jsx(PaymentStep, { value: choice, onChange: (next) => {
|
|
15
24
|
if (!next) {
|
|
16
25
|
onChange({ type: "hold" });
|
|
@@ -40,31 +49,21 @@ export function FlightPaymentStep({ value, onChange, savedMethods, loadingSavedM
|
|
|
40
49
|
});
|
|
41
50
|
return;
|
|
42
51
|
}
|
|
43
|
-
if (next.type === "extra" && next.optionId ===
|
|
52
|
+
if (next.type === "extra" && next.optionId === extraAgencyCredit.id) {
|
|
44
53
|
onChange({ type: "ticket_on_credit" });
|
|
45
54
|
return;
|
|
46
55
|
}
|
|
47
56
|
// `hold` at the contract level — the parent's order-creation flow
|
|
48
57
|
// produces a payment session + landing URL the operator shares.
|
|
49
58
|
onChange({ type: "hold" });
|
|
50
|
-
}, capabilities: capabilities ?? {}, savedMethods: savedMethods, loadingSavedMethods: loadingSavedMethods, extraOptions:
|
|
59
|
+
}, capabilities: capabilities ?? {}, savedMethods: savedMethods, loadingSavedMethods: loadingSavedMethods, extraOptions: extraOptions }));
|
|
51
60
|
}
|
|
52
61
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
-
// Flight-specific extras
|
|
54
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
-
const EXTRA_AGENCY_CREDIT = {
|
|
56
|
-
id: "ticket_on_credit",
|
|
57
|
-
label: "Issue ticket on agency credit",
|
|
58
|
-
description: "Bill against the agency's IATA / consolidator credit line.",
|
|
59
|
-
icon: _jsx(Landmark, { className: "h-4 w-4 text-muted-foreground" }),
|
|
60
|
-
};
|
|
61
|
-
const FLIGHT_EXTRA_OPTIONS = [EXTRA_AGENCY_CREDIT];
|
|
62
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
62
|
// PaymentIntent ⇄ PaymentChoice translation
|
|
64
63
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
-
function intentToChoice(intent, savedMethods, selectedSavedId) {
|
|
64
|
+
function intentToChoice(intent, savedMethods, selectedSavedId, agencyCreditOptionId) {
|
|
66
65
|
if (intent.type === "ticket_on_credit") {
|
|
67
|
-
return { type: "extra", optionId:
|
|
66
|
+
return { type: "extra", optionId: agencyCreditOptionId };
|
|
68
67
|
}
|
|
69
68
|
if (intent.type === "card") {
|
|
70
69
|
if (selectedSavedId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-search-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-search-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EAGpB,MAAM,kCAAkC,CAAA;AAUzC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAA;AAE/C,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAChD,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAA;CACjE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"flight-search-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-search-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EAGpB,MAAM,kCAAkC,CAAA;AAUzC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAA;AAE/C,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAChD,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAA;CACjE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,qBAAqB,2CAoIrF"}
|
|
@@ -5,9 +5,11 @@ import { DatePicker } from "@voyantjs/ui/components/date-picker";
|
|
|
5
5
|
import { ToggleGroup, ToggleGroupItem } from "@voyantjs/ui/components/toggle-group";
|
|
6
6
|
import { ArrowLeftRight, Search } from "lucide-react";
|
|
7
7
|
import { useState } from "react";
|
|
8
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
8
9
|
import { AirportCombobox } from "./airport-combobox.js";
|
|
9
10
|
import { PaxCabinPopover } from "./pax-cabin-popover.js";
|
|
10
11
|
export function FlightSearchForm({ onSearch, loading, initial }) {
|
|
12
|
+
const messages = useFlightsUiMessagesOrDefault().flightSearchForm;
|
|
11
13
|
const initialSlices = initial?.slices ?? [];
|
|
12
14
|
const [tripType, setTripType] = useState(initial?.tripType ?? (initialSlices.length === 2 ? "round_trip" : "one_way"));
|
|
13
15
|
const [origin, setOrigin] = useState(initialSlices[0]?.origin ?? null);
|
|
@@ -49,8 +51,8 @@ export function FlightSearchForm({ onSearch, loading, initial }) {
|
|
|
49
51
|
const next = v[0];
|
|
50
52
|
if (next)
|
|
51
53
|
setTripType(next);
|
|
52
|
-
}, children: [_jsx(ToggleGroupItem, { size: "lg", value: "round_trip", children:
|
|
54
|
+
}, children: [_jsx(ToggleGroupItem, { size: "lg", value: "round_trip", children: messages.roundTrip }), _jsx(ToggleGroupItem, { size: "lg", value: "one_way", children: messages.oneWay })] }), _jsxs("div", { className: "flex flex-1 items-center gap-1", children: [_jsx(AirportCombobox, { value: origin, onChange: setOrigin, placeholder: messages.fromPlaceholder, className: "flex-1" }), _jsx(Button, { type: "button", variant: "outline", size: "icon", onClick: swap, "aria-label": messages.swapAriaLabel, className: "size-10 shrink-0", children: _jsx(ArrowLeftRight, { className: "h-4 w-4" }) }), _jsx(AirportCombobox, { value: destination, onChange: setDestination, placeholder: messages.toPlaceholder, className: "flex-1" })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(DatePicker, { value: departureDate, onChange: setDepartureDate, placeholder: messages.departPlaceholder, className: "h-10 flex-1 min-w-32" }), _jsx(DatePicker, { value: returnDate, onChange: setReturnDate, placeholder: messages.returnPlaceholder, disabled: tripType === "one_way", className: "h-10 flex-1 min-w-32" })] }), _jsx(PaxCabinPopover, { passengers: passengers, cabin: cabin, onChange: (next) => {
|
|
53
55
|
setPassengers(next.passengers);
|
|
54
56
|
setCabin(next.cabin);
|
|
55
|
-
} }), _jsxs(Button, { type: "submit", size: "lg", disabled: !ready || loading, className: "shrink-0 px-6", children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), loading ?
|
|
57
|
+
} }), _jsxs(Button, { type: "submit", size: "lg", disabled: !ready || loading, className: "shrink-0 px-6", children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), loading ? messages.searching : messages.search] })] }) }));
|
|
56
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-seat-map.d.ts","sourceRoot":"","sources":["../../src/components/flight-seat-map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-seat-map.d.ts","sourceRoot":"","sources":["../../src/components/flight-seat-map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAA;AAYrE;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,4EAA4E;IAC5E,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,mFAAmF;IACnF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAClC,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,SAAS,GACV,EAAE,kBAAkB,2CA6BpB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@voyantjs/ui/components/tooltip";
|
|
4
5
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
5
6
|
import { Plane } from "lucide-react";
|
|
7
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
8
|
/**
|
|
7
9
|
* Visual seat map. Renders each row of the aircraft using the supplied
|
|
8
10
|
* `columnLayout` (with `null` slots becoming aisles), each seat as a
|
|
@@ -12,19 +14,22 @@ import { Plane } from "lucide-react";
|
|
|
12
14
|
* Pure presentational — state lives in the parent step.
|
|
13
15
|
*/
|
|
14
16
|
export function FlightSeatMap({ seatMap, picks, onSeatClick, highlightedPaxLabel, className, }) {
|
|
17
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
15
18
|
const aircraftName = seatMap.providerData?.aircraftName ?? seatMap.aircraft;
|
|
16
|
-
return (_jsx(TooltipProvider, { delay: 200, children: _jsxs("div", { className: cn("flex flex-col items-center gap-3", className), children: [_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs", children: [_jsx(Plane, { className: "h-3.5 w-3.5" }), aircraftName ??
|
|
19
|
+
return (_jsx(TooltipProvider, { delay: 200, children: _jsxs("div", { className: cn("flex flex-col items-center gap-3", className), children: [_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs", children: [_jsx(Plane, { className: "h-3.5 w-3.5" }), aircraftName ?? messages.flightSeatMap.cabin, highlightedPaxLabel && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-foreground/60", children: "\u00B7" }), _jsx("span", { className: "text-foreground", children: formatMessage(messages.flightSeatMap.pickingSeatFor, {
|
|
20
|
+
passenger: highlightedPaxLabel,
|
|
21
|
+
}) })] }))] }), _jsx("div", { className: "rounded-2xl border bg-card p-4", children: _jsx(Cabin, { seatMap: seatMap, picks: picks, onSeatClick: onSeatClick, messages: messages }) }), _jsx(Legend, { messages: messages })] }) }));
|
|
17
22
|
}
|
|
18
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
-
function Cabin({ seatMap, picks, onSeatClick, }) {
|
|
24
|
+
function Cabin({ seatMap, picks, onSeatClick, messages, }) {
|
|
20
25
|
const layout = seatMap.columnLayout;
|
|
21
26
|
const pickIndex = new Map(picks.map((p) => [p.seatNumber, p]));
|
|
22
|
-
return (_jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx(ColumnHeader, { layout: layout }), seatMap.rows.map((row) => (_jsx(SeatRowView, { rowNumber: row.row, layout: layout, seats: row.seats, pickIndex: pickIndex, onSeatClick: onSeatClick }, row.row)))] }));
|
|
27
|
+
return (_jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx(ColumnHeader, { layout: layout }), seatMap.rows.map((row) => (_jsx(SeatRowView, { rowNumber: row.row, layout: layout, seats: row.seats, pickIndex: pickIndex, onSeatClick: onSeatClick, messages: messages }, row.row)))] }));
|
|
23
28
|
}
|
|
24
29
|
function ColumnHeader({ layout }) {
|
|
25
30
|
return (_jsxs("div", { className: "mb-1 flex items-center gap-1", children: [_jsx("span", { className: "w-6 shrink-0 text-center font-mono text-[10px] text-muted-foreground" }), layout.map((col, i) => col == null ? (_jsx("div", { className: "w-3 shrink-0" }, `gap-${i}`)) : (_jsx("span", { className: "w-7 shrink-0 text-center font-mono text-[10px] text-muted-foreground", children: col }, `col-${i}`)))] }));
|
|
26
31
|
}
|
|
27
|
-
function SeatRowView({ rowNumber, layout, seats, pickIndex, onSeatClick, }) {
|
|
32
|
+
function SeatRowView({ rowNumber, layout, seats, pickIndex, onSeatClick, messages, }) {
|
|
28
33
|
const seatByCol = new Map(seats.map((s) => [s.column, s]));
|
|
29
34
|
return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "w-6 shrink-0 text-center font-mono text-[10px] text-muted-foreground tabular-nums", children: rowNumber }), layout.map((col, i) => {
|
|
30
35
|
if (col == null) {
|
|
@@ -34,21 +39,21 @@ function SeatRowView({ rowNumber, layout, seats, pickIndex, onSeatClick, }) {
|
|
|
34
39
|
if (!seat) {
|
|
35
40
|
return (_jsx("div", { className: "h-7 w-7 shrink-0 rounded-md border border-dashed border-muted/30" }, `gap-${rowNumber}-${i}`));
|
|
36
41
|
}
|
|
37
|
-
return (_jsx(SeatTile, { seat: seat, pick: pickIndex.get(seat.seatNumber) ?? null, onClick: onSeatClick }, seat.seatNumber));
|
|
42
|
+
return (_jsx(SeatTile, { seat: seat, pick: pickIndex.get(seat.seatNumber) ?? null, onClick: onSeatClick, messages: messages }, seat.seatNumber));
|
|
38
43
|
})] }));
|
|
39
44
|
}
|
|
40
|
-
function SeatTile({ seat, pick, onClick, }) {
|
|
45
|
+
function SeatTile({ seat, pick, onClick, messages, }) {
|
|
41
46
|
const isClickable = !!onClick && (seat.status === "available" || seat.status === "selected" || pick != null);
|
|
42
47
|
const tile = (_jsxs("button", { type: "button", disabled: !isClickable, onClick: onClick ? () => onClick(seat) : undefined, className: cn("relative flex h-7 w-7 shrink-0 items-center justify-center rounded-md border font-mono text-[10px] transition-colors", seat.status === "available" && categoryClasses(seat.category), seat.status === "blocked" && "cursor-not-allowed border-transparent bg-muted/60", seat.status === "unavailable" && "cursor-not-allowed border-transparent bg-muted/30", pick && (pick.swatch ?? "bg-primary text-primary-foreground border-primary"), isClickable && !pick && "hover:border-primary hover:bg-primary/5"), children: [pick ? _jsx("span", { className: "font-semibold", children: pick.label }) : null, seat.category === "exit_row" && !pick && (_jsx("span", { className: "absolute top-0 right-0 text-[7px] font-bold leading-none text-amber-600", children: "E" }))] }));
|
|
43
48
|
if (!isClickable && seat.status !== "available")
|
|
44
49
|
return tile;
|
|
45
|
-
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: tile }), _jsx(TooltipContent, { className: "max-w-[220px]", children: _jsx(SeatTooltip, { seat: seat, pickedBy: pick?.label }) })] }));
|
|
50
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: tile }), _jsx(TooltipContent, { className: "max-w-[220px]", children: _jsx(SeatTooltip, { seat: seat, pickedBy: pick?.label, messages: messages }) })] }));
|
|
46
51
|
}
|
|
47
|
-
function SeatTooltip({ seat, pickedBy }) {
|
|
48
|
-
return (_jsxs("div", { className: "flex flex-col gap-1 text-xs", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "font-mono font-semibold", children: seat.seatNumber }), _jsxs("span", { className: "text-muted-foreground", children: [humanCategory(seat.category), seat.window &&
|
|
52
|
+
function SeatTooltip({ seat, pickedBy, messages, }) {
|
|
53
|
+
return (_jsxs("div", { className: "flex flex-col gap-1 text-xs", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "font-mono font-semibold", children: seat.seatNumber }), _jsxs("span", { className: "text-muted-foreground", children: [humanCategory(seat.category, messages), seat.window && ` · ${messages.flightSeatMap.window}`, seat.aisle && ` · ${messages.flightSeatMap.aisle}`] })] }), seat.price ? (_jsxs("span", { className: "font-medium", children: ["+", formatMoney(seat.price.amount, seat.price.currency)] })) : (_jsx("span", { className: "text-muted-foreground", children: messages.flightSeatMap.noCharge })), seat.notes && _jsx("span", { className: "text-muted-foreground", children: seat.notes }), pickedBy && (_jsx("span", { className: "text-primary", children: formatMessage(messages.flightSeatMap.pickedBy, { passenger: pickedBy }) }))] }));
|
|
49
54
|
}
|
|
50
|
-
function Legend() {
|
|
51
|
-
return (_jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3 text-[11px] text-muted-foreground", children: [_jsx(LegendChip, { className: "border-emerald-500/60 bg-card", label:
|
|
55
|
+
function Legend({ messages }) {
|
|
56
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3 text-[11px] text-muted-foreground", children: [_jsx(LegendChip, { className: "border-emerald-500/60 bg-card", label: messages.flightSeatMap.legend.available }), _jsx(LegendChip, { className: "border-cyan-500/60 bg-cyan-500/5", label: messages.flightSeatMap.legend.preferred }), _jsx(LegendChip, { className: "border-amber-500/60 bg-amber-500/5", label: messages.flightSeatMap.legend.exitRow }), _jsx(LegendChip, { className: "bg-primary text-primary-foreground", label: messages.flightSeatMap.legend.picked }), _jsx(LegendChip, { className: "bg-muted/60", label: messages.flightSeatMap.legend.taken })] }));
|
|
52
57
|
}
|
|
53
58
|
function LegendChip({ className, label }) {
|
|
54
59
|
return (_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: cn("h-3.5 w-3.5 rounded-sm border", className) }), label] }));
|
|
@@ -68,20 +73,20 @@ function categoryClasses(category) {
|
|
|
68
73
|
return "border-emerald-500/60 bg-card text-emerald-700";
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
|
-
function humanCategory(c) {
|
|
76
|
+
function humanCategory(c, messages) {
|
|
72
77
|
switch (c) {
|
|
73
78
|
case "exit_row":
|
|
74
|
-
return
|
|
79
|
+
return messages.flightSeatMap.categories.exit_row;
|
|
75
80
|
case "extra_legroom":
|
|
76
|
-
return
|
|
81
|
+
return messages.flightSeatMap.categories.extra_legroom;
|
|
77
82
|
case "preferred":
|
|
78
|
-
return
|
|
83
|
+
return messages.flightSeatMap.categories.preferred;
|
|
79
84
|
case "premium":
|
|
80
|
-
return
|
|
85
|
+
return messages.flightSeatMap.categories.premium;
|
|
81
86
|
case "bulkhead":
|
|
82
|
-
return
|
|
87
|
+
return messages.flightSeatMap.categories.bulkhead;
|
|
83
88
|
default:
|
|
84
|
-
return
|
|
89
|
+
return messages.flightSeatMap.categories.standard;
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
function formatMoney(amount, currency) {
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
4
4
|
import { CheckCircle2, X } from "lucide-react";
|
|
5
5
|
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
7
|
import { FlightSeatMap } from "./flight-seat-map.js";
|
|
7
8
|
/**
|
|
8
9
|
* Wizz-style seat selection step. Tri-option gate up top (Skip / Auto-assign
|
|
@@ -12,8 +13,9 @@ import { FlightSeatMap } from "./flight-seat-map.js";
|
|
|
12
13
|
* seat assigns it to the active passenger and moves the cursor on.
|
|
13
14
|
*/
|
|
14
15
|
export function FlightSeatsStep({ outboundOffer, returnOffer, passengers, passengerCounts, getSeatMap, value, onChange, mode, onModeChange, }) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
16
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
17
|
+
const segments = useMemo(() => collectSegments(outboundOffer, returnOffer, messages), [outboundOffer, returnOffer, messages]);
|
|
18
|
+
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts, messages), [passengers, passengerCounts, messages]);
|
|
17
19
|
const [activeSegmentIdx, setActiveSegmentIdx] = useState(0);
|
|
18
20
|
const [activePaxIdx, setActivePaxIdx] = useState(0);
|
|
19
21
|
// Auto-advance to the next pax once a seat is picked for the current one.
|
|
@@ -34,7 +36,7 @@ export function FlightSeatsStep({ outboundOffer, returnOffer, passengers, passen
|
|
|
34
36
|
}
|
|
35
37
|
}, [value, segments, activeSegmentIdx, paxRows, mode]);
|
|
36
38
|
const activeSegment = segments[activeSegmentIdx] ?? null;
|
|
37
|
-
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
39
|
+
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightSeatsStep.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.flightSeatsStep.description })] }), _jsx(ModePicker, { mode: mode, onChange: onModeChange, messages: messages }), mode === "now" && activeSegment && (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(SegmentTabs, { segments: segments, activeIdx: activeSegmentIdx, paxCount: paxRows.length, picks: value, onChange: (idx) => {
|
|
38
40
|
setActiveSegmentIdx(idx);
|
|
39
41
|
setActivePaxIdx(0);
|
|
40
42
|
} }), _jsx(PaxBar, { paxRows: paxRows, activeIdx: activePaxIdx, picks: value, segmentId: activeSegment.segmentId, onActivate: setActivePaxIdx, onClear: (passengerId) => {
|
|
@@ -42,7 +44,7 @@ export function FlightSeatsStep({ outboundOffer, returnOffer, passengers, passen
|
|
|
42
44
|
} }), _jsx(SeatMapPanel, { slot: getSeatMap({
|
|
43
45
|
offerId: activeSegment.offerId,
|
|
44
46
|
segmentId: activeSegment.segmentId,
|
|
45
|
-
}), paxRows: paxRows, activePaxIdx: activePaxIdx, picks: value, segmentId: activeSegment.segmentId, onPick: (seat) => {
|
|
47
|
+
}), paxRows: paxRows, activePaxIdx: activePaxIdx, picks: value, segmentId: activeSegment.segmentId, messages: messages, onPick: (seat) => {
|
|
46
48
|
const pax = paxRows[activePaxIdx];
|
|
47
49
|
if (!pax)
|
|
48
50
|
return;
|
|
@@ -65,11 +67,11 @@ const PAX_SWATCHES = [
|
|
|
65
67
|
"bg-teal-600 text-white border-teal-700",
|
|
66
68
|
"bg-indigo-600 text-white border-indigo-700",
|
|
67
69
|
];
|
|
68
|
-
function ModePicker({ mode, onChange }) {
|
|
69
|
-
return (_jsxs("div", { className: "grid gap-2 md:grid-cols-3", children: [_jsx(ModeCard, { active: mode === "skip", onClick: () => onChange("skip"), title:
|
|
70
|
+
function ModePicker({ mode, onChange, messages, }) {
|
|
71
|
+
return (_jsxs("div", { className: "grid gap-2 md:grid-cols-3", children: [_jsx(ModeCard, { active: mode === "skip", onClick: () => onChange("skip"), title: messages.flightSeatsStep.modes.skip.title, body: messages.flightSeatsStep.modes.skip.body }), _jsx(ModeCard, { active: mode === "auto", onClick: () => onChange("auto"), title: messages.flightSeatsStep.modes.auto.title, body: messages.flightSeatsStep.modes.auto.body, recommended: true, messages: messages }), _jsx(ModeCard, { active: mode === "now", onClick: () => onChange("now"), title: messages.flightSeatsStep.modes.now.title, body: messages.flightSeatsStep.modes.now.body })] }));
|
|
70
72
|
}
|
|
71
|
-
function ModeCard({ active, onClick, title, body, recommended, }) {
|
|
72
|
-
return (_jsxs("button", { type: "button", onClick: onClick, className: cn("relative flex flex-col items-start gap-1 rounded-lg border bg-card p-4 text-left transition-colors", active ? "border-primary ring-2 ring-primary/20" : "hover:border-primary/40"), children: [recommended && (_jsx("span", { className: "absolute top-2 right-2 rounded-full bg-primary/10 px-2 py-0.5 font-medium text-[9px] text-primary uppercase tracking-wider", children:
|
|
73
|
+
function ModeCard({ active, onClick, title, body, recommended, messages, }) {
|
|
74
|
+
return (_jsxs("button", { type: "button", onClick: onClick, className: cn("relative flex flex-col items-start gap-1 rounded-lg border bg-card p-4 text-left transition-colors", active ? "border-primary ring-2 ring-primary/20" : "hover:border-primary/40"), children: [recommended && (_jsx("span", { className: "absolute top-2 right-2 rounded-full bg-primary/10 px-2 py-0.5 font-medium text-[9px] text-primary uppercase tracking-wider", children: messages?.common.recommended })), active && _jsx(CheckCircle2, { className: "absolute top-3 right-3 h-4 w-4 text-primary" }), _jsx("span", { className: "font-medium text-sm", children: title }), _jsx("span", { className: "text-muted-foreground text-xs", children: body })] }));
|
|
73
75
|
}
|
|
74
76
|
function SegmentTabs({ segments, activeIdx, paxCount, picks, onChange, }) {
|
|
75
77
|
return (_jsx("div", { className: "flex items-center gap-2 overflow-x-auto rounded-md border bg-muted/20 p-1", children: segments.map((seg, idx) => {
|
|
@@ -89,7 +91,7 @@ function PaxBar({ paxRows, activeIdx, picks, segmentId, onActivate, onClear, })
|
|
|
89
91
|
} })] })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" }))] }) }, pax.passengerId));
|
|
90
92
|
}) }));
|
|
91
93
|
}
|
|
92
|
-
function SeatMapPanel({ slot, paxRows, activePaxIdx, picks, segmentId, onPick, }) {
|
|
94
|
+
function SeatMapPanel({ slot, paxRows, activePaxIdx, picks, segmentId, onPick, messages, }) {
|
|
93
95
|
if (slot.loading) {
|
|
94
96
|
return _jsx("div", { className: "h-72 animate-pulse rounded-2xl bg-muted/40" });
|
|
95
97
|
}
|
|
@@ -97,7 +99,7 @@ function SeatMapPanel({ slot, paxRows, activePaxIdx, picks, segmentId, onPick, }
|
|
|
97
99
|
return (_jsx("div", { className: "rounded-xl border border-destructive/40 bg-destructive/5 p-4 text-destructive text-sm", children: slot.error }));
|
|
98
100
|
}
|
|
99
101
|
if (!slot.seatMap) {
|
|
100
|
-
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children:
|
|
102
|
+
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children: messages.flightSeatsStep.seatMapUnavailable }));
|
|
101
103
|
}
|
|
102
104
|
const markers = picks
|
|
103
105
|
.filter((p) => p.segmentId === segmentId)
|
|
@@ -118,13 +120,13 @@ function SeatMapPanel({ slot, paxRows, activePaxIdx, picks, segmentId, onPick, }
|
|
|
118
120
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
121
|
// Helpers
|
|
120
122
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
121
|
-
function collectSegments(outbound, returnLeg) {
|
|
123
|
+
function collectSegments(outbound, returnLeg, messages) {
|
|
122
124
|
const out = [];
|
|
123
125
|
for (const seg of itinerarySegments(outbound)) {
|
|
124
126
|
out.push({
|
|
125
127
|
offerId: outbound.offerId,
|
|
126
128
|
segmentId: seg.segmentId,
|
|
127
|
-
legLabel:
|
|
129
|
+
legLabel: messages.common.legLabels.outbound,
|
|
128
130
|
origin: seg.departure.iataCode,
|
|
129
131
|
destination: seg.arrival.iataCode,
|
|
130
132
|
carrier: seg.carrierCode,
|
|
@@ -136,7 +138,7 @@ function collectSegments(outbound, returnLeg) {
|
|
|
136
138
|
out.push({
|
|
137
139
|
offerId: returnLeg.offerId,
|
|
138
140
|
segmentId: seg.segmentId,
|
|
139
|
-
legLabel:
|
|
141
|
+
legLabel: messages.common.legLabels.return,
|
|
140
142
|
origin: seg.departure.iataCode,
|
|
141
143
|
destination: seg.arrival.iataCode,
|
|
142
144
|
carrier: seg.carrierCode,
|
|
@@ -154,7 +156,7 @@ function itinerarySegments(offer) {
|
|
|
154
156
|
}
|
|
155
157
|
return out;
|
|
156
158
|
}
|
|
157
|
-
function buildPassengerRows(passengers, counts) {
|
|
159
|
+
function buildPassengerRows(passengers, counts, messages) {
|
|
158
160
|
const rows = [];
|
|
159
161
|
const total = passengers.length > 0
|
|
160
162
|
? passengers.length
|
|
@@ -164,7 +166,7 @@ function buildPassengerRows(passengers, counts) {
|
|
|
164
166
|
if (p) {
|
|
165
167
|
rows.push({
|
|
166
168
|
passengerId: p.passengerId,
|
|
167
|
-
label: nameOrFallback(p, i),
|
|
169
|
+
label: nameOrFallback(p, i, messages),
|
|
168
170
|
short: shortLabel(p, i),
|
|
169
171
|
swatch: PAX_SWATCHES[i % PAX_SWATCHES.length] ?? PAX_SWATCHES[0],
|
|
170
172
|
});
|
|
@@ -172,7 +174,7 @@ function buildPassengerRows(passengers, counts) {
|
|
|
172
174
|
else {
|
|
173
175
|
rows.push({
|
|
174
176
|
passengerId: synthPaxId(i, counts),
|
|
175
|
-
label: synthPaxLabel(i, counts),
|
|
177
|
+
label: synthPaxLabel(i, counts, messages),
|
|
176
178
|
short: String(i + 1),
|
|
177
179
|
swatch: PAX_SWATCHES[i % PAX_SWATCHES.length] ?? PAX_SWATCHES[0],
|
|
178
180
|
});
|
|
@@ -180,12 +182,11 @@ function buildPassengerRows(passengers, counts) {
|
|
|
180
182
|
}
|
|
181
183
|
return rows;
|
|
182
184
|
}
|
|
183
|
-
function nameOrFallback(p, idx) {
|
|
185
|
+
function nameOrFallback(p, idx, messages) {
|
|
184
186
|
const full = `${p.firstName} ${p.lastName}`.trim();
|
|
185
187
|
if (full)
|
|
186
188
|
return full;
|
|
187
|
-
|
|
188
|
-
return `${cap} ${idx + 1}`;
|
|
189
|
+
return `${messages.common.passengerTypeLabels[p.type]} ${idx + 1}`;
|
|
189
190
|
}
|
|
190
191
|
function shortLabel(p, idx) {
|
|
191
192
|
const initials = `${p.firstName[0] ?? ""}${p.lastName[0] ?? ""}`.trim();
|
|
@@ -201,11 +202,12 @@ function synthPaxId(i, counts) {
|
|
|
201
202
|
return `pax_child_${afterAdults + 1}`;
|
|
202
203
|
return `pax_infant_${i - counts.adults - (counts.children ?? 0) + 1}`;
|
|
203
204
|
}
|
|
204
|
-
function synthPaxLabel(i, counts) {
|
|
205
|
+
function synthPaxLabel(i, counts, messages) {
|
|
205
206
|
if (i < counts.adults)
|
|
206
|
-
return
|
|
207
|
+
return `${messages.common.passengerTypeLabels.adult} ${i + 1}`;
|
|
207
208
|
const afterAdults = i - counts.adults;
|
|
208
|
-
if (afterAdults < (counts.children ?? 0))
|
|
209
|
-
return
|
|
210
|
-
|
|
209
|
+
if (afterAdults < (counts.children ?? 0)) {
|
|
210
|
+
return `${messages.common.passengerTypeLabels.child} ${afterAdults + 1}`;
|
|
211
|
+
}
|
|
212
|
+
return `${messages.common.passengerTypeLabels.infant} ${i - counts.adults - (counts.children ?? 0) + 1}`;
|
|
211
213
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-services-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-services-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,gBAAgB,EAEhB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-services-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-services-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,gBAAgB,EAEhB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;AASzC,KAAK,eAAe,GAAG,WAAW,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAA;AACpE,KAAK,WAAW,GAAG,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAA;AAE5D,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,eAAe,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACxC,aAAa,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,aAAa,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,eAAe,EAAE,eAAe,CAAA;IAChC,UAAU,EAAE,eAAe,CAAA;IAC3B,MAAM,EAAE,WAAW,CAAA;IACnB,kBAAkB,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACnD,cAAc,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAA;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,eAAe,EACf,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACV,MAAM,EACN,kBAAkB,EAClB,cAAc,EACd,OAAO,GACR,EAAE,uBAAuB,2CA6FzB"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { Button } from "@voyantjs/ui/components/button";
|
|
4
5
|
import { Checkbox } from "@voyantjs/ui/components/checkbox";
|
|
5
6
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
6
7
|
import { Accessibility, Minus, Package, Plus, Sparkles } from "lucide-react";
|
|
7
8
|
import { useMemo } from "react";
|
|
9
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
8
10
|
/**
|
|
9
11
|
* Combined services step covering special assistance (per-pax, trip-wide)
|
|
10
12
|
* and extras (per-pax, per-leg). Assistance is a flat checkbox list per
|
|
@@ -13,13 +15,14 @@ import { useMemo } from "react";
|
|
|
13
15
|
* leave the step blank and continue.
|
|
14
16
|
*/
|
|
15
17
|
export function FlightServicesStep({ outboundCatalog, returnCatalog, outboundOffer, returnOffer, passengers, passengerCounts, assistance, extras, onAssistanceChange, onExtrasChange, loading, }) {
|
|
18
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
16
19
|
const isRoundTrip = !!returnOffer;
|
|
17
|
-
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts), [passengers, passengerCounts]);
|
|
20
|
+
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts, messages), [passengers, passengerCounts, messages]);
|
|
18
21
|
if (loading) {
|
|
19
22
|
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-muted/40" }), _jsx("div", { className: "h-40 animate-pulse rounded-xl bg-muted/40" })] }));
|
|
20
23
|
}
|
|
21
24
|
if (!outboundCatalog) {
|
|
22
|
-
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children:
|
|
25
|
+
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children: messages.flightServicesStep.servicesUnavailable }));
|
|
23
26
|
}
|
|
24
27
|
const toggleAssistance = (passengerId, optionId, checked) => {
|
|
25
28
|
const filtered = assistance.filter((p) => !(p.passengerId === passengerId && p.optionId === optionId));
|
|
@@ -39,12 +42,14 @@ export function FlightServicesStep({ outboundCatalog, returnCatalog, outboundOff
|
|
|
39
42
|
onExtrasChange(filtered);
|
|
40
43
|
}
|
|
41
44
|
};
|
|
42
|
-
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
45
|
+
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightServicesStep.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.flightServicesStep.description })] }), _jsx(AssistanceSection, { passengers: paxRows, options: outboundCatalog.assistance, value: assistance, onToggle: toggleAssistance, messages: messages }), _jsx(ExtrasSection, { legLabel: messages.common.legLabels.outbound, offer: outboundOffer, catalog: outboundCatalog, passengers: paxRows, sliceIndex: 0, value: extras, onSetQty: setExtraQty, messages: messages }), isRoundTrip && returnCatalog && returnOffer && (_jsx(ExtrasSection, { legLabel: messages.common.legLabels.return, offer: returnOffer, catalog: returnCatalog, passengers: paxRows, sliceIndex: 1, value: extras, onSetQty: setExtraQty, messages: messages }))] }));
|
|
43
46
|
}
|
|
44
|
-
function AssistanceSection({ passengers, options, value, onToggle, }) {
|
|
45
|
-
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-center gap-2", children: [_jsx(Accessibility, { className: "h-4 w-4 text-muted-foreground" }), _jsx("h3", { className: "font-medium text-sm", children:
|
|
47
|
+
function AssistanceSection({ passengers, options, value, onToggle, messages, }) {
|
|
48
|
+
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-center gap-2", children: [_jsx(Accessibility, { className: "h-4 w-4 text-muted-foreground" }), _jsx("h3", { className: "font-medium text-sm", children: messages.flightServicesStep.specialAssistance })] }), _jsx("div", { className: "flex flex-col gap-5", children: passengers.map((pax) => {
|
|
46
49
|
const paxPicks = value.filter((v) => v.passengerId === pax.passengerId);
|
|
47
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: pax.label }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: paxPicks.length === 0
|
|
50
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: pax.label }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: paxPicks.length === 0
|
|
51
|
+
? messages.flightServicesStep.noAssistanceNeeded
|
|
52
|
+
: `${paxPicks.length} ${messages.common.selected}` })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: options.map((opt) => {
|
|
48
53
|
const checked = paxPicks.some((p) => p.optionId === opt.id);
|
|
49
54
|
const id = `svc-${pax.passengerId}-${opt.id}`;
|
|
50
55
|
return (_jsxs("div", { className: cn("flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition-colors", checked
|
|
@@ -53,13 +58,13 @@ function AssistanceSection({ passengers, options, value, onToggle, }) {
|
|
|
53
58
|
}) })] }, pax.passengerId));
|
|
54
59
|
}) })] }));
|
|
55
60
|
}
|
|
56
|
-
function ExtrasSection({ legLabel, offer, catalog, passengers, sliceIndex, value, onSetQty, }) {
|
|
61
|
+
function ExtrasSection({ legLabel, offer, catalog, passengers, sliceIndex, value, onSetQty, messages, }) {
|
|
57
62
|
if (catalog.extras.length === 0)
|
|
58
63
|
return null;
|
|
59
64
|
const itin = offer.itineraries[0];
|
|
60
65
|
const first = itin?.segments[0];
|
|
61
66
|
const last = itin?.segments[itin.segments.length - 1];
|
|
62
|
-
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-baseline justify-between gap-2", children: [_jsxs("h3", { className: "flex items-center gap-2 font-medium text-sm", children: [_jsx(Sparkles, { className: "h-4 w-4 text-muted-foreground" }),
|
|
67
|
+
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-baseline justify-between gap-2", children: [_jsxs("h3", { className: "flex items-center gap-2 font-medium text-sm", children: [_jsx(Sparkles, { className: "h-4 w-4 text-muted-foreground" }), formatMessage(messages.flightServicesStep.extras, { leg: legLabel })] }), first && last && (_jsxs("span", { className: "text-muted-foreground text-xs", children: [first.departure.iataCode, " \u2192 ", last.arrival.iataCode] }))] }), _jsx("div", { className: "flex flex-col gap-4", children: passengers.map((pax) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: pax.label }), _jsx("ul", { className: "flex flex-col gap-1.5", children: catalog.extras.map((opt) => {
|
|
63
68
|
const pick = value.find((v) => v.passengerId === pax.passengerId &&
|
|
64
69
|
v.sliceIndex === sliceIndex &&
|
|
65
70
|
v.optionId === opt.id);
|
|
@@ -71,32 +76,40 @@ function ExtraRow({ option, quantity, onChange, }) {
|
|
|
71
76
|
return (_jsxs("li", { className: "flex items-center justify-between gap-3 rounded-md border bg-card px-3 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Package, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate text-sm", children: option.label }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: formatMoney(option.price.amount, option.price.currency) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => onChange(Math.max(0, quantity - 1)), disabled: quantity === 0, children: _jsx(Minus, { className: "h-3 w-3" }) }), _jsx("span", { className: "min-w-6 text-center font-medium text-sm tabular-nums", children: quantity }), _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => onChange(Math.min(9, quantity + 1)), children: _jsx(Plus, { className: "h-3 w-3" }) })] })] }));
|
|
72
77
|
}
|
|
73
78
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
74
|
-
function buildPassengerRows(passengers, counts) {
|
|
79
|
+
function buildPassengerRows(passengers, counts, messages) {
|
|
75
80
|
if (passengers.length > 0) {
|
|
76
81
|
return passengers.map((p) => ({
|
|
77
82
|
passengerId: p.passengerId,
|
|
78
|
-
label: nameOrFallback(p),
|
|
83
|
+
label: nameOrFallback(p, messages),
|
|
79
84
|
}));
|
|
80
85
|
}
|
|
81
86
|
const out = [];
|
|
82
87
|
for (let i = 1; i <= counts.adults; i++) {
|
|
83
|
-
out.push({
|
|
88
|
+
out.push({
|
|
89
|
+
passengerId: `pax_adult_${i}`,
|
|
90
|
+
label: `${messages.common.passengerTypeLabels.adult} ${i}`,
|
|
91
|
+
});
|
|
84
92
|
}
|
|
85
93
|
for (let i = 1; i <= (counts.children ?? 0); i++) {
|
|
86
|
-
out.push({
|
|
94
|
+
out.push({
|
|
95
|
+
passengerId: `pax_child_${i}`,
|
|
96
|
+
label: `${messages.common.passengerTypeLabels.child} ${i}`,
|
|
97
|
+
});
|
|
87
98
|
}
|
|
88
99
|
for (let i = 1; i <= (counts.infants ?? 0); i++) {
|
|
89
|
-
out.push({
|
|
100
|
+
out.push({
|
|
101
|
+
passengerId: `pax_infant_${i}`,
|
|
102
|
+
label: `${messages.common.passengerTypeLabels.infant} ${i}`,
|
|
103
|
+
});
|
|
90
104
|
}
|
|
91
105
|
return out;
|
|
92
106
|
}
|
|
93
|
-
function nameOrFallback(p) {
|
|
107
|
+
function nameOrFallback(p, messages) {
|
|
94
108
|
const full = `${p.firstName} ${p.lastName}`.trim();
|
|
95
109
|
if (full)
|
|
96
110
|
return full;
|
|
97
111
|
const idx = p.passengerId.match(/_(\d+)$/)?.[1] ?? "1";
|
|
98
|
-
|
|
99
|
-
return `${cap} ${idx}`;
|
|
112
|
+
return `${messages.common.passengerTypeLabels[p.type]} ${idx}`;
|
|
100
113
|
}
|
|
101
114
|
function formatMoney(amount, currency) {
|
|
102
115
|
const n = Number(amount);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pax-cabin-popover.d.ts","sourceRoot":"","sources":["../../src/components/pax-cabin-popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"pax-cabin-popover.d.ts","sourceRoot":"","sources":["../../src/components/pax-cabin-popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAcnF,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,eAAe,CAAA;IAC3B,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAA;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CA6E/F"}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { Button } from "@voyantjs/ui/components/button";
|
|
4
5
|
import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components/popover";
|
|
5
6
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components/select";
|
|
6
7
|
import { Minus, Plus, Users } from "lucide-react";
|
|
7
|
-
|
|
8
|
-
economy: "Economy",
|
|
9
|
-
premium_economy: "Premium economy",
|
|
10
|
-
business: "Business",
|
|
11
|
-
first: "First",
|
|
12
|
-
};
|
|
8
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
13
9
|
/**
|
|
14
10
|
* Compact pax + cabin selector — single trigger button summarizing
|
|
15
11
|
* "2 adults · Economy" that opens a popover with steppers for each pax
|
|
@@ -17,8 +13,9 @@ const CABIN_LABEL = {
|
|
|
17
13
|
* pattern; keeps the search form on one row.
|
|
18
14
|
*/
|
|
19
15
|
export function PaxCabinPopover({ passengers, cabin, onChange, className }) {
|
|
16
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
20
17
|
const total = passengers.adults + (passengers.children ?? 0) + (passengers.infants ?? 0);
|
|
21
|
-
const summary = `${total} ${total === 1 ?
|
|
18
|
+
const summary = `${total} ${total === 1 ? messages.common.passengerSingular : messages.common.passengerPlural} · ${messages.common.cabinLabels[cabin]}`;
|
|
22
19
|
const setCount = (key, value) => {
|
|
23
20
|
onChange({
|
|
24
21
|
passengers: {
|
|
@@ -28,11 +25,11 @@ export function PaxCabinPopover({ passengers, cabin, onChange, className }) {
|
|
|
28
25
|
cabin,
|
|
29
26
|
});
|
|
30
27
|
};
|
|
31
|
-
return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "lg", className: className }), children: [_jsx(Users, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "text-sm", children: summary })] }), _jsx(PopoverContent, { align: "end", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(PaxStepper, { label:
|
|
28
|
+
return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "lg", className: className }), children: [_jsx(Users, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "text-sm", children: summary })] }), _jsx(PopoverContent, { align: "end", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(PaxStepper, { label: messages.paxCabinPopover.adults, sublabel: messages.paxCabinPopover.adultsSublabel, value: passengers.adults, min: 1, onChange: (v) => setCount("adults", v), messages: messages }), _jsx(PaxStepper, { label: messages.paxCabinPopover.children, sublabel: messages.paxCabinPopover.childrenSublabel, value: passengers.children ?? 0, min: 0, onChange: (v) => setCount("children", v), messages: messages }), _jsx(PaxStepper, { label: messages.paxCabinPopover.infants, sublabel: messages.paxCabinPopover.infantsSublabel, value: passengers.infants ?? 0, min: 0, onChange: (v) => setCount("infants", v), messages: messages }), _jsxs("div", { className: "mt-1 flex flex-col gap-1.5 border-t pt-3", children: [_jsx("span", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: messages.paxCabinPopover.cabin }), _jsxs(Select, { value: cabin, onValueChange: (v) => {
|
|
32
29
|
if (v)
|
|
33
30
|
onChange({ passengers, cabin: v });
|
|
34
|
-
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(
|
|
31
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(messages.common.cabinLabels).map((c) => (_jsx(SelectItem, { value: c, children: messages.common.cabinLabels[c] }, c))) })] })] })] }) })] }));
|
|
35
32
|
}
|
|
36
|
-
function PaxStepper({ label, sublabel, value, min, onChange, }) {
|
|
37
|
-
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsx("span", { className: "text-sm font-medium", children: label }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: sublabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => onChange(value - 1), disabled: value <= min, "aria-label":
|
|
33
|
+
function PaxStepper({ label, sublabel, value, min, onChange, messages, }) {
|
|
34
|
+
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsx("span", { className: "text-sm font-medium", children: label }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: sublabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => onChange(value - 1), disabled: value <= min, "aria-label": formatMessage(messages.paxCabinPopover.decrease, { label }), children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "w-6 text-center text-sm font-medium tabular-nums", children: value }), _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => onChange(value + 1), "aria-label": formatMessage(messages.paxCabinPopover.increase, { label }), children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }));
|
|
38
35
|
}
|
|
@@ -30,11 +30,6 @@ export interface PopularRoutesProps {
|
|
|
30
30
|
/** Section title; pass `null` to hide. */
|
|
31
31
|
title?: string | null;
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Default route set — recognizable city pairs across regions so a fresh
|
|
35
|
-
* /flights page has something to click without having to type airports.
|
|
36
|
-
* Pages can pass their own `routes` to override.
|
|
37
|
-
*/
|
|
38
33
|
export declare const DEFAULT_POPULAR_ROUTES: PopularRoute[];
|
|
39
34
|
/**
|
|
40
35
|
* A grid of clickable popular-route cards. Each card synthesizes a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popular-routes.d.ts","sourceRoot":"","sources":["../../src/components/popular-routes.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"popular-routes.d.ts","sourceRoot":"","sources":["../../src/components/popular-routes.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAOvF,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAA;IACnB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,8EAA8E;IAC9E,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAChD,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,SAAS,GAAG,YAAY,CAAA;IACnC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAkDD,eAAO,MAAM,sBAAsB,EAAE,YAAY,EAKhD,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,QAAuB,EACvB,OAAY,EACZ,UAAc,EACd,KAAiB,EACjB,MAAU,EACV,SAAS,EACT,KAAK,GACN,EAAE,kBAAkB,2CAqEpB"}
|