@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,8 +1,10 @@
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 { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { cn } from "@voyantjs/ui/lib/utils";
5
6
  import { Plane } from "lucide-react";
7
+ import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
6
8
  import { AirlineLogo } from "./airline-logo.js";
7
9
  /**
8
10
  * Carrier-aware itinerary renderer. One itinerary = one direction of travel
@@ -17,6 +19,7 @@ import { AirlineLogo } from "./airline-logo.js";
17
19
  * suitable for the booking ledger.
18
20
  */
19
21
  export function FlightItinerary({ itinerary, label, sublabel, carrierName, airportName, aircraftName, compact, className, }) {
22
+ const messages = useFlightsUiMessagesOrDefault();
20
23
  const segs = itinerary.segments;
21
24
  const first = segs[0];
22
25
  const last = segs[segs.length - 1];
@@ -26,17 +29,34 @@ export function FlightItinerary({ itinerary, label, sublabel, carrierName, airpo
26
29
  const totalDuration = itinerary.duration ?? deriveDuration(first, last);
27
30
  const carriers = Array.from(new Set(segs.map((s) => s.carrierCode)));
28
31
  if (compact) {
29
- return (_jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [(label || sublabel) && (_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [label && (_jsx("span", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })), sublabel && _jsx("span", { className: "text-[11px] text-muted-foreground", children: sublabel })] })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex shrink-0 items-center -space-x-1", children: carriers.map((code) => (_jsx(AirlineLogo, { iataCode: code, name: carrierName?.(code), size: 20 }, code))) }), _jsxs("span", { className: "font-mono text-xs text-foreground", children: [first.departure.iataCode, " \u2192 ", last.arrival.iataCode] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [formatTime(first.departure.at), " \u2013 ", formatTime(last.arrival.at)] }), _jsxs("span", { className: "ml-auto text-[11px] text-muted-foreground", children: [stops === 0 ? "Nonstop" : `${stops} stop${stops > 1 ? "s" : ""}`, totalDuration && ` · ${formatDuration(totalDuration)}`] })] })] }));
32
+ return (_jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [(label || sublabel) && (_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [label && (_jsx("span", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })), sublabel && _jsx("span", { className: "text-[11px] text-muted-foreground", children: sublabel })] })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex shrink-0 items-center -space-x-1", children: carriers.map((code) => (_jsx(AirlineLogo, { iataCode: code, name: carrierName?.(code), size: 20 }, code))) }), _jsxs("span", { className: "font-mono text-xs text-foreground", children: [first.departure.iataCode, " \u2192 ", last.arrival.iataCode] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [formatTime(first.departure.at), " \u2013 ", formatTime(last.arrival.at)] }), _jsxs("span", { className: "ml-auto text-[11px] text-muted-foreground", children: [formatStops(stops, messages), totalDuration && ` · ${formatDuration(totalDuration)}`] })] })] }));
30
33
  }
31
- return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [(label || sublabel) && (_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [label && (_jsx("h4", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })), (sublabel || totalDuration) && (_jsxs("span", { className: "text-[11px] text-muted-foreground", children: [sublabel, sublabel && totalDuration && " · ", totalDuration && `Total ${formatDuration(totalDuration)}`] }))] })), _jsx("div", { className: "flex flex-col", children: segs.map((seg, idx) => (_jsx(SegmentBlock, { segment: seg, carrierName: carrierName, airportName: airportName, aircraftName: aircraftName, layoverBefore: idx > 0 ? layoverBetween(segs[idx - 1], seg) : null }, seg.segmentId))) })] }));
34
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [(label || sublabel) && (_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [label && (_jsx("h4", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })), (sublabel || totalDuration) && (_jsxs("span", { className: "text-[11px] text-muted-foreground", children: [sublabel, sublabel && totalDuration && " · ", totalDuration &&
35
+ formatMessage(messages.flightItinerary.totalDuration, {
36
+ duration: formatDuration(totalDuration),
37
+ })] }))] })), _jsx("div", { className: "flex flex-col", children: segs.map((seg, idx) => (_jsx(SegmentBlock, { segment: seg, carrierName: carrierName, airportName: airportName, aircraftName: aircraftName, messages: messages, layoverBefore: idx > 0 ? layoverBetween(segs[idx - 1], seg) : null }, seg.segmentId))) })] }));
32
38
  }
33
39
  // ─────────────────────────────────────────────────────────────────────────────
34
- function SegmentBlock({ segment, carrierName, airportName, aircraftName, layoverBefore, }) {
40
+ function SegmentBlock({ segment, carrierName, airportName, aircraftName, messages, layoverBefore, }) {
35
41
  const isCodeshare = segment.operatingCarrierCode != null && segment.operatingCarrierCode !== segment.carrierCode;
36
- return (_jsxs(_Fragment, { children: [layoverBefore && (_jsxs("div", { className: "my-1 flex items-center gap-2 px-2 text-xs text-muted-foreground", children: [_jsx("div", { className: "h-px flex-1 bg-border" }), _jsxs("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[11px]", children: ["Layover \u00B7 ", layoverBefore.dwell, " in ", layoverBefore.airport] }), _jsx("div", { className: "h-px flex-1 bg-border" })] })), _jsxs("div", { className: "rounded-lg border bg-card p-3", children: [_jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2", children: [_jsx(AirlineLogo, { iataCode: segment.carrierCode, name: carrierName?.(segment.carrierCode), size: 24 }), _jsx("span", { className: "font-medium text-sm", children: carrierName?.(segment.carrierCode) ?? segment.carrierCode }), _jsxs("span", { className: "font-mono text-muted-foreground text-xs", children: [segment.carrierCode, segment.flightNumber] }), isCodeshare && (_jsxs(Badge, { variant: "secondary", className: "text-[10px] uppercase tracking-wide", children: ["Operated by", " ", carrierName?.(segment.operatingCarrierCode ?? "") ?? segment.operatingCarrierCode] })), _jsx(Badge, { variant: "outline", className: "ml-auto capitalize", children: segment.cabin.replace("_", " ") })] }), _jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr] items-center gap-3", children: [_jsx(Endpoint, { at: segment.departure.at, iata: segment.departure.iataCode, terminal: segment.departure.terminal, airportName: airportName?.(segment.departure.iataCode) }), _jsxs("div", { className: "flex flex-col items-center gap-1 text-muted-foreground text-xs", children: [_jsx(Plane, { className: "h-3.5 w-3.5" }), segment.duration && _jsx("span", { children: formatDuration(segment.duration) })] }), _jsx(Endpoint, { at: segment.arrival.at, iata: segment.arrival.iataCode, terminal: segment.arrival.terminal, airportName: airportName?.(segment.arrival.iataCode), align: "end" })] }), segment.aircraft && (_jsxs("div", { className: "mt-2 text-[11px] text-muted-foreground", children: ["Aircraft:", " ", _jsx("span", { className: "text-foreground", children: aircraftName?.(segment.aircraft) ?? segment.aircraft })] }))] })] }));
42
+ return (_jsxs(_Fragment, { children: [layoverBefore && (_jsxs("div", { className: "my-1 flex items-center gap-2 px-2 text-xs text-muted-foreground", children: [_jsx("div", { className: "h-px flex-1 bg-border" }), _jsxs("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[11px]", children: [messages.flightItinerary.layover, " \u00B7", " ", formatMessage(messages.flightItinerary.layoverIn, {
43
+ duration: layoverBefore.dwell,
44
+ airport: layoverBefore.airport,
45
+ })] }), _jsx("div", { className: "h-px flex-1 bg-border" })] })), _jsxs("div", { className: "rounded-lg border bg-card p-3", children: [_jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2", children: [_jsx(AirlineLogo, { iataCode: segment.carrierCode, name: carrierName?.(segment.carrierCode), size: 24 }), _jsx("span", { className: "font-medium text-sm", children: carrierName?.(segment.carrierCode) ?? segment.carrierCode }), _jsxs("span", { className: "font-mono text-muted-foreground text-xs", children: [segment.carrierCode, segment.flightNumber] }), isCodeshare && (_jsx(Badge, { variant: "secondary", className: "text-[10px] uppercase tracking-wide", children: formatMessage(messages.flightItinerary.operatedBy, {
46
+ carrier: carrierName?.(segment.operatingCarrierCode ?? "") ??
47
+ segment.operatingCarrierCode ??
48
+ "",
49
+ }) })), _jsx(Badge, { variant: "outline", className: "ml-auto capitalize", children: messages.common.cabinLabels[segment.cabin] })] }), _jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr] items-center gap-3", children: [_jsx(Endpoint, { at: segment.departure.at, iata: segment.departure.iataCode, terminal: segment.departure.terminal, airportName: airportName?.(segment.departure.iataCode), messages: messages }), _jsxs("div", { className: "flex flex-col items-center gap-1 text-muted-foreground text-xs", children: [_jsx(Plane, { className: "h-3.5 w-3.5" }), segment.duration && _jsx("span", { children: formatDuration(segment.duration) })] }), _jsx(Endpoint, { at: segment.arrival.at, iata: segment.arrival.iataCode, terminal: segment.arrival.terminal, airportName: airportName?.(segment.arrival.iataCode), align: "end", messages: messages })] }), segment.aircraft && (_jsxs("div", { className: "mt-2 text-[11px] text-muted-foreground", children: [messages.flightItinerary.aircraft, " ", _jsx("span", { className: "text-foreground", children: aircraftName?.(segment.aircraft) ?? segment.aircraft })] }))] })] }));
37
50
  }
38
- function Endpoint({ at, iata, terminal, airportName, align = "start", }) {
39
- return (_jsxs("div", { className: cn("flex flex-col leading-tight", align === "end" ? "items-end" : "items-start"), children: [_jsx("span", { className: "font-semibold text-lg tabular-nums", children: formatTime(at) }), _jsx("span", { className: "font-mono text-muted-foreground text-xs", children: iata }), airportName && _jsx("span", { className: "text-[11px] text-muted-foreground", children: airportName }), terminal && _jsxs("span", { className: "text-[10px] text-muted-foreground", children: ["Terminal ", terminal] }), _jsx("span", { className: "mt-0.5 text-[10px] text-muted-foreground", children: formatDate(at) })] }));
51
+ function Endpoint({ at, iata, terminal, airportName, messages, align = "start", }) {
52
+ return (_jsxs("div", { className: cn("flex flex-col leading-tight", align === "end" ? "items-end" : "items-start"), children: [_jsx("span", { className: "font-semibold text-lg tabular-nums", children: formatTime(at) }), _jsx("span", { className: "font-mono text-muted-foreground text-xs", children: iata }), airportName && _jsx("span", { className: "text-[11px] text-muted-foreground", children: airportName }), terminal && (_jsx("span", { className: "text-[10px] text-muted-foreground", children: formatMessage(messages.flightItinerary.terminal, { terminal }) })), _jsx("span", { className: "mt-0.5 text-[10px] text-muted-foreground", children: formatDate(at) })] }));
53
+ }
54
+ function formatStops(stops, messages) {
55
+ if (stops === 0)
56
+ return messages.common.stops.nonstop;
57
+ return formatMessage(stops === 1 ? messages.common.stops.oneStop : messages.common.stops.manyStops, {
58
+ count: stops,
59
+ });
40
60
  }
41
61
  // ── Formatters ───────────────────────────────────────────────────────────────
42
62
  function formatTime(iso) {
@@ -1 +1 @@
1
- {"version":3,"file":"flight-offer-detail.d.ts","sourceRoot":"","sources":["../../src/components/flight-offer-detail.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOlF,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,WAAW,CAAA;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,kEAAkE;IAClE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,GACV,EAAE,sBAAsB,2CAwDxB"}
1
+ {"version":3,"file":"flight-offer-detail.d.ts","sourceRoot":"","sources":["../../src/components/flight-offer-detail.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAQlF,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,WAAW,CAAA;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,kEAAkE;IAClE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,GACV,EAAE,sBAAsB,2CA0ExB"}
@@ -1,8 +1,10 @@
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 { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { Separator } from "@voyantjs/ui/components/separator";
5
6
  import { cn } from "@voyantjs/ui/lib/utils";
7
+ import { useFlightsUiI18nOrDefault } from "../i18n/index.js";
6
8
  import { FlightItinerary } from "./flight-itinerary.js";
7
9
  /**
8
10
  * Full-fidelity flight offer view for the detail sheet. Composes the shared
@@ -11,51 +13,37 @@ import { FlightItinerary } from "./flight-itinerary.js";
11
13
  * itinerary component itself.
12
14
  */
13
15
  export function FlightOfferDetail({ offer, carrierName, airportName, aircraftName, itineraryLabels, className, }) {
16
+ const i18n = useFlightsUiI18nOrDefault();
17
+ const messages = i18n.messages;
14
18
  return (_jsxs("div", { className: cn("flex flex-col gap-6", className), children: [_jsx("section", { className: "flex flex-col gap-5", children: offer.itineraries.map((itin, i) => (_jsx(FlightItinerary
15
19
  // biome-ignore lint/suspicious/noArrayIndexKey: itineraries are positional (outbound/return)
16
- , { itinerary: itin, label: itineraryLabels?.[i] ?? defaultItineraryLabel(i, offer.itineraries.length), carrierName: carrierName, airportName: airportName, aircraftName: aircraftName }, i))) }), _jsxs("section", { className: "flex flex-col gap-2", children: [_jsx("h4", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: "Fare breakdown" }), _jsxs("div", { className: "overflow-hidden rounded-lg border", children: [offer.fareBreakdowns.map((b, i) => (_jsx(FareRow, { breakdown: b }, i))), _jsx(Separator, {}), _jsxs("div", { className: "flex items-center justify-between bg-muted/30 px-4 py-3", children: [_jsx("span", { className: "font-medium text-sm", children: "Total" }), _jsx("span", { className: "font-semibold text-base tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency) })] })] })] }), (offer.validatingCarrier ||
20
+ , { itinerary: itin, label: itineraryLabels?.[i] ?? defaultItineraryLabel(i, offer.itineraries.length, messages), carrierName: carrierName, airportName: airportName, aircraftName: aircraftName }, i))) }), _jsxs("section", { className: "flex flex-col gap-2", children: [_jsx("h4", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: messages.flightOfferDetail.fareBreakdown }), _jsxs("div", { className: "overflow-hidden rounded-lg border", children: [offer.fareBreakdowns.map((b, i) => (_jsx(FareRow, { breakdown: b, i18n: i18n }, i))), _jsx(Separator, {}), _jsxs("div", { className: "flex items-center justify-between bg-muted/30 px-4 py-3", children: [_jsx("span", { className: "font-medium text-sm", children: messages.common.total }), _jsx("span", { className: "font-semibold text-base tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency, i18n) })] })] })] }), (offer.validatingCarrier ||
17
21
  offer.expiresAt ||
18
22
  offer.lastTicketingDate ||
19
- offer.instantTicketing) && (_jsxs("section", { className: "flex flex-wrap items-center gap-2 text-muted-foreground text-xs", children: [offer.validatingCarrier && (_jsxs("span", { children: ["Validating carrier:", " ", _jsx("span", { className: "font-mono text-foreground", children: offer.validatingCarrier })] })), offer.expiresAt && _jsxs("span", { children: ["\u00B7 Expires ", formatDateTime(offer.expiresAt)] }), offer.lastTicketingDate && (_jsxs("span", { children: ["\u00B7 Last ticketing ", formatDate(offer.lastTicketingDate)] })), offer.instantTicketing && _jsx(Badge, { variant: "secondary", children: "Instant ticketing" })] }))] }));
23
+ offer.instantTicketing) && (_jsxs("section", { className: "flex flex-wrap items-center gap-2 text-muted-foreground text-xs", children: [offer.validatingCarrier && (_jsxs("span", { children: [messages.flightOfferDetail.validatingCarrier, " ", _jsx("span", { className: "font-mono text-foreground", children: offer.validatingCarrier })] })), offer.expiresAt && (_jsxs("span", { children: ["\u00B7", " ", formatMessage(messages.flightOfferDetail.expires, {
24
+ date: i18n.formatDateTime(offer.expiresAt),
25
+ })] })), offer.lastTicketingDate && (_jsxs("span", { children: ["\u00B7", " ", formatMessage(messages.flightOfferDetail.lastTicketing, {
26
+ date: i18n.formatDate(offer.lastTicketingDate),
27
+ })] })), offer.instantTicketing && (_jsx(Badge, { variant: "secondary", children: messages.flightOfferDetail.instantTicketing }))] }))] }));
20
28
  }
21
- function defaultItineraryLabel(idx, total) {
29
+ function defaultItineraryLabel(idx, total, messages) {
22
30
  if (total === 1)
23
- return "Itinerary";
31
+ return messages.common.legLabels.itinerary;
24
32
  if (total === 2)
25
- return idx === 0 ? "Outbound" : "Return";
26
- return `Leg ${idx + 1}`;
33
+ return idx === 0 ? messages.common.legLabels.outbound : messages.common.legLabels.return;
34
+ return formatMessage(messages.common.legLabels.leg, { number: idx + 1 });
27
35
  }
28
- function FareRow({ breakdown }) {
29
- return (_jsxs("div", { className: "flex items-center justify-between gap-4 px-4 py-2.5 text-sm", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsxs("span", { className: "capitalize", children: [breakdown.passengerCount, "\u00D7 ", breakdown.passengerType] }), breakdown.fareFamily && (_jsx("span", { className: "text-muted-foreground text-xs", children: breakdown.fareFamily }))] }), _jsxs("div", { className: "flex items-center gap-4 text-muted-foreground text-xs tabular-nums", children: [_jsxs("span", { children: ["Base ", formatMoney(breakdown.baseFare.amount, breakdown.baseFare.currency)] }), _jsxs("span", { children: ["Tax ", formatMoney(breakdown.taxes.amount, breakdown.taxes.currency)] }), _jsx("span", { className: "font-medium text-foreground text-sm", children: formatMoney(breakdown.total.amount, breakdown.total.currency) })] })] }));
36
+ function FareRow({ breakdown, i18n, }) {
37
+ const messages = i18n.messages;
38
+ return (_jsxs("div", { className: "flex items-center justify-between gap-4 px-4 py-2.5 text-sm", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsxs("span", { className: "capitalize", children: [breakdown.passengerCount, "\u00D7 ", messages.common.passengerTypeLabels[breakdown.passengerType]] }), breakdown.fareFamily && (_jsx("span", { className: "text-muted-foreground text-xs", children: breakdown.fareFamily }))] }), _jsxs("div", { className: "flex items-center gap-4 text-muted-foreground text-xs tabular-nums", children: [_jsx("span", { children: formatMessage(messages.flightOfferDetail.base, {
39
+ amount: formatMoney(breakdown.baseFare.amount, breakdown.baseFare.currency, i18n),
40
+ }) }), _jsx("span", { children: formatMessage(messages.flightOfferDetail.tax, {
41
+ amount: formatMoney(breakdown.taxes.amount, breakdown.taxes.currency, i18n),
42
+ }) }), _jsx("span", { className: "font-medium text-foreground text-sm", children: formatMoney(breakdown.total.amount, breakdown.total.currency, i18n) })] })] }));
30
43
  }
31
- function formatMoney(amount, currency) {
44
+ function formatMoney(amount, currency, i18n) {
32
45
  const n = Number(amount);
33
46
  if (!Number.isFinite(n))
34
47
  return `${amount} ${currency}`;
35
- return new Intl.NumberFormat(undefined, {
36
- style: "currency",
37
- currency,
38
- maximumFractionDigits: 0,
39
- }).format(n);
40
- }
41
- function formatDate(iso) {
42
- const d = new Date(iso);
43
- if (Number.isNaN(d.getTime()))
44
- return iso;
45
- return new Intl.DateTimeFormat(undefined, {
46
- weekday: "short",
47
- day: "numeric",
48
- month: "short",
49
- }).format(d);
50
- }
51
- function formatDateTime(iso) {
52
- const d = new Date(iso);
53
- if (Number.isNaN(d.getTime()))
54
- return iso;
55
- return new Intl.DateTimeFormat(undefined, {
56
- day: "numeric",
57
- month: "short",
58
- hour: "2-digit",
59
- minute: "2-digit",
60
- }).format(d);
48
+ return i18n.formatCurrency(n, currency, { maximumFractionDigits: 0 });
61
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flight-offer-row.d.ts","sourceRoot":"","sources":["../../src/components/flight-offer-row.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,kCAAkC,CAAA;AAO9E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAA;IAClB,wDAAwD;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAsB,EACtB,WAAW,EACX,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,2CAwCrB"}
1
+ {"version":3,"file":"flight-offer-row.d.ts","sourceRoot":"","sources":["../../src/components/flight-offer-row.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,kCAAkC,CAAA;AAQ9E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAA;IAClB,wDAAwD;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,WAAW,EACX,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,2CA0CrB"}
@@ -1,8 +1,10 @@
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 { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { cn } from "@voyantjs/ui/lib/utils";
5
6
  import { Plane } from "lucide-react";
7
+ import { useFlightsUiI18nOrDefault } from "../i18n/index.js";
6
8
  import { AirlineLogo } from "./airline-logo.js";
7
9
  /**
8
10
  * One row in the search-results list. Lays out each itinerary on its own
@@ -12,23 +14,26 @@ import { AirlineLogo } from "./airline-logo.js";
12
14
  * For per-leg searches, each offer carries one itinerary; for combined
13
15
  * round-trip searches it carries two. The renderer handles both shapes.
14
16
  */
15
- export function FlightOfferRow({ offer, onClick, onSelect, selectLabel = "Select", carrierName, selected, className, }) {
17
+ export function FlightOfferRow({ offer, onClick, onSelect, selectLabel, carrierName, selected, className, }) {
18
+ const i18n = useFlightsUiI18nOrDefault();
19
+ const messages = i18n.messages;
16
20
  const interactive = !!onClick;
17
21
  const Container = interactive ? "button" : "div";
18
- return (_jsxs(Container, { type: interactive ? "button" : undefined, onClick: onClick ? () => onClick(offer) : undefined, className: cn("flex w-full items-stretch gap-4 rounded-lg border bg-card p-4 text-left shadow-sm transition-colors", interactive && "hover:border-primary/40 hover:bg-accent/30", selected && "border-primary ring-1 ring-primary/40", className), children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: offer.itineraries.map((itin, i) => (_jsx(ItineraryRow, { itinerary: itin, carrierName: carrierName }, i))) }), _jsxs("div", { className: "flex shrink-0 flex-col items-end justify-center gap-2 border-l pl-4", children: [_jsx("div", { className: "font-semibold text-2xl tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency) }), _jsx(PriceFootnote, { offer: offer }), onSelect && (_jsx("button", { type: "button", onClick: (e) => {
22
+ return (_jsxs(Container, { type: interactive ? "button" : undefined, onClick: onClick ? () => onClick(offer) : undefined, className: cn("flex w-full items-stretch gap-4 rounded-lg border bg-card p-4 text-left shadow-sm transition-colors", interactive && "hover:border-primary/40 hover:bg-accent/30", selected && "border-primary ring-1 ring-primary/40", className), children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: offer.itineraries.map((itin, i) => (_jsx(ItineraryRow, { itinerary: itin, carrierName: carrierName, messages: messages }, i))) }), _jsxs("div", { className: "flex shrink-0 flex-col items-end justify-center gap-2 border-l pl-4", children: [_jsx("div", { className: "font-semibold text-2xl tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency, i18n) }), _jsx(PriceFootnote, { offer: offer, i18n: i18n }), onSelect && (_jsx("button", { type: "button", onClick: (e) => {
19
23
  e.stopPropagation();
20
24
  onSelect(offer);
21
- }, className: "mt-1 inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 font-medium text-primary-foreground text-xs hover:bg-primary/90", children: selectLabel }))] })] }));
25
+ }, className: "mt-1 inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 font-medium text-primary-foreground text-xs hover:bg-primary/90", children: selectLabel ?? messages.flightOfferRow.select }))] })] }));
22
26
  }
23
- function PriceFootnote({ offer }) {
27
+ function PriceFootnote({ offer, i18n, }) {
28
+ const messages = i18n.messages;
24
29
  const totalPax = offer.fareBreakdowns.reduce((n, b) => n + b.passengerCount, 0);
25
30
  const adult = offer.fareBreakdowns.find((b) => b.passengerType === "adult");
26
31
  if (totalPax <= 1) {
27
- return _jsx("div", { className: "text-muted-foreground text-xs", children: "total" });
32
+ return _jsx("div", { className: "text-muted-foreground text-xs", children: messages.common.total });
28
33
  }
29
- return (_jsxs("div", { className: "flex flex-col items-end gap-0.5 text-muted-foreground text-xs", children: [_jsxs("span", { children: ["total \u00B7 ", totalPax, " pax"] }), adult && (_jsxs("span", { children: [formatMoney(adult.total.amount, adult.total.currency), _jsx("span", { className: "ml-0.5 text-muted-foreground/70", children: "/adult" })] }))] }));
34
+ return (_jsxs("div", { className: "flex flex-col items-end gap-0.5 text-muted-foreground text-xs", children: [_jsxs("span", { children: [messages.common.total, " \u00B7 ", totalPax, " ", messages.common.pax] }), adult && (_jsxs("span", { children: [formatMoney(adult.total.amount, adult.total.currency, i18n), _jsx("span", { className: "ml-0.5 text-muted-foreground/70", children: messages.common.adultPerPassenger })] }))] }));
30
35
  }
31
- function ItineraryRow({ itinerary, carrierName, }) {
36
+ function ItineraryRow({ itinerary, carrierName, messages, }) {
32
37
  const segs = itinerary.segments;
33
38
  const first = segs[0];
34
39
  const last = segs[segs.length - 1];
@@ -38,23 +43,22 @@ function ItineraryRow({ itinerary, carrierName, }) {
38
43
  const stops = segs.length - 1;
39
44
  const hasCodeshare = segs.some((s) => s.operatingCarrierCode != null && s.operatingCarrierCode !== s.carrierCode);
40
45
  const hasInterline = carriers.length > 1;
41
- return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex shrink-0 items-center -space-x-1.5", children: carriers.map((code) => (_jsx(AirlineLogo, { iataCode: code, name: carrierName?.(code), size: 28 }, code))) }), _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [_jsx(Endpoint, { at: first.departure.at, iata: first.departure.iataCode }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-center gap-1", children: [_jsx("div", { className: "text-[11px] text-muted-foreground", children: formatDuration(itinerary.duration) }), _jsxs("div", { className: "flex w-full items-center gap-1.5", children: [_jsx("div", { className: "h-px flex-1 bg-border" }), _jsx(Plane, { className: "h-3 w-3 text-muted-foreground" }), _jsx("div", { className: "h-px flex-1 bg-border" })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [stops === 0 ? (_jsx("span", { className: "font-medium text-emerald-600", children: "Nonstop" })) : (_jsxs("span", { children: [stops, " stop", stops > 1 ? "s" : "", " via", " ", segs
46
+ return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex shrink-0 items-center -space-x-1.5", children: carriers.map((code) => (_jsx(AirlineLogo, { iataCode: code, name: carrierName?.(code), size: 28 }, code))) }), _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [_jsx(Endpoint, { at: first.departure.at, iata: first.departure.iataCode }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-center gap-1", children: [_jsx("div", { className: "text-[11px] text-muted-foreground", children: formatDuration(itinerary.duration) }), _jsxs("div", { className: "flex w-full items-center gap-1.5", children: [_jsx("div", { className: "h-px flex-1 bg-border" }), _jsx(Plane, { className: "h-3 w-3 text-muted-foreground" }), _jsx("div", { className: "h-px flex-1 bg-border" })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [stops === 0 ? (_jsx("span", { className: "font-medium text-emerald-600", children: messages.common.stops.nonstop })) : (_jsx("span", { children: formatMessage(messages.common.stops.via, {
47
+ stops: formatMessage(stops === 1 ? messages.common.stops.oneStop : messages.common.stops.manyStops, { count: stops }),
48
+ airports: segs
42
49
  .slice(0, -1)
43
50
  .map((s) => s.arrival.iataCode)
44
- .join(", ")] })), hasInterline && (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[9px]", children: "Interline" })), hasCodeshare && (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[9px]", children: "Codeshare" }))] })] }), _jsx(Endpoint, { at: last.arrival.at, iata: last.arrival.iataCode, align: "end" })] }), _jsx(Badge, { variant: "outline", className: "shrink-0 capitalize", children: first.cabin.replace("_", " ") })] }));
51
+ .join(", "),
52
+ }) })), hasInterline && (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[9px]", children: messages.flightOfferRow.interline })), hasCodeshare && (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[9px]", children: messages.flightOfferRow.codeshare }))] })] }), _jsx(Endpoint, { at: last.arrival.at, iata: last.arrival.iataCode, align: "end" })] }), _jsx(Badge, { variant: "outline", className: "shrink-0 capitalize", children: messages.common.cabinLabels[first.cabin] })] }));
45
53
  }
46
54
  function Endpoint({ at, iata, align = "start", }) {
47
55
  return (_jsxs("div", { className: cn("flex shrink-0 flex-col leading-tight", align === "end" ? "items-end" : "items-start"), children: [_jsx("span", { className: "font-semibold text-base tabular-nums", children: formatTime(at) }), _jsx("span", { className: "font-mono text-muted-foreground text-xs", children: iata })] }));
48
56
  }
49
- function formatMoney(amount, currency) {
57
+ function formatMoney(amount, currency, i18n) {
50
58
  const n = Number(amount);
51
59
  if (!Number.isFinite(n))
52
60
  return `${amount} ${currency}`;
53
- return new Intl.NumberFormat(undefined, {
54
- style: "currency",
55
- currency,
56
- maximumFractionDigits: 0,
57
- }).format(n);
61
+ return i18n.formatCurrency(n, currency, { maximumFractionDigits: 0 });
58
62
  }
59
63
  function formatTime(iso) {
60
64
  const d = new Date(iso);
@@ -1 +1 @@
1
- {"version":3,"file":"flight-order-confirmation.d.ts","sourceRoot":"","sources":["../../src/components/flight-order-confirmation.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAUnE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,WAAW,CAAA;IAClB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,+EAA+E;IAC/E,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;CACxD;AAaD,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,GACb,EAAE,4BAA4B,2CAwH9B"}
1
+ {"version":3,"file":"flight-order-confirmation.d.ts","sourceRoot":"","sources":["../../src/components/flight-order-confirmation.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAWnE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,WAAW,CAAA;IAClB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,+EAA+E;IAC/E,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;CACxD;AAaD,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,GACb,EAAE,4BAA4B,2CAgI9B"}
@@ -1,50 +1,46 @@
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 { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { Button } from "@voyantjs/ui/components/button";
5
6
  import { Separator } from "@voyantjs/ui/components/separator";
6
7
  import { cn } from "@voyantjs/ui/lib/utils";
7
8
  import { CheckCircle2, Clock, Mail, Phone, Ticket, XCircle } from "lucide-react";
9
+ import { useFlightsUiI18nOrDefault } from "../i18n/index.js";
8
10
  import { FlightOfferDetail } from "./flight-offer-detail.js";
9
11
  const STATUS_VARIANTS = {
10
- pending: { label: "Pending", tone: "pending", icon: _jsx(Clock, { className: "h-4 w-4" }) },
11
- confirmed: { label: "Confirmed", tone: "ok", icon: _jsx(CheckCircle2, { className: "h-4 w-4" }) },
12
- ticketed: { label: "Ticketed", tone: "ok", icon: _jsx(Ticket, { className: "h-4 w-4" }) },
13
- cancelled: { label: "Cancelled", tone: "bad", icon: _jsx(XCircle, { className: "h-4 w-4" }) },
14
- failed: { label: "Failed", tone: "bad", icon: _jsx(XCircle, { className: "h-4 w-4" }) },
12
+ pending: { tone: "pending", icon: _jsx(Clock, { className: "h-4 w-4" }) },
13
+ confirmed: { tone: "ok", icon: _jsx(CheckCircle2, { className: "h-4 w-4" }) },
14
+ ticketed: { tone: "ok", icon: _jsx(Ticket, { className: "h-4 w-4" }) },
15
+ cancelled: { tone: "bad", icon: _jsx(XCircle, { className: "h-4 w-4" }) },
16
+ failed: { tone: "bad", icon: _jsx(XCircle, { className: "h-4 w-4" }) },
15
17
  };
16
18
  export function FlightOrderConfirmation({ order, onCancel, cancelLoading, carrierName, airportName, aircraftName, }) {
19
+ const i18n = useFlightsUiI18nOrDefault();
20
+ const messages = i18n.messages;
17
21
  const status = STATUS_VARIANTS[order.status];
18
22
  const isCancellable = onCancel != null && (order.status === "confirmed" || order.status === "ticketed");
19
- return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Booking confirmed" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h2", { className: "font-mono text-2xl font-semibold tracking-wider", children: order.pnr ?? order.orderId }), _jsxs(Badge, { variant: status.tone === "ok" ? "default" : "secondary", className: cn("gap-1.5", status.tone === "bad" && "bg-destructive/10 text-destructive"), children: [status.icon, status.label] })] }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: order.orderId })] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-2xl font-semibold tabular-nums", children: formatMoney(order.totalPrice.amount, order.totalPrice.currency) }), _jsx("div", { className: "text-xs text-muted-foreground", children: "total" })] })] }), order.paymentDeadline && order.status === "confirmed" && (_jsxs("div", { className: "mt-4 flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900", children: [_jsx(Clock, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsxs("div", { children: ["Tickets must be issued before ", _jsx("strong", { children: formatDateTime(order.paymentDeadline) }), " ", "or the seats will be released."] })] }))] }), _jsx(Section, { title: "Passengers", children: _jsx("div", { className: "flex flex-col gap-2", children: order.passengers.map((p) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border bg-card px-3 py-2.5 text-sm", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsx("span", { className: "font-medium", children: [p.firstName, p.middleName, p.lastName].filter(Boolean).join(" ") }), _jsxs("span", { className: "text-xs text-muted-foreground capitalize", children: [p.type, p.dateOfBirth && ` · DOB ${p.dateOfBirth}`] })] }), order.tickets && (_jsx(TicketChip, { number: order.tickets.find((t) => t.passengerId === p.passengerId)?.ticketNumber }))] }, p.passengerId))) }) }), (order.contact?.email || order.contact?.phone) && (_jsx(Section, { title: "Contact", children: _jsxs("div", { className: "flex flex-wrap gap-4 text-sm", children: [order.contact.email && (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), order.contact.email] })), order.contact.phone && (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), order.contact.phone] }))] }) })), _jsx(Section, { title: "Itinerary", children: _jsx(FlightOfferDetail, { offer: order.offer, carrierName: carrierName, airportName: airportName, aircraftName: aircraftName }) }), isCancellable && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { variant: "destructive", onClick: () => onCancel(order), disabled: cancelLoading, children: cancelLoading ? "Cancelling…" : "Cancel booking" }) })] }))] }));
23
+ return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: messages.flightOrderConfirmation.bookingConfirmed }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h2", { className: "font-mono text-2xl font-semibold tracking-wider", children: order.pnr ?? order.orderId }), _jsxs(Badge, { variant: status.tone === "ok" ? "default" : "secondary", className: cn("gap-1.5", status.tone === "bad" && "bg-destructive/10 text-destructive"), children: [status.icon, messages.common.orderStatusLabels[order.status]] })] }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: order.orderId })] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-2xl font-semibold tabular-nums", children: formatMoney(order.totalPrice.amount, order.totalPrice.currency, i18n) }), _jsx("div", { className: "text-xs text-muted-foreground", children: messages.common.total })] })] }), order.paymentDeadline && order.status === "confirmed" && (_jsxs("div", { className: "mt-4 flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900", children: [_jsx(Clock, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("div", { children: formatMessage(messages.flightOrderConfirmation.ticketDeadline, {
24
+ date: i18n.formatDateTime(order.paymentDeadline),
25
+ }) })] }))] }), _jsx(Section, { title: messages.flightOrderConfirmation.passengers, children: _jsx("div", { className: "flex flex-col gap-2", children: order.passengers.map((p) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border bg-card px-3 py-2.5 text-sm", children: [_jsxs("div", { className: "flex flex-col leading-tight", children: [_jsx("span", { className: "font-medium", children: [p.firstName, p.middleName, p.lastName].filter(Boolean).join(" ") }), _jsxs("span", { className: "text-xs text-muted-foreground capitalize", children: [p.type, p.dateOfBirth &&
26
+ ` · ${formatMessage(messages.flightOrderConfirmation.dob, {
27
+ date: p.dateOfBirth,
28
+ })}`] })] }), order.tickets && (_jsx(TicketChip, { number: order.tickets.find((t) => t.passengerId === p.passengerId)?.ticketNumber }))] }, p.passengerId))) }) }), (order.contact?.email || order.contact?.phone) && (_jsx(Section, { title: messages.flightOrderConfirmation.contact, children: _jsxs("div", { className: "flex flex-wrap gap-4 text-sm", children: [order.contact.email && (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), order.contact.email] })), order.contact.phone && (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), order.contact.phone] }))] }) })), _jsx(Section, { title: messages.flightOrderConfirmation.itinerary, children: _jsx(FlightOfferDetail, { offer: order.offer, carrierName: carrierName, airportName: airportName, aircraftName: aircraftName }) }), isCancellable && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { variant: "destructive", onClick: () => onCancel(order), disabled: cancelLoading, children: cancelLoading
29
+ ? messages.flightOrderConfirmation.cancelling
30
+ : messages.flightOrderConfirmation.cancelBooking }) })] }))] }));
20
31
  }
21
32
  function Section({ title, children }) {
22
33
  return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsx("h3", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: title }), children] }));
23
34
  }
24
35
  function TicketChip({ number }) {
25
36
  if (!number) {
26
- return _jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" });
37
+ return _jsx("span", { className: "text-xs text-muted-foreground", children: "-" });
27
38
  }
28
39
  return _jsx("code", { className: "rounded bg-muted px-2 py-1 font-mono text-[11px]", children: number });
29
40
  }
30
- function formatMoney(amount, currency) {
41
+ function formatMoney(amount, currency, i18n) {
31
42
  const n = Number(amount);
32
43
  if (!Number.isFinite(n))
33
44
  return `${amount} ${currency}`;
34
- return new Intl.NumberFormat(undefined, {
35
- style: "currency",
36
- currency,
37
- maximumFractionDigits: 0,
38
- }).format(n);
39
- }
40
- function formatDateTime(iso) {
41
- const d = new Date(iso);
42
- if (Number.isNaN(d.getTime()))
43
- return iso;
44
- return new Intl.DateTimeFormat(undefined, {
45
- day: "numeric",
46
- month: "short",
47
- hour: "2-digit",
48
- minute: "2-digit",
49
- }).format(d);
45
+ return i18n.formatCurrency(n, currency, { maximumFractionDigits: 0 });
50
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flight-passenger-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-passenger-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,aAAa,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAA;AAE1D,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CACpC,IAAI,CACF,eAAe,EACf,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CACvF,CACF,CAAA;AAcD,MAAM,WAAW,wBAAwB;IACvC,2DAA2D;IAC3D,MAAM,EAAE,eAAe,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CACb,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,EAClD,QAAQ,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,KAC1C,SAAS,CAAA;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,KAAK,EACL,QAAQ,EACR,iBAAiB,EACjB,YAAY,GACb,EAAE,wBAAwB,2CAmD1B;AAkPD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBnF"}
1
+ {"version":3,"file":"flight-passenger-form.d.ts","sourceRoot":"","sources":["../../src/components/flight-passenger-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,aAAa,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAA;AAI1D,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CACpC,IAAI,CACF,eAAe,EACf,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CACvF,CACF,CAAA;AAcD,MAAM,WAAW,wBAAwB;IACvC,2DAA2D;IAC3D,MAAM,EAAE,eAAe,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CACb,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,EAClD,QAAQ,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,KAC1C,SAAS,CAAA;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,KAAK,EACL,QAAQ,EACR,iBAAiB,EACjB,YAAY,GACb,EAAE,wBAAwB,2CAkD1B;AA2PD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBnF"}
@@ -8,6 +8,8 @@ import { Label } from "@voyantjs/ui/components/label";
8
8
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components/select";
9
9
  import { CircleAlert, IdCard } from "lucide-react";
10
10
  import { useEffect, useMemo } from "react";
11
+ import { flightsUiEn } from "../i18n/en.js";
12
+ import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
11
13
  // Date-of-birth range — anyone born within the last 110 years is plausible;
12
14
  // future dates make no sense. Computed once at module load.
13
15
  const DOB_END_MONTH = new Date();
@@ -31,6 +33,7 @@ const EXPIRY_END_MONTH = new Date(EXPIRY_START_MONTH.getFullYear() + 30, 11, 31)
31
33
  * straight through `bookFlight`.
32
34
  */
33
35
  export function FlightPassengerForm({ counts, value, onChange, documentsRequired, renderPicker, }) {
36
+ const messages = useFlightsUiMessagesOrDefault();
34
37
  // Derive the canonical pax slots from `counts`. Passenger ids are stable
35
38
  // by position so re-ordering is safe.
36
39
  const slots = useMemo(() => buildSlots(counts), [counts]);
@@ -49,11 +52,11 @@ export function FlightPassengerForm({ counts, value, onChange, documentsRequired
49
52
  const set = (passengerId, patch) => {
50
53
  onChange(value.map((p) => (p.passengerId === passengerId ? { ...p, ...patch } : p)));
51
54
  };
52
- return (_jsxs("div", { className: "flex flex-col gap-4", children: [documentsRequired && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/5 p-3 text-amber-700 text-sm", children: [_jsx(CircleAlert, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: "This route looks international \u2014 adding travel documents now speeds up online check-in and avoids airport-counter fees. You can still skip and add them later." })] })), slots.map((slot, i) => {
55
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [documentsRequired && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/5 p-3 text-amber-700 text-sm", children: [_jsx(CircleAlert, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("span", { children: messages.flightPassengerForm.documentsRequiredNotice })] })), slots.map((slot, i) => {
53
56
  const pax = value.find((p) => p.passengerId === slot.passengerId) ??
54
57
  blankPassenger(slot.passengerId, slot.type);
55
58
  const idx = sameTypeIndex(slots, slot, i) + 1;
56
- return (_jsx(PassengerCard, { label: `${labelFor(slot.type)} ${idx}`, value: pax, onChange: (patch) => set(slot.passengerId, patch), picker: renderPicker?.(slot, (prefill) => set(slot.passengerId, stripUndefined(prefill))) }, slot.passengerId));
59
+ return (_jsx(PassengerCard, { label: `${labelFor(slot.type, messages)} ${idx}`, messages: messages, value: pax, onChange: (patch) => set(slot.passengerId, patch), picker: renderPicker?.(slot, (prefill) => set(slot.passengerId, stripUndefined(prefill))) }, slot.passengerId));
57
60
  })] }));
58
61
  }
59
62
  function stripUndefined(obj) {
@@ -64,7 +67,7 @@ function stripUndefined(obj) {
64
67
  }
65
68
  return out;
66
69
  }
67
- function PassengerCard({ label, value, onChange, picker, }) {
70
+ function PassengerCard({ label, value, onChange, picker, messages, }) {
68
71
  const doc = value.documents?.[0] ?? null;
69
72
  const setDoc = (next) => {
70
73
  onChange({ documents: next ? [next] : undefined });
@@ -72,14 +75,14 @@ function PassengerCard({ label, value, onChange, picker, }) {
72
75
  const updateDoc = (patch) => {
73
76
  setDoc({ ...(doc ?? emptyDoc()), ...patch });
74
77
  };
75
- return (_jsxs("div", { className: "rounded-lg border bg-card p-4 shadow-sm", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [_jsx("h3", { className: "font-medium text-sm", children: label }), picker] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: "First name", required: true, children: _jsx(Input, { value: value.firstName, onChange: (e) => onChange({ firstName: e.target.value }), placeholder: "As on passport" }) }), _jsx(Field, { label: "Middle name", children: _jsx(Input, { value: value.middleName ?? "", onChange: (e) => onChange({ middleName: e.target.value }), placeholder: "Optional" }) }), _jsx(Field, { label: "Last name", required: true, children: _jsx(Input, { value: value.lastName, onChange: (e) => onChange({ lastName: e.target.value }), placeholder: "As on passport" }) }), _jsx(Field, { label: "Date of birth", required: true, children: _jsx(DatePicker, { value: value.dateOfBirth || null, onChange: (v) => onChange({ dateOfBirth: v ?? "" }), placeholder: "Select date", className: "w-full", captionLayout: "dropdown", startMonth: DOB_START_MONTH, endMonth: DOB_END_MONTH, disabled: { after: DOB_END_MONTH } }) }), _jsx(Field, { label: "Gender", children: _jsxs(Select, { value: value.gender ?? "", onValueChange: (v) => {
78
+ return (_jsxs("div", { className: "rounded-lg border bg-card p-4 shadow-sm", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [_jsx("h3", { className: "font-medium text-sm", children: label }), picker] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: messages.flightPassengerForm.fields.firstName, required: true, children: _jsx(Input, { value: value.firstName, onChange: (e) => onChange({ firstName: e.target.value }), placeholder: messages.flightPassengerForm.placeholders.asOnPassport }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.middleName, children: _jsx(Input, { value: value.middleName ?? "", onChange: (e) => onChange({ middleName: e.target.value }), placeholder: messages.flightPassengerForm.placeholders.optional }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.lastName, required: true, children: _jsx(Input, { value: value.lastName, onChange: (e) => onChange({ lastName: e.target.value }), placeholder: messages.flightPassengerForm.placeholders.asOnPassport }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.dateOfBirth, required: true, children: _jsx(DatePicker, { value: value.dateOfBirth || null, onChange: (v) => onChange({ dateOfBirth: v ?? "" }), placeholder: messages.flightPassengerForm.placeholders.selectDate, className: "w-full", captionLayout: "dropdown", startMonth: DOB_START_MONTH, endMonth: DOB_END_MONTH, disabled: { after: DOB_END_MONTH } }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.gender, children: _jsxs(Select, { value: value.gender ?? "", onValueChange: (v) => {
76
79
  if (v)
77
80
  onChange({ gender: v });
78
- }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "M", children: "Male" }), _jsx(SelectItem, { value: "F", children: "Female" }), _jsx(SelectItem, { value: "X", children: "Unspecified / X" })] })] }) })] }), _jsxs("div", { className: "mt-4 border-t pt-4", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-medium text-sm", children: [_jsx(IdCard, { className: "h-3.5 w-3.5 text-muted-foreground" }), "Travel document"] }), _jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs", children: [_jsx(Checkbox, { id: `pax-${value.passengerId}-doc-toggle`, checked: doc != null, onCheckedChange: (v) => setDoc(v ? emptyDoc() : null) }), _jsx("label", { htmlFor: `pax-${value.passengerId}-doc-toggle`, className: "cursor-pointer", children: "Add now" })] })] }), !doc && (_jsx("p", { className: "text-muted-foreground text-xs", children: "Skip to add at check-in. Required up-front for most international travel." })), doc && (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: "Document type", required: true, children: _jsxs(Select, { value: doc.type, onValueChange: (v) => {
81
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: messages.flightPassengerForm.placeholders.select }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "M", children: messages.common.genderLabels.M }), _jsx(SelectItem, { value: "F", children: messages.common.genderLabels.F }), _jsx(SelectItem, { value: "X", children: messages.common.genderLabels.X })] })] }) })] }), _jsxs("div", { className: "mt-4 border-t pt-4", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-medium text-sm", children: [_jsx(IdCard, { className: "h-3.5 w-3.5 text-muted-foreground" }), messages.flightPassengerForm.fields.travelDocument] }), _jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs", children: [_jsx(Checkbox, { id: `pax-${value.passengerId}-doc-toggle`, checked: doc != null, onCheckedChange: (v) => setDoc(v ? emptyDoc() : null) }), _jsx("label", { htmlFor: `pax-${value.passengerId}-doc-toggle`, className: "cursor-pointer", children: messages.flightPassengerForm.addNow })] })] }), !doc && (_jsx("p", { className: "text-muted-foreground text-xs", children: messages.flightPassengerForm.skipDocuments })), doc && (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: messages.flightPassengerForm.fields.documentType, required: true, children: _jsxs(Select, { value: doc.type, onValueChange: (v) => {
79
82
  if (!v)
80
83
  return;
81
84
  updateDoc({ type: v });
82
- }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "passport", children: "Passport" }), _jsx(SelectItem, { value: "national_id", children: "National ID" }), _jsx(SelectItem, { value: "visa", children: "Visa" })] })] }) }), _jsx(Field, { label: "Document number", required: true, children: _jsx(Input, { value: doc.number, onChange: (e) => updateDoc({ number: e.target.value }), placeholder: "As printed on document", autoCapitalize: "characters" }) }), _jsx(Field, { label: "Country of issue", required: true, children: _jsx(CountryCombobox, { value: doc.countryOfIssue || null, onChange: (code) => updateDoc({ countryOfIssue: code ?? "" }) }) }), _jsx(Field, { label: "Country of nationality", children: _jsx(CountryCombobox, { value: doc.countryOfNationality ?? null, onChange: (code) => updateDoc({ countryOfNationality: code ?? undefined }) }) }), _jsx(Field, { label: "Expiry date", required: true, children: _jsx(DatePicker, { value: doc.expiryDate ?? null, onChange: (v) => updateDoc({ expiryDate: v ?? undefined }), placeholder: "Select date", className: "w-full", captionLayout: "dropdown", startMonth: EXPIRY_START_MONTH, endMonth: EXPIRY_END_MONTH, disabled: { before: EXPIRY_START_MONTH } }) })] }))] })] }));
85
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "passport", children: messages.common.documentTypeLabels.passport }), _jsx(SelectItem, { value: "national_id", children: messages.common.documentTypeLabels.national_id }), _jsx(SelectItem, { value: "visa", children: messages.common.documentTypeLabels.visa })] })] }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.documentNumber, required: true, children: _jsx(Input, { value: doc.number, onChange: (e) => updateDoc({ number: e.target.value }), placeholder: messages.flightPassengerForm.placeholders.asPrintedOnDocument, autoCapitalize: "characters" }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.countryOfIssue, required: true, children: _jsx(CountryCombobox, { value: doc.countryOfIssue || null, onChange: (code) => updateDoc({ countryOfIssue: code ?? "" }) }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.countryOfNationality, children: _jsx(CountryCombobox, { value: doc.countryOfNationality ?? null, onChange: (code) => updateDoc({ countryOfNationality: code ?? undefined }) }) }), _jsx(Field, { label: messages.flightPassengerForm.fields.expiryDate, required: true, children: _jsx(DatePicker, { value: doc.expiryDate ?? null, onChange: (v) => updateDoc({ expiryDate: v ?? undefined }), placeholder: messages.flightPassengerForm.placeholders.selectDate, className: "w-full", captionLayout: "dropdown", startMonth: EXPIRY_START_MONTH, endMonth: EXPIRY_END_MONTH, disabled: { before: EXPIRY_START_MONTH } }) })] }))] })] }));
83
86
  }
84
87
  function emptyDoc() {
85
88
  return {
@@ -113,8 +116,8 @@ function blankPassenger(passengerId, type) {
113
116
  dateOfBirth: "",
114
117
  };
115
118
  }
116
- function labelFor(type) {
117
- return type[0]?.toUpperCase() + type.slice(1);
119
+ function labelFor(type, messages) {
120
+ return messages.common.passengerTypeLabels[type];
118
121
  }
119
122
  function sameTypeIndex(slots, slot, idx) {
120
123
  let count = 0;
@@ -131,23 +134,24 @@ function sameTypeIndex(slots, slot, idx) {
131
134
  * the operator opted to add one its required fields must be filled.
132
135
  */
133
136
  export function validatePassengers(value) {
137
+ const messages = flightsUiEn.flightPassengerForm.validation;
134
138
  const errors = {};
135
139
  for (const p of value) {
136
140
  if (!p.firstName.trim())
137
- errors[p.passengerId] = "First name required";
141
+ errors[p.passengerId] = messages.firstNameRequired;
138
142
  else if (!p.lastName.trim())
139
- errors[p.passengerId] = "Last name required";
143
+ errors[p.passengerId] = messages.lastNameRequired;
140
144
  else if (!p.dateOfBirth)
141
- errors[p.passengerId] = "Date of birth required";
145
+ errors[p.passengerId] = messages.dateOfBirthRequired;
142
146
  else {
143
147
  const doc = p.documents?.[0];
144
148
  if (doc) {
145
149
  if (!doc.number.trim())
146
- errors[p.passengerId] = "Document number required";
150
+ errors[p.passengerId] = messages.documentNumberRequired;
147
151
  else if (!doc.countryOfIssue.trim())
148
- errors[p.passengerId] = "Document country required";
152
+ errors[p.passengerId] = messages.documentCountryRequired;
149
153
  else if (!doc.expiryDate)
150
- errors[p.passengerId] = "Document expiry required";
154
+ errors[p.passengerId] = messages.documentExpiryRequired;
151
155
  }
152
156
  }
153
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flight-payment-selector.d.ts","sourceRoot":"","sources":["../../src/components/flight-payment-selector.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAKrE,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;CACzC;AAqCD,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,0BAA0B,2CAqD/F"}
1
+ {"version":3,"file":"flight-payment-selector.d.ts","sourceRoot":"","sources":["../../src/components/flight-payment-selector.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAMrE,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;CACzC;AA0BD,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,0BAA0B,2CAqD/F"}
@@ -2,35 +2,31 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "@voyantjs/ui/lib/utils";
4
4
  import { Banknote, Clock, CreditCard } from "lucide-react";
5
+ import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
5
6
  const INTENTS = [
6
7
  {
7
8
  id: "hold",
8
- title: "Hold seats — pay later",
9
- description: "Confirms the booking now and locks in the price for the connector's hold window. Tickets issue when payment lands.",
10
9
  icon: _jsx(Clock, { className: "h-5 w-5" }),
11
10
  build: () => ({ type: "hold" }),
12
11
  },
13
12
  {
14
13
  id: "card",
15
- title: "Pay by card",
16
- description: "Tickets issue immediately. Card details handled outside this form by the connector's tokenization flow.",
17
14
  icon: _jsx(CreditCard, { className: "h-5 w-5" }),
18
15
  build: () => ({ type: "card", token: "demo_card_token" }),
19
16
  },
20
17
  {
21
18
  id: "ticket_on_credit",
22
- title: "Ticket on agency credit",
23
- description: "Issue against the operator's IATA office credit line. Settles via BSP in the next reporting cycle.",
24
19
  icon: _jsx(Banknote, { className: "h-5 w-5" }),
25
20
  build: () => ({ type: "ticket_on_credit" }),
26
21
  },
27
22
  ];
28
23
  export function FlightPaymentSelector({ value, onChange, available }) {
24
+ const messages = useFlightsUiMessagesOrDefault().flightPaymentSelector;
29
25
  const visibleIntents = available ? INTENTS.filter((i) => available.includes(i.id)) : INTENTS;
30
- return (_jsxs("div", { className: "rounded-lg border bg-card p-4 shadow-sm", children: [_jsx("h3", { className: "mb-3 font-medium text-sm", children: "Payment intent" }), _jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: "How the booking should be paid. Hold lets you confirm seats now and ticket later; card / on-credit issue tickets immediately." }), _jsx("div", { role: "radiogroup", className: "flex flex-col gap-2", children: visibleIntents.map((intent) => {
26
+ 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 }), _jsx("div", { role: "radiogroup", className: "flex flex-col gap-2", children: visibleIntents.map((intent) => {
31
27
  const isSelected = value.type === intent.id;
32
28
  return (_jsxs("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(intent.build()), className: cn("flex w-full items-start gap-3 rounded-lg border p-3 text-left transition-colors", isSelected ? "border-primary/40 bg-primary/5" : "border-border hover:bg-accent/30"), children: [_jsx("div", { className: cn("shrink-0 rounded-md border p-2", isSelected
33
29
  ? "border-primary/30 bg-primary text-primary-foreground"
34
- : "border-border bg-muted text-muted-foreground"), children: intent.icon }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: "text-sm font-medium", children: intent.title }), _jsx("span", { className: "text-xs text-muted-foreground", children: intent.description })] }), _jsx("div", { className: cn("mt-1 h-4 w-4 shrink-0 rounded-full border-2", isSelected ? "border-primary bg-primary" : "border-border"), "aria-hidden": true })] }, intent.id));
30
+ : "border-border bg-muted text-muted-foreground"), children: intent.icon }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: "text-sm font-medium", children: messages.intents[intent.id].title }), _jsx("span", { className: "text-xs text-muted-foreground", children: messages.intents[intent.id].description })] }), _jsx("div", { className: cn("mt-1 h-4 w-4 shrink-0 rounded-full border-2", isSelected ? "border-primary bg-primary" : "border-border"), "aria-hidden": true })] }, intent.id));
35
31
  }) })] }));
36
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flight-payment-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-payment-step.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAMrE,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,CAAA;AAC5D,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAA;AAEpD,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IACvC,2EAA2E;IAC3E,YAAY,EAAE,mBAAmB,EAAE,CAAA;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAA;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,YAAY,GACb,EAAE,sBAAsB,2CAoDxB"}
1
+ {"version":3,"file":"flight-payment-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-payment-step.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,uBAAuB,EAE5B,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAOrE,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,CAAA;AAC5D,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAA;AAEpD,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IACvC,2EAA2E;IAC3E,YAAY,EAAE,mBAAmB,EAAE,CAAA;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAA;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,YAAY,GACb,EAAE,sBAAsB,2CAkExB"}