@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.
Files changed (56) hide show
  1. package/dist/components/airport-combobox.d.ts.map +1 -1
  2. package/dist/components/airport-combobox.js +4 -2
  3. package/dist/components/flight-baggage-step.d.ts.map +1 -1
  4. package/dist/components/flight-baggage-step.js +29 -18
  5. package/dist/components/flight-billing-step.d.ts.map +1 -1
  6. package/dist/components/flight-billing-step.js +30 -24
  7. package/dist/components/flight-booking-journey.d.ts.map +1 -1
  8. package/dist/components/flight-booking-journey.js +15 -26
  9. package/dist/components/flight-booking-ledger.d.ts.map +1 -1
  10. package/dist/components/flight-booking-ledger.js +22 -12
  11. package/dist/components/flight-booking-page.js +12 -12
  12. package/dist/components/flight-booking-shell.d.ts.map +1 -1
  13. package/dist/components/flight-booking-shell.js +54 -29
  14. package/dist/components/flight-contact-form.d.ts.map +1 -1
  15. package/dist/components/flight-contact-form.js +7 -3
  16. package/dist/components/flight-fare-upsell-step.d.ts.map +1 -1
  17. package/dist/components/flight-fare-upsell-step.js +58 -30
  18. package/dist/components/flight-filters-bar.d.ts.map +1 -1
  19. package/dist/components/flight-filters-bar.js +21 -13
  20. package/dist/components/flight-itinerary.d.ts.map +1 -1
  21. package/dist/components/flight-itinerary.js +26 -6
  22. package/dist/components/flight-offer-detail.d.ts.map +1 -1
  23. package/dist/components/flight-offer-detail.js +23 -35
  24. package/dist/components/flight-offer-row.d.ts.map +1 -1
  25. package/dist/components/flight-offer-row.js +19 -15
  26. package/dist/components/flight-order-confirmation.d.ts.map +1 -1
  27. package/dist/components/flight-order-confirmation.js +20 -24
  28. package/dist/components/flight-passenger-form.d.ts.map +1 -1
  29. package/dist/components/flight-passenger-form.js +18 -14
  30. package/dist/components/flight-payment-selector.d.ts.map +1 -1
  31. package/dist/components/flight-payment-selector.js +4 -8
  32. package/dist/components/flight-payment-step.d.ts.map +1 -1
  33. package/dist/components/flight-payment-step.js +14 -15
  34. package/dist/components/flight-search-form.d.ts.map +1 -1
  35. package/dist/components/flight-search-form.js +4 -2
  36. package/dist/components/flight-seat-map.d.ts.map +1 -1
  37. package/dist/components/flight-seat-map.js +24 -19
  38. package/dist/components/flight-seats-step.js +26 -24
  39. package/dist/components/flight-services-step.d.ts.map +1 -1
  40. package/dist/components/flight-services-step.js +29 -16
  41. package/dist/components/pax-cabin-popover.d.ts.map +1 -1
  42. package/dist/components/pax-cabin-popover.js +8 -11
  43. package/dist/components/popular-routes.d.ts +0 -5
  44. package/dist/components/popular-routes.d.ts.map +1 -1
  45. package/dist/components/popular-routes.js +25 -43
  46. package/dist/i18n/en.d.ts +404 -0
  47. package/dist/i18n/en.d.ts.map +1 -1
  48. package/dist/i18n/en.js +460 -0
  49. package/dist/i18n/messages.d.ts +331 -0
  50. package/dist/i18n/messages.d.ts.map +1 -1
  51. package/dist/i18n/provider.d.ts +808 -0
  52. package/dist/i18n/provider.d.ts.map +1 -1
  53. package/dist/i18n/ro.d.ts +404 -0
  54. package/dist/i18n/ro.d.ts.map +1 -1
  55. package/dist/i18n/ro.js +460 -0
  56. 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":"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"}
1
+ {"version":3,"file":"flight-booking-shell.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-shell.tsx"],"names":[],"mappings":"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"}
@@ -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", label: "Review" },
18
- { id: "fares", label: "Fare" },
19
- { id: "passengers", label: "Passengers" },
20
- { id: "bags", label: "Bags" },
21
- { id: "seats", label: "Seats" },
22
- { id: "services", label: "Services" },
23
- { id: "billing", label: "Billing" },
24
- { id: "payment", label: "Payment" },
25
- { id: "confirm", label: "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 ?? (() => ({ seatMap: null, error: "Seat maps unavailable" })) })), 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
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 ? "Back to results" : "Back"] }), step.id === "confirm" ? (_jsx(Button, { onClick: submit, disabled: submitting, children: submitting ? "Booking…" : "Confirm booking" })) : (_jsxs(Button, { onClick: goNext, disabled: !canContinue, children: ["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 }) })] }));
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 ? "Review your trip" : "Review your flight" }), _jsx(FlightItinerary, { itinerary: selection.outbound.itineraries[0] ?? { segments: [] }, label: isRoundTrip ? "Outbound" : undefined, carrierName: carrierName, airportName: airportName }), selection.return && (_jsx(FlightItinerary, { itinerary: selection.return.itineraries[0] ?? { segments: [] }, label: "Return", carrierName: carrierName, airportName: airportName }))] }));
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: "Confirm booking" }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(FlightItinerary, { itinerary: selection.outbound.itineraries[0] ?? { segments: [] }, label: isRoundTrip ? "Outbound" : undefined, compact: true, carrierName: carrierName, airportName: airportName }), selection.return && (_jsx(FlightItinerary, { itinerary: selection.return.itineraries[0] ?? { segments: [] }, label: "Return", compact: true, carrierName: carrierName, airportName: airportName }))] }), _jsx(Row, { label: "Passengers", children: passengers.length }), _jsx(Row, { label: "Documents", children: docsCount === passengers.length && passengers.length > 0
278
- ? `All ${docsCount} added`
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
- ? `${docsCount} of ${passengers.length} added`
281
- : "Add at check-in" }), _jsx(Row, { label: "Contact", children: billing.email || "—" }), _jsx(Row, { label: "Billed to", children: billing.mode === "company"
282
- ? `${billing.companyName ?? "—"} · ${billing.vatNumber ?? ""}`
283
- : `${billing.firstName} ${billing.lastName}`.trim() || "—" }), _jsx(Row, { label: "Payment", children: _jsx("span", { className: "capitalize", children: payment.type.replace("_", " ") }) }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Submitting will hold seats with the connector and (depending on the chosen payment intent) either issue tickets immediately or open a ticketing window. The booking will appear under the order id once confirmed." })] }));
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.label }), i < steps.length - 1 && _jsx("div", { className: "h-px flex-1 bg-border" })] }, s.id));
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: `${v.label} fare${labelSuffix}`,
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 ? "Included" : undefined,
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 ? "Included" : undefined,
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: `${seatPicks.length} seat${seatPicks.length > 1 ? "s" : ""} picked`,
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 ? "Free" : undefined,
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: `Special assistance (${assistance.length})`,
428
- meta: "Free",
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":"AAMA,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,2CA4C5E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAKxE"}
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
- return (_jsxs("div", { className: "rounded-lg border bg-card p-4 shadow-sm", children: [_jsx("h3", { className: "mb-3 font-medium text-sm", children: "Contact details" }), _jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: "Used by the airline to send confirmation, schedule changes, and operational notices for this booking." }), _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: ["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: "traveler@example.com", 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: "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: "+1 555 123 4567", className: "pl-9" })] })] })] })] }));
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 "Email is required";
20
+ return messages.emailRequired;
17
21
  // Loose email check — adapters do their own validation
18
22
  if (!/.+@.+\..+/.test(value.email))
19
- return "Email looks invalid";
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;AAMzC,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,2CA8F3B"}
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 paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts), [passengers, passengerCounts]);
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: "This offer doesn't surface fare upgrade tiers." }));
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: "Upgrade your fare" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Add bag, seat picks, and flexibility per leg \u2014 or keep the base fare." })] }), 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: "Same fare for all passengers" })] }))] }), _jsx(FareLegSection, { label: "Outbound", bundles: outboundBundles, sliceIndex: 0, paxRows: paxRows, value: value, sameForAll: sameForAllPassengers || !isMultiPax, onSetPick: setPick, onSetLegPick: setLegPick }), returnOffer && returnBundles.length > 0 && (_jsx(FareLegSection, { label: "Return", bundles: returnBundles, sliceIndex: 1, paxRows: paxRows, value: value, sameForAll: sameForAllPassengers || !isMultiPax, onSetPick: setPick, onSetLegPick: setLegPick }))] }));
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: "Reset to Basic" }))] }), sameForAll ? (_jsx(BundleGrid, { bundles: bundles, selectedId: legPick, contextLabel: paxRows.length > 1 ? `Applies to all ${paxRows.length} passengers` : null, onPick: (id) => onSetLegPick(sliceIndex, id) })) : (_jsx("div", { className: "flex flex-col gap-4", children: paxRows.map((pax) => {
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 ? `+${formatMoney(bundle.priceDelta.amount, bundle.priceDelta.currency)}` : "Included";
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: "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
79
- ? `Cabin bag ${bundle.inclusions.cabinBag.weightKg ? `(${bundle.inclusions.cabinBag.weightKg} kg)` : ""}`.trim()
80
- : "No cabin bag" }), _jsx(Inclusion, { ok: !!bundle.inclusions.checkedBag?.included, icon: _jsx(Luggage, { className: "h-3.5 w-3.5" }), label: bundle.inclusions.checkedBag?.included
81
- ? `Checked bag ${bundle.inclusions.checkedBag.weightKg
82
- ? `${bundle.inclusions.checkedBag.weightKg} kg`
83
- : ""}${bundle.inclusions.checkedBag.pieces && bundle.inclusions.checkedBag.pieces > 1
84
- ? ` × ${bundle.inclusions.checkedBag.pieces}`
85
- : ""}`.trim()
86
- : "No checked bag" }), _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"
87
- ? "Free seat selection"
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
- ? "Standard seat selection"
90
- : "No seat selection" }), _jsx(Inclusion, { ok: !!bundle.inclusions.priorityBoarding, label: "Priority boarding" }), _jsx(Inclusion, { ok: !!bundle.inclusions.loungeAccess, label: "Lounge access" }), _jsx(Inclusion, { ok: !!bundle.inclusions.changeable, label: bundle.inclusions.changeable ? "Free changes" : "Changes for a fee" }), _jsx(Inclusion, { ok: !!bundle.inclusions.refundable, label: bundle.inclusions.refundable ? "Refundable" : "Non-refundable" }), 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" }), " Selected"] }))] }));
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({ passengerId: `pax_adult_${i}`, label: `Adult ${i}` });
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({ passengerId: `pax_child_${i}`, label: `Child ${i}` });
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({ passengerId: `pax_infant_${i}`, label: `Infant ${i}` });
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
- const cap = p.type[0]?.toUpperCase() + p.type.slice(1);
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;AAmBnE,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,2CA+C/F"}
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" }), "Clear all"] }))] }));
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: "Airlines", count: selected.length }) }), _jsx(PopoverContent, { className: "w-[260px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Filter airlines\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "No airlines." }), _jsx(CommandGroup, { children: buckets.map((b) => {
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: "Clear filter" }) })] }))] })] }) })] }));
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 preview = selected != null ? (_jsx("span", { className: "text-muted-foreground", children: selected === 0 ? "· Nonstop" : `· ≤ ${selected}` })) : null;
66
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsx(Button, { variant: "outline", size: "sm", className: TRIGGER_CLASS }), children: _jsx(TriggerContents, { label: "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) => {
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 ? "Nonstop" : `Up to ${b.stops} stop${b.stops > 1 ? "s" : ""}` }), _jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: b.count })] }, b.stops));
69
- }) }), selected != null && (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, {}), _jsx(CommandGroup, { children: _jsx(CommandItem, { onSelect: () => onSelect(null), className: "justify-center text-center text-muted-foreground", children: "Clear filter" }) })] }))] }) }) })] }));
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: "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: "Maximum price" }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, placeholder: "No cap", defaultValue: value ?? "", className: "h-8", onBlur: (e) => {
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: "Clear" })] }) })] }));
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;AAOhF,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,kDA4EtB"}
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"}