@voyantjs/flights-ui 0.35.0 → 0.37.0
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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-booking-shell.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAEX,eAAe,EAGhB,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-booking-shell.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAEX,eAAe,EAGhB,MAAM,kCAAkC,CAAA;AAQzC,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,KAAK,iBAAiB,EAAmB,MAAM,wBAAwB,CAAA;AAiDhF;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACxC,aAAa,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,UAAU,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,iBAAiB,CAAA;CACjF;AAED,sFAAsF;AACtF,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,SAAS,EAAE,wBAAwB,CAAA;IACnC,UAAU,EAAE,eAAe,CAAA;IAC3B,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAA;IAC1E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,2FAA2F;IAC3F,WAAW,CAAC,EAAE,wBAAwB,CAAA;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,gCAAgC,CAAA;IACtD;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,uBAAuB,CAAA;IAC7C,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;IAChE;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,SAAS,CAAA;IAChG;;OAEG;IACH,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7F;;;OAGG;IACH,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CACtD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,UAAU,EACV,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,qBAAqB,GACtB,EAAE,uBAAuB,kDAsYzB"}
|
|
@@ -1,9 +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 { cn } from "@voyantjs/ui/lib/utils";
|
|
5
6
|
import { Check, ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
7
|
import { useMemo, useState } from "react";
|
|
8
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
7
9
|
import { FlightBaggageStep } from "./flight-baggage-step.js";
|
|
8
10
|
import { emptyBillingValue, FlightBillingStep, validateBilling, } from "./flight-billing-step.js";
|
|
9
11
|
import { FlightBookingLedger, } from "./flight-booking-ledger.js";
|
|
@@ -14,15 +16,15 @@ import { FlightPaymentStep, } from "./flight-payment-step.js";
|
|
|
14
16
|
import { FlightSeatsStep } from "./flight-seats-step.js";
|
|
15
17
|
import { FlightServicesStep } from "./flight-services-step.js";
|
|
16
18
|
const ALL_STEPS = [
|
|
17
|
-
{ id: "review"
|
|
18
|
-
{ id: "fares"
|
|
19
|
-
{ id: "passengers"
|
|
20
|
-
{ id: "bags"
|
|
21
|
-
{ id: "seats"
|
|
22
|
-
{ id: "services"
|
|
23
|
-
{ id: "billing"
|
|
24
|
-
{ id: "payment"
|
|
25
|
-
{ id: "confirm"
|
|
19
|
+
{ id: "review" },
|
|
20
|
+
{ id: "fares" },
|
|
21
|
+
{ id: "passengers" },
|
|
22
|
+
{ id: "bags" },
|
|
23
|
+
{ id: "seats" },
|
|
24
|
+
{ id: "services" },
|
|
25
|
+
{ id: "billing" },
|
|
26
|
+
{ id: "payment" },
|
|
27
|
+
{ id: "confirm" },
|
|
26
28
|
];
|
|
27
29
|
/**
|
|
28
30
|
* Steps that always render — the rest are adapter-capability-gated.
|
|
@@ -51,6 +53,7 @@ const ALWAYS_VISIBLE = new Set([
|
|
|
51
53
|
* called — so the booking adapter sees a single offer with all legs intact.
|
|
52
54
|
*/
|
|
53
55
|
export function FlightBookingShell({ selection, passengers, onBook, onBooked, onCancel, onEditOutbound, onEditReturn, carrierName, airportName, ancillaries, seatMaps, savedPaymentMethods, paymentCapabilities, documentsRequired, renderPassengerPicker, renderBillingPersonPicker, renderBillingOrgPicker, onSaveBillingDefaults, }) {
|
|
56
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
54
57
|
const [stepId, setStepId] = useState("review");
|
|
55
58
|
const [paxList, setPaxList] = useState([]);
|
|
56
59
|
const [billing, setBilling] = useState(emptyBillingValue);
|
|
@@ -142,6 +145,7 @@ export function FlightBookingShell({ selection, passengers, onBook, onBooked, on
|
|
|
142
145
|
outboundCatalog: ancillaries?.outboundCatalog ?? null,
|
|
143
146
|
returnCatalog: ancillaries?.returnCatalog ?? null,
|
|
144
147
|
seatMaps,
|
|
148
|
+
messages,
|
|
145
149
|
}), [
|
|
146
150
|
baggage,
|
|
147
151
|
extras,
|
|
@@ -153,6 +157,7 @@ export function FlightBookingShell({ selection, passengers, onBook, onBooked, on
|
|
|
153
157
|
ancillaries?.outboundCatalog,
|
|
154
158
|
ancillaries?.returnCatalog,
|
|
155
159
|
seatMaps,
|
|
160
|
+
messages,
|
|
156
161
|
]);
|
|
157
162
|
const completedSections = useMemo(() => {
|
|
158
163
|
const set = new Set();
|
|
@@ -255,32 +260,44 @@ export function FlightBookingShell({ selection, passengers, onBook, onBooked, on
|
|
|
255
260
|
setSubmitting(false);
|
|
256
261
|
}
|
|
257
262
|
};
|
|
258
|
-
return (_jsxs("div", { className: "flex flex-col gap-6 lg:grid lg:grid-cols-[1fr_360px] lg:items-start", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-5", children: [_jsx(Stepper, { steps: steps, currentIdx: stepIdx }), error && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-destructive text-sm", children: error })), _jsxs("div", { className: "flex flex-col gap-4", children: [step.id === "review" && (_jsx(ReviewStep, { selection: selection, carrierName: carrierName, airportName: airportName })), step.id === "fares" && (_jsx(FlightFareUpsellStep, { outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: fareBundles, onChange: setFareBundles, sameForAllPassengers: sameFareForAllPax, onSameForAllPassengersChange: setSameFareForAllPax })), step.id === "passengers" && (_jsx(FlightPassengerForm, { counts: passengers, value: paxList, onChange: setPaxList, documentsRequired: documentsRequired, renderPicker: renderPassengerPicker })), step.id === "bags" && (_jsx(FlightBaggageStep, { outboundCatalog: ancillaries?.outboundCatalog ?? null, returnCatalog: ancillaries?.returnCatalog ?? null, outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: baggage, onChange: setBaggage, sameForBothDirections: sameForBothDirections, onSameForBothDirectionsChange: setSameForBothDirections, loading: ancillaries?.loading })), step.id === "seats" && (_jsx(FlightSeatsStep, { outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: seats, onChange: setSeats, mode: seatsMode, onModeChange: setSeatsMode, getSeatMap: seatMaps?.getSeatMap ??
|
|
263
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 lg:grid lg:grid-cols-[1fr_360px] lg:items-start", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-5", children: [_jsx(Stepper, { steps: steps, currentIdx: stepIdx, messages: messages }), error && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-destructive text-sm", children: error })), _jsxs("div", { className: "flex flex-col gap-4", children: [step.id === "review" && (_jsx(ReviewStep, { selection: selection, carrierName: carrierName, airportName: airportName })), step.id === "fares" && (_jsx(FlightFareUpsellStep, { outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: fareBundles, onChange: setFareBundles, sameForAllPassengers: sameFareForAllPax, onSameForAllPassengersChange: setSameFareForAllPax })), step.id === "passengers" && (_jsx(FlightPassengerForm, { counts: passengers, value: paxList, onChange: setPaxList, documentsRequired: documentsRequired, renderPicker: renderPassengerPicker })), step.id === "bags" && (_jsx(FlightBaggageStep, { outboundCatalog: ancillaries?.outboundCatalog ?? null, returnCatalog: ancillaries?.returnCatalog ?? null, outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: baggage, onChange: setBaggage, sameForBothDirections: sameForBothDirections, onSameForBothDirectionsChange: setSameForBothDirections, loading: ancillaries?.loading })), step.id === "seats" && (_jsx(FlightSeatsStep, { outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, value: seats, onChange: setSeats, mode: seatsMode, onModeChange: setSeatsMode, getSeatMap: seatMaps?.getSeatMap ??
|
|
264
|
+
(() => ({ seatMap: null, error: messages.flightBookingShell.seatMapsUnavailable })) })), step.id === "services" && (_jsx(FlightServicesStep, { outboundCatalog: ancillaries?.outboundCatalog ?? null, returnCatalog: ancillaries?.returnCatalog ?? null, outboundOffer: selection.outbound, returnOffer: selection.return, passengers: paxList, passengerCounts: passengers, assistance: assistance, extras: extras, onAssistanceChange: setAssistance, onExtrasChange: setExtras, loading: ancillaries?.loading })), step.id === "billing" && (_jsx(FlightBillingStep, { value: billing, onChange: setBilling, eligiblePassengers: paxList
|
|
259
265
|
.filter((p) => p.type === "adult" && p.firstName.trim() !== "" && p.lastName.trim() !== "")
|
|
260
266
|
.map((p) => ({
|
|
261
267
|
id: p.passengerId,
|
|
262
268
|
firstName: p.firstName,
|
|
263
269
|
...(p.middleName ? { middleName: p.middleName } : {}),
|
|
264
270
|
lastName: p.lastName,
|
|
265
|
-
})), renderPersonPicker: renderBillingPersonPicker, renderOrgPicker: renderBillingOrgPicker })), step.id === "payment" && (_jsx(FlightPaymentStep, { value: payment, onChange: setPayment, savedMethods: savedPaymentMethods?.methods ?? [], loadingSavedMethods: savedPaymentMethods?.loading, selectedSavedId: paymentSavedId, onSelectSaved: setPaymentSavedId, capabilities: paymentCapabilities })), step.id === "confirm" && (_jsx(ConfirmStep, { selection: selection, passengers: paxList, billing: billing, payment: payment, carrierName: carrierName, airportName: airportName }))] }), _jsxs("div", { className: "flex items-center justify-between border-t pt-4", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => (stepIdx === 0 ? onCancel?.() : goBack()), disabled: submitting, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), stepIdx === 0
|
|
271
|
+
})), renderPersonPicker: renderBillingPersonPicker, renderOrgPicker: renderBillingOrgPicker })), step.id === "payment" && (_jsx(FlightPaymentStep, { value: payment, onChange: setPayment, savedMethods: savedPaymentMethods?.methods ?? [], loadingSavedMethods: savedPaymentMethods?.loading, selectedSavedId: paymentSavedId, onSelectSaved: setPaymentSavedId, capabilities: paymentCapabilities })), step.id === "confirm" && (_jsx(ConfirmStep, { selection: selection, passengers: paxList, billing: billing, payment: payment, carrierName: carrierName, airportName: airportName }))] }), _jsxs("div", { className: "flex items-center justify-between border-t pt-4", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => (stepIdx === 0 ? onCancel?.() : goBack()), disabled: submitting, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), stepIdx === 0
|
|
272
|
+
? messages.flightBookingShell.backToResults
|
|
273
|
+
: messages.flightBookingShell.back] }), step.id === "confirm" ? (_jsx(Button, { onClick: submit, disabled: submitting, children: submitting
|
|
274
|
+
? messages.flightBookingShell.booking
|
|
275
|
+
: messages.flightBookingShell.confirmBooking })) : (_jsxs(Button, { onClick: goNext, disabled: !canContinue, children: [messages.flightBookingShell.continue, _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] }))] })] }), _jsx("div", { className: "lg:sticky lg:top-6", children: _jsx(FlightBookingLedger, { selection: selection, passengers: passengers, carrierName: carrierName, airportName: airportName, outboundExtras: outboundExtras, returnExtras: returnExtras, onEditOutbound: onEditOutbound, onEditReturn: onEditReturn, completedSections: completedSections }) })] }));
|
|
266
276
|
}
|
|
267
277
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
268
278
|
// Steps
|
|
269
279
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
270
280
|
function ReviewStep({ selection, carrierName, airportName, }) {
|
|
281
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
271
282
|
const isRoundTrip = !!selection.return;
|
|
272
|
-
return (_jsxs("div", { className: "flex flex-col gap-5 rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-base", children: isRoundTrip
|
|
283
|
+
return (_jsxs("div", { className: "flex flex-col gap-5 rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-base", children: isRoundTrip
|
|
284
|
+
? messages.flightBookingShell.reviewTrip
|
|
285
|
+
: messages.flightBookingShell.reviewFlight }), _jsx(FlightItinerary, { itinerary: selection.outbound.itineraries[0] ?? { segments: [] }, label: isRoundTrip ? messages.common.legLabels.outbound : undefined, carrierName: carrierName, airportName: airportName }), selection.return && (_jsx(FlightItinerary, { itinerary: selection.return.itineraries[0] ?? { segments: [] }, label: messages.common.legLabels.return, carrierName: carrierName, airportName: airportName }))] }));
|
|
273
286
|
}
|
|
274
287
|
function ConfirmStep({ selection, passengers, billing, payment, carrierName, airportName, }) {
|
|
288
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
275
289
|
const docsCount = passengers.filter((p) => (p.documents?.length ?? 0) > 0).length;
|
|
276
290
|
const isRoundTrip = !!selection.return;
|
|
277
|
-
return (_jsxs("div", { className: "flex flex-col gap-5 rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
278
|
-
?
|
|
291
|
+
return (_jsxs("div", { className: "flex flex-col gap-5 rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightBookingShell.confirmTitle }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(FlightItinerary, { itinerary: selection.outbound.itineraries[0] ?? { segments: [] }, label: isRoundTrip ? messages.common.legLabels.outbound : undefined, compact: true, carrierName: carrierName, airportName: airportName }), selection.return && (_jsx(FlightItinerary, { itinerary: selection.return.itineraries[0] ?? { segments: [] }, label: messages.common.legLabels.return, compact: true, carrierName: carrierName, airportName: airportName }))] }), _jsx(Row, { label: messages.flightBookingShell.rows.passengers, children: passengers.length }), _jsx(Row, { label: messages.flightBookingShell.rows.documents, children: docsCount === passengers.length && passengers.length > 0
|
|
292
|
+
? formatMessage(messages.flightBookingShell.documentsAllAdded, { count: docsCount })
|
|
279
293
|
: docsCount > 0
|
|
280
|
-
?
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
? formatMessage(messages.flightBookingShell.documentsSomeAdded, {
|
|
295
|
+
count: docsCount,
|
|
296
|
+
total: passengers.length,
|
|
297
|
+
})
|
|
298
|
+
: messages.flightBookingShell.documentsAddAtCheckIn }), _jsx(Row, { label: messages.flightBookingShell.rows.contact, children: billing.email || messages.common.noValue }), _jsx(Row, { label: messages.flightBookingShell.rows.billedTo, children: billing.mode === "company"
|
|
299
|
+
? `${billing.companyName ?? messages.common.noValue} · ${billing.vatNumber ?? ""}`
|
|
300
|
+
: `${billing.firstName} ${billing.lastName}`.trim() || messages.common.noValue }), _jsx(Row, { label: messages.flightBookingShell.rows.payment, children: _jsx("span", { className: "capitalize", children: payment.type.replace("_", " ") }) }), _jsx("p", { className: "text-muted-foreground text-xs", children: messages.flightBookingShell.confirmDescription })] }));
|
|
284
301
|
}
|
|
285
302
|
function Row({ label, children }) {
|
|
286
303
|
return (_jsxs("div", { className: "flex items-baseline justify-between border-b py-2 text-sm last:border-b-0", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { children: children })] }));
|
|
@@ -288,17 +305,17 @@ function Row({ label, children }) {
|
|
|
288
305
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
289
306
|
// Stepper
|
|
290
307
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
291
|
-
function Stepper({ steps, currentIdx }) {
|
|
308
|
+
function Stepper({ steps, currentIdx, messages, }) {
|
|
292
309
|
return (_jsx("ol", { className: "flex items-center gap-2 overflow-x-auto", children: steps.map((s, i) => {
|
|
293
310
|
const isActive = i === currentIdx;
|
|
294
311
|
const isComplete = i < currentIdx;
|
|
295
|
-
return (_jsxs("li", { className: "flex flex-1 items-center gap-2", children: [_jsx("div", { className: cn("flex h-7 w-7 shrink-0 items-center justify-center rounded-full border font-medium text-xs tabular-nums", isComplete && "border-primary bg-primary text-primary-foreground", isActive && !isComplete && "border-primary text-primary", !isActive && !isComplete && "border-border text-muted-foreground"), children: isComplete ? _jsx(Check, { className: "h-3.5 w-3.5" }) : i + 1 }), _jsx("span", { className: cn("truncate text-sm", isActive ? "font-medium text-foreground" : "text-muted-foreground"), children: s.
|
|
312
|
+
return (_jsxs("li", { className: "flex flex-1 items-center gap-2", children: [_jsx("div", { className: cn("flex h-7 w-7 shrink-0 items-center justify-center rounded-full border font-medium text-xs tabular-nums", isComplete && "border-primary bg-primary text-primary-foreground", isActive && !isComplete && "border-primary text-primary", !isActive && !isComplete && "border-border text-muted-foreground"), children: isComplete ? _jsx(Check, { className: "h-3.5 w-3.5" }) : i + 1 }), _jsx("span", { className: cn("truncate text-sm", isActive ? "font-medium text-foreground" : "text-muted-foreground"), children: messages.flightBookingShell.steps[s.id] }), i < steps.length - 1 && _jsx("div", { className: "h-px flex-1 bg-border" })] }, s.id));
|
|
296
313
|
}) }));
|
|
297
314
|
}
|
|
298
315
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
299
316
|
// Ledger line items from ancillary picks
|
|
300
317
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
301
|
-
function buildLedgerExtras({ baggage, extras, assistance, seats, fareBundles, outboundOffer, returnOffer, outboundCatalog, returnCatalog, seatMaps, }) {
|
|
318
|
+
function buildLedgerExtras({ baggage, extras, assistance, seats, fareBundles, outboundOffer, returnOffer, outboundCatalog, returnCatalog, seatMaps, messages, }) {
|
|
302
319
|
const lines = (sliceIndex, catalog, offer) => {
|
|
303
320
|
const out = [];
|
|
304
321
|
// Fare-bundle picks (first so they sit at the top of the leg block).
|
|
@@ -327,11 +344,14 @@ function buildLedgerExtras({ baggage, extras, assistance, seats, fareBundles, ou
|
|
|
327
344
|
}
|
|
328
345
|
}
|
|
329
346
|
for (const [, v] of agg) {
|
|
330
|
-
const labelSuffix = v.count > 1 ? ` (${v.count} pax)` : "";
|
|
347
|
+
const labelSuffix = v.count > 1 ? ` (${v.count} ${messages.common.pax})` : "";
|
|
331
348
|
out.push({
|
|
332
|
-
label:
|
|
349
|
+
label: formatMessage(messages.flightBookingShell.lineItems.fare, {
|
|
350
|
+
label: v.label,
|
|
351
|
+
suffix: labelSuffix,
|
|
352
|
+
}),
|
|
333
353
|
amount: v.price > 0 ? { amount: v.price.toFixed(2), currency: v.currency } : undefined,
|
|
334
|
-
meta: v.price === 0 ?
|
|
354
|
+
meta: v.price === 0 ? messages.common.included : undefined,
|
|
335
355
|
});
|
|
336
356
|
}
|
|
337
357
|
}
|
|
@@ -363,7 +383,7 @@ function buildLedgerExtras({ baggage, extras, assistance, seats, fareBundles, ou
|
|
|
363
383
|
out.push({
|
|
364
384
|
label: v.count > 1 ? `${v.count}× ${v.label}` : v.label,
|
|
365
385
|
amount: v.price > 0 ? { amount: v.price.toFixed(2), currency: v.currency } : undefined,
|
|
366
|
-
meta: v.price === 0 ?
|
|
386
|
+
meta: v.price === 0 ? messages.common.included : undefined,
|
|
367
387
|
});
|
|
368
388
|
}
|
|
369
389
|
// Extras — same aggregation.
|
|
@@ -415,17 +435,22 @@ function buildLedgerExtras({ baggage, extras, assistance, seats, fareBundles, ou
|
|
|
415
435
|
}
|
|
416
436
|
}
|
|
417
437
|
out.push({
|
|
418
|
-
label:
|
|
438
|
+
label: formatMessage(messages.flightBookingShell.lineItems.seatsPicked, {
|
|
439
|
+
count: seatPicks.length,
|
|
440
|
+
plural: seatPicks.length > 1 ? "s" : "",
|
|
441
|
+
}),
|
|
419
442
|
amount: total > 0 ? { amount: total.toFixed(2), currency } : undefined,
|
|
420
|
-
meta: total === 0 ?
|
|
443
|
+
meta: total === 0 ? messages.common.free : undefined,
|
|
421
444
|
});
|
|
422
445
|
}
|
|
423
446
|
}
|
|
424
447
|
// Assistance — only on the outbound block (it's trip-wide, not per leg).
|
|
425
448
|
if (sliceIndex === 0 && assistance.length > 0) {
|
|
426
449
|
out.push({
|
|
427
|
-
label:
|
|
428
|
-
|
|
450
|
+
label: formatMessage(messages.flightBookingShell.lineItems.specialAssistance, {
|
|
451
|
+
count: assistance.length,
|
|
452
|
+
}),
|
|
453
|
+
meta: messages.common.free,
|
|
429
454
|
});
|
|
430
455
|
}
|
|
431
456
|
return out;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-contact-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-contact-form.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flight-contact-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-contact-form.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CA0C5E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAMxE"}
|
|
@@ -3,19 +3,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Input } from "@voyantjs/ui/components/input";
|
|
4
4
|
import { Label } from "@voyantjs/ui/components/label";
|
|
5
5
|
import { Mail, Phone } from "lucide-react";
|
|
6
|
+
import { flightsUiEn } from "../i18n/en.js";
|
|
7
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
8
|
/**
|
|
7
9
|
* Booking contact details — used by the connector to send confirmation +
|
|
8
10
|
* any operational disruption notices. Email is required by most providers;
|
|
9
11
|
* phone is recommended but optional.
|
|
10
12
|
*/
|
|
11
13
|
export function FlightContactForm({ value, onChange }) {
|
|
12
|
-
|
|
14
|
+
const messages = useFlightsUiMessagesOrDefault().flightContactForm;
|
|
15
|
+
return (_jsxs("div", { className: "rounded-lg border bg-card p-4 shadow-sm", children: [_jsx("h3", { className: "mb-3 font-medium text-sm", children: messages.title }), _jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: messages.description }), _jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { className: "text-[11px] uppercase tracking-wider text-muted-foreground", children: [messages.email, " ", _jsx("span", { className: "ml-0.5 text-destructive", children: "*" })] }), _jsxs("div", { className: "relative", children: [_jsx(Mail, { className: "-translate-y-1/2 absolute top-1/2 left-3 h-4 w-4 text-muted-foreground" }), _jsx(Input, { type: "email", autoComplete: "email", value: value.email ?? "", onChange: (e) => onChange({ ...value, email: e.target.value }), placeholder: messages.emailPlaceholder, className: "pl-9" })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-[11px] uppercase tracking-wider text-muted-foreground", children: messages.phone }), _jsxs("div", { className: "relative", children: [_jsx(Phone, { className: "-translate-y-1/2 absolute top-1/2 left-3 h-4 w-4 text-muted-foreground" }), _jsx(Input, { type: "tel", autoComplete: "tel", value: value.phone ?? "", onChange: (e) => onChange({ ...value, phone: e.target.value }), placeholder: messages.phonePlaceholder, className: "pl-9" })] })] })] })] }));
|
|
13
16
|
}
|
|
14
17
|
export function validateContact(value) {
|
|
18
|
+
const messages = flightsUiEn.flightContactForm.validation;
|
|
15
19
|
if (!value.email?.trim())
|
|
16
|
-
return
|
|
20
|
+
return messages.emailRequired;
|
|
17
21
|
// Loose email check — adapters do their own validation
|
|
18
22
|
if (!/.+@.+\..+/.test(value.email))
|
|
19
|
-
return
|
|
23
|
+
return messages.emailInvalid;
|
|
20
24
|
return null;
|
|
21
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-fare-upsell-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-fare-upsell-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAElB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-fare-upsell-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-fare-upsell-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAElB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;AAQzC,KAAK,eAAe,GAAG,WAAW,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAA;AAGpE,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,0DAA0D;IAC1D,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,yEAAyE;IACzE,eAAe,EAAE,eAAe,CAAA;IAChC,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,yEAAyE;IACzE,oBAAoB,EAAE,OAAO,CAAA;IAC7B,4BAA4B,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,KAAK,EACL,QAAQ,EACR,oBAAoB,EACpB,4BAA4B,GAC7B,EAAE,yBAAyB,2CAiG3B"}
|
|
@@ -1,9 +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 { Checkbox } from "@voyantjs/ui/components/checkbox";
|
|
4
5
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
5
6
|
import { Briefcase, Check, Crown, Luggage, Sparkles, X } from "lucide-react";
|
|
6
7
|
import { useMemo } from "react";
|
|
8
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
7
9
|
/**
|
|
8
10
|
* Per-pax per-leg branded-fare upsell step. For multi-pax bookings the
|
|
9
11
|
* "Same fare for all passengers" toggle (default ON) collapses the picker
|
|
@@ -13,12 +15,13 @@ import { useMemo } from "react";
|
|
|
13
15
|
* carriers + B2B agency bookings actually exercise.
|
|
14
16
|
*/
|
|
15
17
|
export function FlightFareUpsellStep({ outboundOffer, returnOffer, passengers, passengerCounts, value, onChange, sameForAllPassengers, onSameForAllPassengersChange, }) {
|
|
16
|
-
const
|
|
18
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
19
|
+
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts, messages), [passengers, passengerCounts, messages]);
|
|
17
20
|
const isMultiPax = paxRows.length > 1;
|
|
18
21
|
const outboundBundles = outboundOffer.fareBundles ?? [];
|
|
19
22
|
const returnBundles = returnOffer?.fareBundles ?? [];
|
|
20
23
|
if (outboundBundles.length === 0 && returnBundles.length === 0) {
|
|
21
|
-
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children:
|
|
24
|
+
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children: messages.flightFareUpsellStep.unavailable }));
|
|
22
25
|
}
|
|
23
26
|
const setPick = (passengerId, sliceIndex, bundleId) => {
|
|
24
27
|
const filtered = value.filter((p) => !(p.passengerId === passengerId && p.sliceIndex === sliceIndex));
|
|
@@ -46,9 +49,9 @@ export function FlightFareUpsellStep({ outboundOffer, returnOffer, passengers, p
|
|
|
46
49
|
}));
|
|
47
50
|
onChange([...filtered, ...additions]);
|
|
48
51
|
};
|
|
49
|
-
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
52
|
+
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightFareUpsellStep.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.flightFareUpsellStep.description })] }), isMultiPax && (_jsxs("div", { className: "flex shrink-0 items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "fare-same-for-all", checked: sameForAllPassengers, onCheckedChange: (v) => onSameForAllPassengersChange(!!v) }), _jsx("label", { htmlFor: "fare-same-for-all", className: "cursor-pointer", children: messages.flightFareUpsellStep.sameForAllPassengers })] }))] }), _jsx(FareLegSection, { label: messages.common.legLabels.outbound, bundles: outboundBundles, sliceIndex: 0, paxRows: paxRows, value: value, sameForAll: sameForAllPassengers || !isMultiPax, onSetPick: setPick, onSetLegPick: setLegPick, messages: messages }), returnOffer && returnBundles.length > 0 && (_jsx(FareLegSection, { label: messages.common.legLabels.return, bundles: returnBundles, sliceIndex: 1, paxRows: paxRows, value: value, sameForAll: sameForAllPassengers || !isMultiPax, onSetPick: setPick, onSetLegPick: setLegPick, messages: messages }))] }));
|
|
50
53
|
}
|
|
51
|
-
function FareLegSection({ label, bundles, sliceIndex, paxRows, value, sameForAll, onSetPick, onSetLegPick, }) {
|
|
54
|
+
function FareLegSection({ label, bundles, sliceIndex, paxRows, value, sameForAll, onSetPick, onSetLegPick, messages, }) {
|
|
52
55
|
// For "same for all" mode we treat the leg as having one selection — the
|
|
53
56
|
// bundleId common to every pax pick on this leg (null when split or none).
|
|
54
57
|
const legPick = (() => {
|
|
@@ -61,33 +64,50 @@ function FareLegSection({ label, bundles, sliceIndex, paxRows, value, sameForAll
|
|
|
61
64
|
return null;
|
|
62
65
|
})();
|
|
63
66
|
const someoneSelected = value.some((v) => v.sliceIndex === sliceIndex);
|
|
64
|
-
return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("header", { className: "flex items-baseline justify-between gap-2", children: [_jsx("h3", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label }), someoneSelected && (_jsx("button", { type: "button", onClick: () => onSetLegPick(sliceIndex, null), className: "text-muted-foreground text-xs hover:text-foreground", children:
|
|
67
|
+
return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("header", { className: "flex items-baseline justify-between gap-2", children: [_jsx("h3", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label }), someoneSelected && (_jsx("button", { type: "button", onClick: () => onSetLegPick(sliceIndex, null), className: "text-muted-foreground text-xs hover:text-foreground", children: messages.flightFareUpsellStep.resetToBasic }))] }), sameForAll ? (_jsx(BundleGrid, { bundles: bundles, selectedId: legPick, contextLabel: paxRows.length > 1
|
|
68
|
+
? formatMessage(messages.flightFareUpsellStep.appliesToAllPassengers, {
|
|
69
|
+
count: paxRows.length,
|
|
70
|
+
})
|
|
71
|
+
: null, onPick: (id) => onSetLegPick(sliceIndex, id), messages: messages })) : (_jsx("div", { className: "flex flex-col gap-4", children: paxRows.map((pax) => {
|
|
65
72
|
const pick = value.find((v) => v.passengerId === pax.passengerId && v.sliceIndex === sliceIndex);
|
|
66
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: pax.label }), _jsx(BundleGrid, { bundles: bundles, selectedId: pick?.bundleId ?? null, onPick: (id) => onSetPick(pax.passengerId, sliceIndex, id) })] }, pax.passengerId));
|
|
73
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: pax.label }), _jsx(BundleGrid, { bundles: bundles, selectedId: pick?.bundleId ?? null, onPick: (id) => onSetPick(pax.passengerId, sliceIndex, id), messages: messages })] }, pax.passengerId));
|
|
67
74
|
}) }))] }));
|
|
68
75
|
}
|
|
69
|
-
function BundleGrid({ bundles, selectedId, contextLabel, onPick, }) {
|
|
70
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "grid gap-3 md:grid-cols-3", children: bundles.map((b) => (_jsx(BundleCard, { bundle: b, selected: selectedId === b.id, isBasicByDefault: selectedId == null && b.tier === "basic", onPick: () => onPick(b.tier === "basic" ? null : b.id) }, b.id))) }), contextLabel && _jsx("span", { className: "text-[11px] text-muted-foreground", children: contextLabel })] }));
|
|
76
|
+
function BundleGrid({ bundles, selectedId, contextLabel, onPick, messages, }) {
|
|
77
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "grid gap-3 md:grid-cols-3", children: bundles.map((b) => (_jsx(BundleCard, { bundle: b, selected: selectedId === b.id, isBasicByDefault: selectedId == null && b.tier === "basic", onPick: () => onPick(b.tier === "basic" ? null : b.id), messages: messages }, b.id))) }), contextLabel && _jsx("span", { className: "text-[11px] text-muted-foreground", children: contextLabel })] }));
|
|
71
78
|
}
|
|
72
|
-
function BundleCard({ bundle, selected, isBasicByDefault, onPick, }) {
|
|
79
|
+
function BundleCard({ bundle, selected, isBasicByDefault, onPick, messages, }) {
|
|
73
80
|
const delta = Number(bundle.priceDelta.amount);
|
|
74
|
-
const deltaLabel = delta > 0
|
|
81
|
+
const deltaLabel = delta > 0
|
|
82
|
+
? `+${formatMoney(bundle.priceDelta.amount, bundle.priceDelta.currency)}`
|
|
83
|
+
: messages.common.included;
|
|
75
84
|
const showActiveRing = selected || isBasicByDefault;
|
|
76
85
|
return (_jsxs("button", { type: "button", onClick: onPick, className: cn("relative flex flex-col gap-3 rounded-lg border bg-card p-4 text-left transition-colors", showActiveRing
|
|
77
86
|
? "border-primary ring-2 ring-primary/20"
|
|
78
|
-
: "hover:border-primary/40 hover:bg-accent/30", bundle.recommended && !showActiveRing && "border-primary/40"), children: [bundle.recommended && (_jsx("span", { className: "-translate-y-1/2 absolute top-0 left-4 rounded-full bg-primary px-2 py-0.5 font-medium text-[9px] text-primary-foreground uppercase tracking-wider", children:
|
|
79
|
-
?
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
: "hover:border-primary/40 hover:bg-accent/30", bundle.recommended && !showActiveRing && "border-primary/40"), children: [bundle.recommended && (_jsx("span", { className: "-translate-y-1/2 absolute top-0 left-4 rounded-full bg-primary px-2 py-0.5 font-medium text-[9px] text-primary-foreground uppercase tracking-wider", children: messages.common.recommended })), _jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-semibold text-sm", children: [_jsx(TierIcon, { tier: bundle.tier }), bundle.label] }), _jsx("span", { className: cn("font-medium text-xs", delta > 0 ? "text-foreground" : "text-emerald-600"), children: deltaLabel })] }), _jsxs("ul", { className: "flex flex-col gap-1.5", children: [_jsx(Inclusion, { ok: !!bundle.inclusions.cabinBag?.included, icon: _jsx(Briefcase, { className: "h-3.5 w-3.5" }), label: bundle.inclusions.cabinBag?.included
|
|
88
|
+
? formatMessage(messages.flightFareUpsellStep.cabinBag, {
|
|
89
|
+
weight: bundle.inclusions.cabinBag.weightKg
|
|
90
|
+
? `(${bundle.inclusions.cabinBag.weightKg} kg)`
|
|
91
|
+
: "",
|
|
92
|
+
}).trim()
|
|
93
|
+
: messages.flightFareUpsellStep.noCabinBag }), _jsx(Inclusion, { ok: !!bundle.inclusions.checkedBag?.included, icon: _jsx(Luggage, { className: "h-3.5 w-3.5" }), label: bundle.inclusions.checkedBag?.included
|
|
94
|
+
? formatMessage(messages.flightFareUpsellStep.checkedBag, {
|
|
95
|
+
weight: bundle.inclusions.checkedBag.weightKg
|
|
96
|
+
? `${bundle.inclusions.checkedBag.weightKg} kg`
|
|
97
|
+
: "",
|
|
98
|
+
pieces: bundle.inclusions.checkedBag.pieces && bundle.inclusions.checkedBag.pieces > 1
|
|
99
|
+
? ` × ${bundle.inclusions.checkedBag.pieces}`
|
|
100
|
+
: "",
|
|
101
|
+
}).trim()
|
|
102
|
+
: messages.flightFareUpsellStep.noCheckedBag }), _jsx(Inclusion, { ok: bundle.inclusions.seatSelection !== "none" && bundle.inclusions.seatSelection != null, icon: _jsx(Sparkles, { className: "h-3.5 w-3.5" }), label: bundle.inclusions.seatSelection === "free"
|
|
103
|
+
? messages.flightFareUpsellStep.freeSeatSelection
|
|
88
104
|
: bundle.inclusions.seatSelection === "standard"
|
|
89
|
-
?
|
|
90
|
-
:
|
|
105
|
+
? messages.flightFareUpsellStep.standardSeatSelection
|
|
106
|
+
: messages.flightFareUpsellStep.noSeatSelection }), _jsx(Inclusion, { ok: !!bundle.inclusions.priorityBoarding, label: messages.flightFareUpsellStep.priorityBoarding }), _jsx(Inclusion, { ok: !!bundle.inclusions.loungeAccess, label: messages.flightFareUpsellStep.loungeAccess }), _jsx(Inclusion, { ok: !!bundle.inclusions.changeable, label: bundle.inclusions.changeable
|
|
107
|
+
? messages.flightFareUpsellStep.freeChanges
|
|
108
|
+
: messages.flightFareUpsellStep.changesForFee }), _jsx(Inclusion, { ok: !!bundle.inclusions.refundable, label: bundle.inclusions.refundable
|
|
109
|
+
? messages.flightFareUpsellStep.refundable
|
|
110
|
+
: messages.flightFareUpsellStep.nonRefundable }), bundle.inclusions.notes?.map((n) => (_jsx(Inclusion, { ok: true, label: n }, n)))] }), selected && (_jsxs("span", { className: "mt-1 inline-flex items-center gap-1 self-start font-medium text-primary text-xs", children: [_jsx(Check, { className: "h-3 w-3" }), _jsx("span", { children: messages.common.selected })] }))] }));
|
|
91
111
|
}
|
|
92
112
|
function Inclusion({ ok, icon, label }) {
|
|
93
113
|
return (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [ok ? (_jsx(Check, { className: "h-3.5 w-3.5 shrink-0 text-emerald-600" })) : (_jsx(X, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/60" })), _jsxs("span", { className: "flex items-center gap-1.5 text-foreground", children: [icon, _jsx("span", { className: cn(!ok && "text-muted-foreground"), children: label })] })] }));
|
|
@@ -103,31 +123,39 @@ function TierIcon({ tier }) {
|
|
|
103
123
|
return _jsx(Briefcase, { className: "h-3.5 w-3.5 text-muted-foreground" });
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
|
-
function buildPassengerRows(passengers, counts) {
|
|
126
|
+
function buildPassengerRows(passengers, counts, messages) {
|
|
107
127
|
if (passengers.length > 0) {
|
|
108
128
|
return passengers.map((p, idx) => ({
|
|
109
129
|
passengerId: p.passengerId,
|
|
110
|
-
label: nameOrFallback(p, idx),
|
|
130
|
+
label: nameOrFallback(p, idx, messages),
|
|
111
131
|
}));
|
|
112
132
|
}
|
|
113
133
|
const out = [];
|
|
114
134
|
for (let i = 1; i <= counts.adults; i++) {
|
|
115
|
-
out.push({
|
|
135
|
+
out.push({
|
|
136
|
+
passengerId: `pax_adult_${i}`,
|
|
137
|
+
label: `${messages.common.passengerTypeLabels.adult} ${i}`,
|
|
138
|
+
});
|
|
116
139
|
}
|
|
117
140
|
for (let i = 1; i <= (counts.children ?? 0); i++) {
|
|
118
|
-
out.push({
|
|
141
|
+
out.push({
|
|
142
|
+
passengerId: `pax_child_${i}`,
|
|
143
|
+
label: `${messages.common.passengerTypeLabels.child} ${i}`,
|
|
144
|
+
});
|
|
119
145
|
}
|
|
120
146
|
for (let i = 1; i <= (counts.infants ?? 0); i++) {
|
|
121
|
-
out.push({
|
|
147
|
+
out.push({
|
|
148
|
+
passengerId: `pax_infant_${i}`,
|
|
149
|
+
label: `${messages.common.passengerTypeLabels.infant} ${i}`,
|
|
150
|
+
});
|
|
122
151
|
}
|
|
123
152
|
return out;
|
|
124
153
|
}
|
|
125
|
-
function nameOrFallback(p, idx) {
|
|
154
|
+
function nameOrFallback(p, idx, messages) {
|
|
126
155
|
const full = `${p.firstName} ${p.lastName}`.trim();
|
|
127
156
|
if (full)
|
|
128
157
|
return full;
|
|
129
|
-
|
|
130
|
-
return `${cap} ${idx + 1}`;
|
|
158
|
+
return `${messages.common.passengerTypeLabels[p.type]} ${idx + 1}`;
|
|
131
159
|
}
|
|
132
160
|
function formatMoney(amount, currency) {
|
|
133
161
|
const n = Number(amount);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-filters-bar.d.ts","sourceRoot":"","sources":["../../src/components/flight-filters-bar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-filters-bar.d.ts","sourceRoot":"","sources":["../../src/components/flight-filters-bar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAoBnE,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,eAAO,MAAM,oBAAoB,EAAE,kBAIlC,CAAA;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC5C,uFAAuF;IACvF,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;CACvD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,qBAAqB,2CAmD/F"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@voyantjs/ui/components/command";
|
|
6
7
|
import { Input } from "@voyantjs/ui/components/input";
|
|
7
8
|
import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components/popover";
|
|
8
9
|
import { PlusCircle, X } from "lucide-react";
|
|
10
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
9
11
|
import { AirlineLogo } from "./airline-logo.js";
|
|
10
12
|
export const EMPTY_FLIGHT_FILTERS = {
|
|
11
13
|
carriers: [],
|
|
@@ -13,15 +15,16 @@ export const EMPTY_FLIGHT_FILTERS = {
|
|
|
13
15
|
maxPrice: null,
|
|
14
16
|
};
|
|
15
17
|
export function FlightFiltersBar({ value, onChange, offers, carrierName }) {
|
|
18
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
16
19
|
const carrierBuckets = deriveCarrierBuckets(offers);
|
|
17
20
|
const stopsBuckets = deriveStopsBuckets(offers);
|
|
18
21
|
const hasSelections = value.carriers.length > 0 || value.maxStops != null || value.maxPrice != null;
|
|
19
|
-
return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(CarrierFilter, { buckets: carrierBuckets, selected: value.carriers, carrierName: carrierName, onToggle: (code) => onChange({
|
|
22
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(CarrierFilter, { buckets: carrierBuckets, selected: value.carriers, carrierName: carrierName, messages: messages.flightFiltersBar, onToggle: (code) => onChange({
|
|
20
23
|
...value,
|
|
21
24
|
carriers: value.carriers.includes(code)
|
|
22
25
|
? value.carriers.filter((c) => c !== code)
|
|
23
26
|
: [...value.carriers, code],
|
|
24
|
-
}), onClear: () => onChange({ ...value, carriers: [] }) }), _jsx(StopsFilter, { buckets: stopsBuckets, selected: value.maxStops, onSelect: (maxStops) => onChange({ ...value, maxStops }) }), _jsx(PriceFilter, { value: value.maxPrice, onChange: (maxPrice) => onChange({ ...value, maxPrice }) }), hasSelections && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 px-2 text-muted-foreground hover:text-foreground", onClick: () => onChange(EMPTY_FLIGHT_FILTERS), children: [_jsx(X, { className: "mr-1 h-3.5 w-3.5" }),
|
|
27
|
+
}), onClear: () => onChange({ ...value, carriers: [] }) }), _jsx(StopsFilter, { buckets: stopsBuckets, selected: value.maxStops, onSelect: (maxStops) => onChange({ ...value, maxStops }), messages: messages }), _jsx(PriceFilter, { value: value.maxPrice, onChange: (maxPrice) => onChange({ ...value, maxPrice }), messages: messages.flightFiltersBar }), hasSelections && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 px-2 text-muted-foreground hover:text-foreground", onClick: () => onChange(EMPTY_FLIGHT_FILTERS), children: [_jsx(X, { className: "mr-1 h-3.5 w-3.5" }), messages.flightFiltersBar.clearAll] }))] }));
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Shared inline trigger contents for the three filter popovers. Renders
|
|
@@ -34,14 +37,14 @@ function TriggerContents({ label, count, preview, }) {
|
|
|
34
37
|
return (_jsxs(_Fragment, { children: [_jsx(PlusCircle, { className: "h-3.5 w-3.5" }), _jsx("span", { children: label }), preview, count != null && count > 0 && (_jsx("span", { className: "-mr-0.5 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-medium tabular-nums text-primary-foreground", children: count }))] }));
|
|
35
38
|
}
|
|
36
39
|
const TRIGGER_CLASS = "h-8 gap-2 border-dashed";
|
|
37
|
-
function CarrierFilter({ buckets, selected, carrierName, onToggle, onClear, }) {
|
|
40
|
+
function CarrierFilter({ buckets, selected, carrierName, onToggle, onClear, messages, }) {
|
|
38
41
|
if (buckets.length === 0)
|
|
39
42
|
return null;
|
|
40
43
|
const selectedSet = new Set(selected);
|
|
41
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label:
|
|
44
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label: messages.airlines, count: selected.length }) }), _jsx(PopoverContent, { className: "w-[260px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: messages.filterAirlinesPlaceholder }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: messages.noAirlines }), _jsx(CommandGroup, { children: buckets.map((b) => {
|
|
42
45
|
const isSelected = selectedSet.has(b.iataCode);
|
|
43
46
|
return (_jsxs(CommandItem, { onSelect: () => onToggle(b.iataCode), children: [_jsx(Checkbox, { checked: isSelected, tabIndex: -1, "aria-hidden": true, className: "mr-2 pointer-events-none" }), _jsx(AirlineLogo, { iataCode: b.iataCode, name: carrierName?.(b.iataCode), size: 20, className: "mr-2" }), _jsx("span", { className: "flex-1 truncate", children: carrierName?.(b.iataCode) ?? b.iataCode }), _jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: b.count })] }, b.iataCode));
|
|
44
|
-
}) }), selected.length > 0 && (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, {}), _jsx(CommandGroup, { children: _jsx(CommandItem, { onSelect: onClear, className: "justify-center text-center text-muted-foreground", children:
|
|
47
|
+
}) }), selected.length > 0 && (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, {}), _jsx(CommandGroup, { children: _jsx(CommandItem, { onSelect: onClear, className: "justify-center text-center text-muted-foreground", children: messages.clearFilter }) })] }))] })] }) })] }));
|
|
45
48
|
}
|
|
46
49
|
function deriveCarrierBuckets(offers) {
|
|
47
50
|
const counts = new Map();
|
|
@@ -59,14 +62,19 @@ function deriveCarrierBuckets(offers) {
|
|
|
59
62
|
.map(([iataCode, count]) => ({ iataCode, count }))
|
|
60
63
|
.sort((a, b) => b.count - a.count);
|
|
61
64
|
}
|
|
62
|
-
function StopsFilter({ buckets, selected, onSelect, }) {
|
|
65
|
+
function StopsFilter({ buckets, selected, onSelect, messages, }) {
|
|
63
66
|
if (buckets.length === 0)
|
|
64
67
|
return null;
|
|
65
|
-
const
|
|
66
|
-
|
|
68
|
+
const filterMessages = messages.flightFiltersBar;
|
|
69
|
+
const preview = selected != null ? (_jsx("span", { className: "text-muted-foreground", children: selected === 0 ? `· ${messages.common.stops.nonstop}` : `· ≤ ${selected}` })) : null;
|
|
70
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label: filterMessages.stops, preview: preview }) }), _jsx(PopoverContent, { className: "w-[200px] p-0", align: "start", children: _jsx(Command, { children: _jsxs(CommandList, { children: [_jsx(CommandGroup, { children: buckets.map((b) => {
|
|
67
71
|
const isSelected = selected === b.stops;
|
|
68
|
-
return (_jsxs(CommandItem, { onSelect: () => onSelect(b.stops), children: [_jsx(Checkbox, { checked: isSelected, tabIndex: -1, "aria-hidden": true, className: "mr-2 pointer-events-none" }), _jsx("span", { className: "flex-1", children: b.stops === 0
|
|
69
|
-
|
|
72
|
+
return (_jsxs(CommandItem, { onSelect: () => onSelect(b.stops), children: [_jsx(Checkbox, { checked: isSelected, tabIndex: -1, "aria-hidden": true, className: "mr-2 pointer-events-none" }), _jsx("span", { className: "flex-1", children: b.stops === 0
|
|
73
|
+
? messages.common.stops.nonstop
|
|
74
|
+
: formatMessage(b.stops === 1
|
|
75
|
+
? messages.common.stops.upToOne
|
|
76
|
+
: messages.common.stops.upToMany, { count: b.stops }) }), _jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: b.count })] }, b.stops));
|
|
77
|
+
}) }), selected != null && (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, {}), _jsx(CommandGroup, { children: _jsx(CommandItem, { onSelect: () => onSelect(null), className: "justify-center text-center text-muted-foreground", children: filterMessages.clearFilter }) })] }))] }) }) })] }));
|
|
70
78
|
}
|
|
71
79
|
function deriveStopsBuckets(offers) {
|
|
72
80
|
const counts = new Map();
|
|
@@ -81,10 +89,10 @@ function deriveStopsBuckets(offers) {
|
|
|
81
89
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
90
|
// Price
|
|
83
91
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
-
function PriceFilter({ value, onChange, }) {
|
|
92
|
+
function PriceFilter({ value, onChange, messages, }) {
|
|
85
93
|
const preview = value != null ? _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 \u2264 ", value] }) : null;
|
|
86
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label:
|
|
94
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label: messages.price, preview: preview }) }), _jsx(PopoverContent, { className: "w-[240px] p-3", align: "start", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: messages.maximumPrice }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, placeholder: messages.noCap, defaultValue: value ?? "", className: "h-8", onBlur: (e) => {
|
|
87
95
|
const n = Number(e.target.value);
|
|
88
96
|
onChange(Number.isFinite(n) && n > 0 ? n : null);
|
|
89
|
-
} }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onChange(null), disabled: value == null, className: "self-start", children:
|
|
97
|
+
} }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onChange(null), disabled: value == null, className: "self-start", children: messages.clear })] }) })] }));
|
|
90
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-itinerary.d.ts","sourceRoot":"","sources":["../../src/components/flight-itinerary.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-itinerary.d.ts","sourceRoot":"","sources":["../../src/components/flight-itinerary.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAShF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,CAAA;IACpB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,KAAK,EACL,QAAQ,EACR,WAAW,EACX,WAAW,EACX,YAAY,EACZ,OAAO,EACP,SAAS,GACV,EAAE,oBAAoB,kDAiFtB"}
|