@voyantjs/flights-ui 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/airport-combobox.d.ts.map +1 -1
- package/dist/components/airport-combobox.js +4 -2
- package/dist/components/flight-baggage-step.d.ts.map +1 -1
- package/dist/components/flight-baggage-step.js +29 -18
- package/dist/components/flight-billing-step.d.ts.map +1 -1
- package/dist/components/flight-billing-step.js +30 -24
- package/dist/components/flight-booking-journey.d.ts.map +1 -1
- package/dist/components/flight-booking-journey.js +15 -26
- package/dist/components/flight-booking-ledger.d.ts.map +1 -1
- package/dist/components/flight-booking-ledger.js +22 -12
- package/dist/components/flight-booking-page.js +12 -12
- package/dist/components/flight-booking-shell.d.ts.map +1 -1
- package/dist/components/flight-booking-shell.js +54 -29
- package/dist/components/flight-contact-form.d.ts.map +1 -1
- package/dist/components/flight-contact-form.js +7 -3
- package/dist/components/flight-fare-upsell-step.d.ts.map +1 -1
- package/dist/components/flight-fare-upsell-step.js +58 -30
- package/dist/components/flight-filters-bar.d.ts.map +1 -1
- package/dist/components/flight-filters-bar.js +21 -13
- package/dist/components/flight-itinerary.d.ts.map +1 -1
- package/dist/components/flight-itinerary.js +26 -6
- package/dist/components/flight-offer-detail.d.ts.map +1 -1
- package/dist/components/flight-offer-detail.js +23 -35
- package/dist/components/flight-offer-row.d.ts.map +1 -1
- package/dist/components/flight-offer-row.js +19 -15
- package/dist/components/flight-order-confirmation.d.ts.map +1 -1
- package/dist/components/flight-order-confirmation.js +20 -24
- package/dist/components/flight-passenger-form.d.ts.map +1 -1
- package/dist/components/flight-passenger-form.js +18 -14
- package/dist/components/flight-payment-selector.d.ts.map +1 -1
- package/dist/components/flight-payment-selector.js +4 -8
- package/dist/components/flight-payment-step.d.ts.map +1 -1
- package/dist/components/flight-payment-step.js +14 -15
- package/dist/components/flight-search-form.d.ts.map +1 -1
- package/dist/components/flight-search-form.js +4 -2
- package/dist/components/flight-seat-map.d.ts.map +1 -1
- package/dist/components/flight-seat-map.js +24 -19
- package/dist/components/flight-seats-step.js +26 -24
- package/dist/components/flight-services-step.d.ts.map +1 -1
- package/dist/components/flight-services-step.js +29 -16
- package/dist/components/pax-cabin-popover.d.ts.map +1 -1
- package/dist/components/pax-cabin-popover.js +8 -11
- package/dist/components/popular-routes.d.ts +0 -5
- package/dist/components/popular-routes.d.ts.map +1 -1
- package/dist/components/popular-routes.js +25 -43
- package/dist/i18n/en.d.ts +404 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +460 -0
- package/dist/i18n/messages.d.ts +331 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +808 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +404 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +460 -0
- package/package.json +15 -15
|
@@ -1,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
|
|
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 &&
|
|
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: [
|
|
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 &&
|
|
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;
|
|
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:
|
|
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: [
|
|
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
|
|
31
|
+
return messages.common.legLabels.itinerary;
|
|
24
32
|
if (total === 2)
|
|
25
|
-
return idx === 0 ?
|
|
26
|
-
return
|
|
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
|
-
|
|
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
|
|
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;
|
|
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
|
|
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:
|
|
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: [
|
|
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:
|
|
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(", ")
|
|
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
|
|
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;
|
|
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: {
|
|
11
|
-
confirmed: {
|
|
12
|
-
ticketed: {
|
|
13
|
-
cancelled: {
|
|
14
|
-
failed: {
|
|
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:
|
|
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: "
|
|
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
|
|
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;
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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] =
|
|
141
|
+
errors[p.passengerId] = messages.firstNameRequired;
|
|
138
142
|
else if (!p.lastName.trim())
|
|
139
|
-
errors[p.passengerId] =
|
|
143
|
+
errors[p.passengerId] = messages.lastNameRequired;
|
|
140
144
|
else if (!p.dateOfBirth)
|
|
141
|
-
errors[p.passengerId] =
|
|
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] =
|
|
150
|
+
errors[p.passengerId] = messages.documentNumberRequired;
|
|
147
151
|
else if (!doc.countryOfIssue.trim())
|
|
148
|
-
errors[p.passengerId] =
|
|
152
|
+
errors[p.passengerId] = messages.documentCountryRequired;
|
|
149
153
|
else if (!doc.expiryDate)
|
|
150
|
-
errors[p.passengerId] =
|
|
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;
|
|
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:
|
|
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;
|
|
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"}
|