@voyantjs/flights-ui 0.34.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/airport-combobox.d.ts.map +1 -1
- package/dist/components/airport-combobox.js +4 -2
- package/dist/components/flight-baggage-step.d.ts.map +1 -1
- package/dist/components/flight-baggage-step.js +29 -18
- package/dist/components/flight-billing-step.d.ts.map +1 -1
- package/dist/components/flight-billing-step.js +30 -24
- package/dist/components/flight-booking-journey.d.ts.map +1 -1
- package/dist/components/flight-booking-journey.js +15 -26
- package/dist/components/flight-booking-ledger.d.ts.map +1 -1
- package/dist/components/flight-booking-ledger.js +22 -12
- package/dist/components/flight-booking-page.js +12 -12
- package/dist/components/flight-booking-shell.d.ts.map +1 -1
- package/dist/components/flight-booking-shell.js +54 -29
- package/dist/components/flight-contact-form.d.ts.map +1 -1
- package/dist/components/flight-contact-form.js +7 -3
- package/dist/components/flight-fare-upsell-step.d.ts.map +1 -1
- package/dist/components/flight-fare-upsell-step.js +58 -30
- package/dist/components/flight-filters-bar.d.ts.map +1 -1
- package/dist/components/flight-filters-bar.js +21 -13
- package/dist/components/flight-itinerary.d.ts.map +1 -1
- package/dist/components/flight-itinerary.js +26 -6
- package/dist/components/flight-offer-detail.d.ts.map +1 -1
- package/dist/components/flight-offer-detail.js +23 -35
- package/dist/components/flight-offer-row.d.ts.map +1 -1
- package/dist/components/flight-offer-row.js +19 -15
- package/dist/components/flight-order-confirmation.d.ts.map +1 -1
- package/dist/components/flight-order-confirmation.js +20 -24
- package/dist/components/flight-passenger-form.d.ts.map +1 -1
- package/dist/components/flight-passenger-form.js +18 -14
- package/dist/components/flight-payment-selector.d.ts.map +1 -1
- package/dist/components/flight-payment-selector.js +4 -8
- package/dist/components/flight-payment-step.d.ts.map +1 -1
- package/dist/components/flight-payment-step.js +14 -15
- package/dist/components/flight-search-form.d.ts.map +1 -1
- package/dist/components/flight-search-form.js +4 -2
- package/dist/components/flight-seat-map.d.ts.map +1 -1
- package/dist/components/flight-seat-map.js +24 -19
- package/dist/components/flight-seats-step.js +26 -24
- package/dist/components/flight-services-step.d.ts.map +1 -1
- package/dist/components/flight-services-step.js +29 -16
- package/dist/components/pax-cabin-popover.d.ts.map +1 -1
- package/dist/components/pax-cabin-popover.js +8 -11
- package/dist/components/popular-routes.d.ts +0 -5
- package/dist/components/popular-routes.d.ts.map +1 -1
- package/dist/components/popular-routes.js +25 -43
- package/dist/i18n/en.d.ts +404 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +460 -0
- package/dist/i18n/messages.d.ts +331 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +808 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +404 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +460 -0
- package/package.json +15 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airport-combobox.d.ts","sourceRoot":"","sources":["../../src/components/airport-combobox.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"airport-combobox.d.ts","sourceRoot":"","sources":["../../src/components/airport-combobox.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,yBAAyB,CAAA;AAgB3E,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IACnE,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE,oBAAoB,2CA2EtB"}
|
|
@@ -7,6 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components
|
|
|
7
7
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
8
8
|
import { ChevronDown, MapPin } from "lucide-react";
|
|
9
9
|
import { useState } from "react";
|
|
10
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
10
11
|
/**
|
|
11
12
|
* Single-line typeahead airport picker. Trigger reads as one of:
|
|
12
13
|
* - placeholder (no selection)
|
|
@@ -15,13 +16,14 @@ import { useState } from "react";
|
|
|
15
16
|
*
|
|
16
17
|
* Backed by `useAirportSearch` (debounced server query).
|
|
17
18
|
*/
|
|
18
|
-
export function AirportCombobox({ value, onChange, placeholder
|
|
19
|
+
export function AirportCombobox({ value, onChange, placeholder, className, disabled, }) {
|
|
20
|
+
const messages = useFlightsUiMessagesOrDefault().airportCombobox;
|
|
19
21
|
const [open, setOpen] = useState(false);
|
|
20
22
|
const [input, setInput] = useState("");
|
|
21
23
|
const search = useAirportSearch(input, { enabled: open, limit: 30 });
|
|
22
24
|
const airports = search.data?.data ?? [];
|
|
23
25
|
const selected = value ? airports.find((a) => a.iataCode === value) : null;
|
|
24
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", disabled: disabled, className: cn("h-10 justify-between gap-2 px-3", className) }), children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), value ? (_jsxs("span", { className: "truncate text-sm", children: [_jsx("span", { className: "font-mono font-medium", children: value }), selected && (_jsx("span", { className: "ml-1.5 font-normal text-muted-foreground", children: selected.city }))] })) : (_jsx("span", { className: "truncate text-sm text-muted-foreground", children: placeholder }))] }), _jsx(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground" })] }), _jsx(PopoverContent, { className: "w-[320px] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { value: input, onValueChange: setInput, placeholder:
|
|
26
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", disabled: disabled, className: cn("h-10 justify-between gap-2 px-3", className) }), children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), value ? (_jsxs("span", { className: "truncate text-sm", children: [_jsx("span", { className: "font-mono font-medium", children: value }), selected && (_jsx("span", { className: "ml-1.5 font-normal text-muted-foreground", children: selected.city }))] })) : (_jsx("span", { className: "truncate text-sm text-muted-foreground", children: placeholder ?? messages.placeholder }))] }), _jsx(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground" })] }), _jsx(PopoverContent, { className: "w-[320px] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { value: input, onValueChange: setInput, placeholder: messages.searchPlaceholder }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: search.isLoading ? messages.searching : messages.empty }), _jsx(CommandGroup, { children: airports.map((a) => (_jsxs(CommandItem, { value: `${a.iataCode} ${a.city} ${a.name}`, onSelect: () => {
|
|
25
27
|
onChange(a.iataCode, a);
|
|
26
28
|
setOpen(false);
|
|
27
29
|
setInput("");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-baggage-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-baggage-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-baggage-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-baggage-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,kCAAkC,CAAA;AASzC,KAAK,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAA;AAG9D,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,eAAe,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACxC,aAAa,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,6CAA6C;IAC7C,aAAa,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;OAGG;IACH,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,yDAAyD;IACzD,eAAe,EAAE,eAAe,CAAA;IAChC,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IACtC,gFAAgF;IAChF,qBAAqB,EAAE,OAAO,CAAA;IAC9B,6BAA6B,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,eAAe,EACf,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,KAAK,EACL,QAAQ,EACR,qBAAqB,EACrB,6BAA6B,EAC7B,OAAO,GACR,EAAE,sBAAsB,2CA0FxB"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { Checkbox } from "@voyantjs/ui/components/checkbox";
|
|
4
5
|
import { Label } from "@voyantjs/ui/components/label";
|
|
5
6
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
6
7
|
import { Briefcase, CheckCircle2, Luggage } from "lucide-react";
|
|
7
8
|
import { useMemo } from "react";
|
|
9
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
8
10
|
/**
|
|
9
11
|
* Wizz-style baggage step. Tiered grid (10/20/26/32 kg with "Recommended"
|
|
10
12
|
* highlight) per passenger per leg, plus a "skip checked bag" path. The
|
|
@@ -12,13 +14,14 @@ import { useMemo } from "react";
|
|
|
12
14
|
* leg — kept on by default per LCC convention.
|
|
13
15
|
*/
|
|
14
16
|
export function FlightBaggageStep({ outboundCatalog, returnCatalog, outboundOffer, returnOffer, passengers, passengerCounts, value, onChange, sameForBothDirections, onSameForBothDirectionsChange, loading, }) {
|
|
17
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
15
18
|
const isRoundTrip = !!returnOffer;
|
|
16
|
-
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts), [passengers, passengerCounts]);
|
|
19
|
+
const paxRows = useMemo(() => buildPassengerRows(passengers, passengerCounts, messages), [passengers, passengerCounts, messages]);
|
|
17
20
|
if (loading) {
|
|
18
21
|
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "h-8 w-64 animate-pulse rounded bg-muted/40" }), _jsx("div", { className: "h-40 animate-pulse rounded-xl bg-muted/40" }), _jsx("div", { className: "h-40 animate-pulse rounded-xl bg-muted/40" })] }));
|
|
19
22
|
}
|
|
20
23
|
if (!outboundCatalog) {
|
|
21
|
-
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children:
|
|
24
|
+
return (_jsx("div", { className: "rounded-xl border border-dashed p-6 text-center text-muted-foreground text-sm", children: messages.flightBaggageStep.unavailable }));
|
|
22
25
|
}
|
|
23
26
|
const setPick = (next, removeMatch) => {
|
|
24
27
|
const filtered = value.filter((p) => !(p.passengerId === removeMatch.passengerId && p.sliceIndex === removeMatch.sliceIndex));
|
|
@@ -32,57 +35,65 @@ export function FlightBaggageStep({ outboundCatalog, returnCatalog, outboundOffe
|
|
|
32
35
|
}
|
|
33
36
|
onChange(updated);
|
|
34
37
|
};
|
|
35
|
-
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
38
|
+
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightBaggageStep.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.flightBaggageStep.description })] }), isRoundTrip && (_jsxs("div", { className: "flex shrink-0 items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "baggage-same-for-both", checked: sameForBothDirections, onCheckedChange: (v) => onSameForBothDirectionsChange(!!v) }), _jsx("label", { htmlFor: "baggage-same-for-both", className: "cursor-pointer", children: messages.flightBaggageStep.sameForBothDirections })] }))] }), _jsx(BaggageLegSection, { legLabel: messages.common.legLabels.outbound, catalog: outboundCatalog, offer: outboundOffer, passengers: paxRows, sliceIndex: 0, value: value, messages: messages, onPick: setPick }), isRoundTrip && returnCatalog && !sameForBothDirections && (_jsx(BaggageLegSection, { legLabel: messages.common.legLabels.return, catalog: returnCatalog, offer: returnOffer ?? outboundOffer, passengers: paxRows, sliceIndex: 1, value: value, messages: messages, onPick: setPick }))] }));
|
|
36
39
|
}
|
|
37
|
-
function BaggageLegSection({ legLabel, catalog, offer, passengers, sliceIndex, value, onPick, }) {
|
|
40
|
+
function BaggageLegSection({ legLabel, catalog, offer, passengers, sliceIndex, value, onPick, messages, }) {
|
|
38
41
|
const itin = offer.itineraries[0];
|
|
39
42
|
const first = itin?.segments[0];
|
|
40
43
|
const last = itin?.segments[itin.segments.length - 1];
|
|
41
|
-
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-baseline justify-between gap-2", children: [_jsxs("h3", { className: "font-medium text-sm", children: [_jsx(Luggage, { className: "mr-1.5 inline h-3.5 w-3.5 -translate-y-px text-muted-foreground" }),
|
|
44
|
+
return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-4 flex items-baseline justify-between gap-2", children: [_jsxs("h3", { className: "font-medium text-sm", children: [_jsx(Luggage, { className: "mr-1.5 inline h-3.5 w-3.5 -translate-y-px text-muted-foreground" }), formatMessage(messages.flightBaggageStep.bags, { leg: legLabel })] }), first && last && (_jsxs("span", { className: "text-muted-foreground text-xs", children: [first.departure.iataCode, " \u2192 ", last.arrival.iataCode, " \u00B7 ", formatDate(first.departure.at)] }))] }), _jsx("div", { className: "flex flex-col gap-5", children: passengers.map((pax) => {
|
|
42
45
|
const pick = value.find((p) => p.passengerId === pax.passengerId && p.sliceIndex === sliceIndex);
|
|
43
|
-
return (_jsx(PaxBaggageRow, { pax: pax, options: catalog.baggage, selectedOptionId: pick?.optionId ?? null, onSelect: (optionId) => onPick(optionId
|
|
46
|
+
return (_jsx(PaxBaggageRow, { pax: pax, options: catalog.baggage, selectedOptionId: pick?.optionId ?? null, messages: messages, onSelect: (optionId) => onPick(optionId
|
|
44
47
|
? { passengerId: pax.passengerId, sliceIndex, optionId, quantity: 1 }
|
|
45
48
|
: null, { passengerId: pax.passengerId, sliceIndex, optionId: "" }) }, pax.passengerId));
|
|
46
49
|
}) })] }));
|
|
47
50
|
}
|
|
48
|
-
function PaxBaggageRow({ pax, options, selectedOptionId, onSelect, }) {
|
|
49
|
-
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "font-medium text-sm", children: pax.label }), selectedOptionId == null && (_jsx("span", { className: "text-[11px] text-muted-foreground", children:
|
|
51
|
+
function PaxBaggageRow({ pax, options, selectedOptionId, onSelect, messages, }) {
|
|
52
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "font-medium text-sm", children: pax.label }), selectedOptionId == null && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: messages.flightBaggageStep.noCheckedBag }))] }), _jsx("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-4", children: options.map((opt) => {
|
|
50
53
|
const isSelected = selectedOptionId === opt.id;
|
|
51
54
|
return (_jsxs("button", { type: "button", onClick: () => onSelect(isSelected ? null : opt.id), className: cn("relative flex flex-col items-center justify-center gap-1.5 rounded-lg border bg-card p-3 text-center transition-colors", isSelected
|
|
52
55
|
? "border-primary ring-2 ring-primary/20"
|
|
53
|
-
: "hover:border-primary/40 hover:bg-accent/30", opt.recommended && !isSelected && "border-primary/40"), children: [opt.recommended && (_jsx("span", { className: "-translate-y-1/2 absolute top-0 left-1/2 -translate-x-1/2 rounded-full bg-primary px-2 py-0.5 font-medium text-[9px] text-primary-foreground uppercase tracking-wider", children:
|
|
54
|
-
?
|
|
56
|
+
: "hover:border-primary/40 hover:bg-accent/30", opt.recommended && !isSelected && "border-primary/40"), children: [opt.recommended && (_jsx("span", { className: "-translate-y-1/2 absolute top-0 left-1/2 -translate-x-1/2 rounded-full bg-primary px-2 py-0.5 font-medium text-[9px] text-primary-foreground uppercase tracking-wider", children: messages.common.recommended })), isSelected && (_jsx(CheckCircle2, { className: "absolute top-2 right-2 h-3.5 w-3.5 text-primary" })), _jsx(Briefcase, { className: "h-7 w-7 text-muted-foreground" }), _jsx("span", { className: "font-semibold text-base", children: opt.weightKg ? `${opt.weightKg} kg` : opt.label }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: opt.price.amount === "0.00"
|
|
57
|
+
? messages.common.included
|
|
55
58
|
: `+${formatMoney(opt.price.amount, opt.price.currency)}` })] }, opt.id));
|
|
56
59
|
}) })] }));
|
|
57
60
|
}
|
|
58
61
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
59
|
-
function buildPassengerRows(passengers, counts) {
|
|
62
|
+
function buildPassengerRows(passengers, counts, messages) {
|
|
60
63
|
if (passengers.length > 0) {
|
|
61
64
|
return passengers.map((p) => ({
|
|
62
65
|
passengerId: p.passengerId,
|
|
63
|
-
label: nameOrFallback(p),
|
|
66
|
+
label: nameOrFallback(p, messages),
|
|
64
67
|
}));
|
|
65
68
|
}
|
|
66
69
|
// Synthesize from counts when passengers haven't been filled yet.
|
|
67
70
|
const out = [];
|
|
68
71
|
for (let i = 1; i <= counts.adults; i++) {
|
|
69
|
-
out.push({
|
|
72
|
+
out.push({
|
|
73
|
+
passengerId: `pax_adult_${i}`,
|
|
74
|
+
label: `${messages.common.passengerTypeLabels.adult} ${i}`,
|
|
75
|
+
});
|
|
70
76
|
}
|
|
71
77
|
for (let i = 1; i <= (counts.children ?? 0); i++) {
|
|
72
|
-
out.push({
|
|
78
|
+
out.push({
|
|
79
|
+
passengerId: `pax_child_${i}`,
|
|
80
|
+
label: `${messages.common.passengerTypeLabels.child} ${i}`,
|
|
81
|
+
});
|
|
73
82
|
}
|
|
74
83
|
for (let i = 1; i <= (counts.infants ?? 0); i++) {
|
|
75
|
-
out.push({
|
|
84
|
+
out.push({
|
|
85
|
+
passengerId: `pax_infant_${i}`,
|
|
86
|
+
label: `${messages.common.passengerTypeLabels.infant} ${i}`,
|
|
87
|
+
});
|
|
76
88
|
}
|
|
77
89
|
return out;
|
|
78
90
|
}
|
|
79
|
-
function nameOrFallback(p) {
|
|
91
|
+
function nameOrFallback(p, messages) {
|
|
80
92
|
const full = `${p.firstName} ${p.lastName}`.trim();
|
|
81
93
|
if (full)
|
|
82
94
|
return full;
|
|
83
95
|
const idx = p.passengerId.match(/_(\d+)$/)?.[1] ?? "1";
|
|
84
|
-
|
|
85
|
-
return `${cap} ${idx}`;
|
|
96
|
+
return `${messages.common.passengerTypeLabels[p.type]} ${idx}`;
|
|
86
97
|
}
|
|
87
98
|
function formatMoney(amount, currency) {
|
|
88
99
|
const n = Number(amount);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-billing-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-billing-step.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-billing-step.d.ts","sourceRoot":"","sources":["../../src/components/flight-billing-step.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAIhD,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAA;IACjB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IACtC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,wBAAwB,EAAE,CAAA;IAC/C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;IACnF;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;CACjF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,GAChB,EAAE,sBAAsB,2CAiExB;AAkPD,wBAAgB,iBAAiB,IAAI,YAAY,CAUhD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAe9D"}
|
|
@@ -12,6 +12,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/component
|
|
|
12
12
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
13
13
|
import { Building2, ChevronDown, User, Users } from "lucide-react";
|
|
14
14
|
import { useState } from "react";
|
|
15
|
+
import { flightsUiEn } from "../i18n/en.js";
|
|
16
|
+
import { useFlightsUiMessagesOrDefault } from "../i18n/index.js";
|
|
15
17
|
/**
|
|
16
18
|
* Two-tab billing step with Privat (personal) + Companie (business / VAT)
|
|
17
19
|
* shapes. Address fields are structured (line1/city/postal/country) so the
|
|
@@ -20,32 +22,35 @@ import { useState } from "react";
|
|
|
20
22
|
* stays decoupled from the CRM data layer.
|
|
21
23
|
*/
|
|
22
24
|
export function FlightBillingStep({ value, onChange, eligiblePassengers, renderPersonPicker, renderOrgPicker, }) {
|
|
25
|
+
const messages = useFlightsUiMessagesOrDefault();
|
|
23
26
|
const apply = (prefill) => onChange({ ...value, ...prefill });
|
|
24
27
|
const set = (patch) => onChange({ ...value, ...patch });
|
|
25
28
|
const hasPassengerOptions = (eligiblePassengers?.length ?? 0) > 0;
|
|
26
|
-
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children:
|
|
29
|
+
return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: messages.flightBillingStep.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.flightBillingStep.description })] }), _jsxs(Tabs, { value: value.mode, onValueChange: (v) => set({ mode: v ?? "personal" }), children: [_jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "personal", children: [_jsx(User, { className: "mr-1.5 h-3.5 w-3.5" }), messages.flightBillingStep.tabs.personal] }), _jsxs(TabsTrigger, { value: "company", children: [_jsx(Building2, { className: "mr-1.5 h-3.5 w-3.5" }), messages.flightBillingStep.tabs.company] })] }), _jsxs(TabsContent, { value: "personal", className: "mt-5 flex flex-col gap-4", children: [(hasPassengerOptions || renderPersonPicker) && (_jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [hasPassengerOptions && (_jsx(PassengerPickerTrigger, { passengers: eligiblePassengers ?? [], messages: messages, onPick: (p) => apply({
|
|
27
30
|
mode: "personal",
|
|
28
31
|
firstName: p.firstName,
|
|
29
32
|
...(p.middleName ? { middleName: p.middleName } : {}),
|
|
30
33
|
lastName: p.lastName,
|
|
31
|
-
}) })), renderPersonPicker?.(apply)] })), _jsx(NameRow, { value: value, onChange: set }), _jsx(ContactRow, { value: value, onChange: set }), _jsx(AddressBlock, { value: value, onChange: set }), _jsx(SaveDefaultRow, { value: value, onChange: set })] }), _jsxs(TabsContent, { value: "company", className: "mt-5 flex flex-col gap-4", children: [renderOrgPicker && _jsx("div", { className: "flex justify-end", children: renderOrgPicker(apply) }), _jsx(CompanyRow, { value: value, onChange: set }), _jsx(ContactRow, { value: value, onChange: set, workPhone: true }), _jsx(AddressBlock, { value: value, onChange: set }), _jsx(SaveDefaultRow, { value: value, onChange: set })] })] })] }));
|
|
34
|
+
}) })), renderPersonPicker?.(apply)] })), _jsx(NameRow, { value: value, onChange: set, messages: messages }), _jsx(ContactRow, { value: value, onChange: set, messages: messages }), _jsx(AddressBlock, { value: value, onChange: set, messages: messages }), _jsx(SaveDefaultRow, { value: value, onChange: set, messages: messages })] }), _jsxs(TabsContent, { value: "company", className: "mt-5 flex flex-col gap-4", children: [renderOrgPicker && _jsx("div", { className: "flex justify-end", children: renderOrgPicker(apply) }), _jsx(CompanyRow, { value: value, onChange: set, messages: messages }), _jsx(ContactRow, { value: value, onChange: set, workPhone: true, messages: messages }), _jsx(AddressBlock, { value: value, onChange: set, messages: messages }), _jsx(SaveDefaultRow, { value: value, onChange: set, messages: messages })] })] })] }));
|
|
32
35
|
}
|
|
33
36
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
-
function NameRow({ value, onChange, }) {
|
|
35
|
-
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label:
|
|
37
|
+
function NameRow({ value, onChange, messages, }) {
|
|
38
|
+
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label: messages.flightBillingStep.fields.firstName, required: true, children: _jsx(Input, { value: value.firstName, onChange: (e) => onChange({ firstName: e.target.value }) }) }), _jsx(Field, { label: messages.flightBillingStep.fields.lastName, required: true, children: _jsx(Input, { value: value.lastName, onChange: (e) => onChange({ lastName: e.target.value }) }) })] }));
|
|
36
39
|
}
|
|
37
|
-
function CompanyRow({ value, onChange, }) {
|
|
38
|
-
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label:
|
|
40
|
+
function CompanyRow({ value, onChange, messages, }) {
|
|
41
|
+
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label: messages.flightBillingStep.fields.companyName, required: true, children: _jsx(Input, { value: value.companyName ?? "", onChange: (e) => onChange({ companyName: e.target.value }) }) }), _jsx(Field, { label: messages.flightBillingStep.fields.vatNumber, required: true, children: _jsx(Input, { value: value.vatNumber ?? "", onChange: (e) => onChange({ vatNumber: e.target.value }), placeholder: messages.flightBillingStep.placeholders.vatNumber }) })] }));
|
|
39
42
|
}
|
|
40
|
-
function ContactRow({ value, onChange, workPhone, }) {
|
|
41
|
-
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label:
|
|
43
|
+
function ContactRow({ value, onChange, workPhone, messages, }) {
|
|
44
|
+
return (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [_jsx(Field, { label: messages.flightBillingStep.fields.email, required: true, children: _jsx(Input, { type: "email", value: value.email, onChange: (e) => onChange({ email: e.target.value }) }) }), _jsx(Field, { label: workPhone
|
|
45
|
+
? messages.flightBillingStep.fields.workPhone
|
|
46
|
+
: messages.flightBillingStep.fields.phone, children: _jsx(PhoneInput, { value: (value.phone ?? ""), onChange: (v) => onChange({ phone: v ? String(v) : undefined }), defaultCountry: "RO", international: true }) })] }));
|
|
42
47
|
}
|
|
43
|
-
function AddressBlock({ value, onChange, }) {
|
|
44
|
-
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Field, { label:
|
|
48
|
+
function AddressBlock({ value, onChange, messages, }) {
|
|
49
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Field, { label: messages.flightBillingStep.fields.streetAddress, required: true, children: _jsx(Input, { value: value.line1, onChange: (e) => onChange({ line1: e.target.value }), placeholder: messages.flightBillingStep.placeholders.streetAddress }) }), _jsx(Field, { label: messages.flightBillingStep.fields.addressLine2, children: _jsx(Input, { value: value.line2 ?? "", onChange: (e) => onChange({ line2: e.target.value }), placeholder: messages.flightBillingStep.placeholders.addressLine2 }) }), _jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: messages.flightBillingStep.fields.city, required: true, children: _jsx(Input, { value: value.city, onChange: (e) => onChange({ city: e.target.value }) }) }), _jsx(Field, { label: messages.flightBillingStep.fields.postalCode, children: _jsx(Input, { value: value.postalCode ?? "", onChange: (e) => onChange({ postalCode: e.target.value }) }) }), _jsx(Field, { label: messages.flightBillingStep.fields.country, required: true, children: _jsx(CountryCombobox, { value: value.countryCode || null, onChange: (code) => onChange({ countryCode: code ?? "" }) }) })] })] }));
|
|
45
50
|
}
|
|
46
|
-
function SaveDefaultRow({ value, onChange, }) {
|
|
51
|
+
function SaveDefaultRow({ value, onChange, messages, }) {
|
|
47
52
|
const id = "billing-save-default";
|
|
48
|
-
return (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-sm", children: [_jsx(Checkbox, { id: id, checked: !!value.saveAsDefault, onCheckedChange: (v) => onChange({ saveAsDefault: !!v }) }), _jsx("label", { htmlFor: id, className: "cursor-pointer", children:
|
|
53
|
+
return (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-sm", children: [_jsx(Checkbox, { id: id, checked: !!value.saveAsDefault, onCheckedChange: (v) => onChange({ saveAsDefault: !!v }) }), _jsx("label", { htmlFor: id, className: "cursor-pointer", children: messages.flightBillingStep.saveDefault })] }));
|
|
49
54
|
}
|
|
50
55
|
function Field({ label, required, children, }) {
|
|
51
56
|
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { className: cn("text-[11px] uppercase tracking-wider text-muted-foreground"), children: [label, required && _jsx("span", { className: "ml-0.5 text-destructive", children: "*" })] }), children] }));
|
|
@@ -57,16 +62,16 @@ function Field({ label, required, children, }) {
|
|
|
57
62
|
* upstream — the operator can click one to copy their first/middle/last name
|
|
58
63
|
* into the billing recipient. Self-contained: doesn't depend on CRM data.
|
|
59
64
|
*/
|
|
60
|
-
function PassengerPickerTrigger({ passengers, onPick, }) {
|
|
65
|
+
function PassengerPickerTrigger({ passengers, messages, onPick, }) {
|
|
61
66
|
const [open, setOpen] = useState(false);
|
|
62
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "gap-2" }), children: [_jsx(Users, { className: "h-3.5 w-3.5" }),
|
|
67
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "gap-2" }), children: [_jsx(Users, { className: "h-3.5 w-3.5" }), messages.flightBillingStep.pickFromPassengers, _jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })] }), _jsx(PopoverContent, { className: "w-[320px] p-0", align: "end", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: messages.flightBillingStep.placeholders.searchPassengers }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: messages.flightBillingStep.noMatchingPassengers }), _jsx(CommandGroup, { children: passengers.map((p) => {
|
|
63
68
|
const fullName = [p.firstName, p.middleName, p.lastName]
|
|
64
69
|
.filter((s) => s?.trim())
|
|
65
70
|
.join(" ");
|
|
66
71
|
return (_jsx(CommandItem, { value: fullName, onSelect: () => {
|
|
67
72
|
onPick(p);
|
|
68
73
|
setOpen(false);
|
|
69
|
-
}, children: _jsx("span", { className: "truncate font-medium text-sm", children: fullName ||
|
|
74
|
+
}, children: _jsx("span", { className: "truncate font-medium text-sm", children: fullName || messages.common.noValue }) }, p.id));
|
|
70
75
|
}) })] })] }) })] }));
|
|
71
76
|
}
|
|
72
77
|
export function emptyBillingValue() {
|
|
@@ -85,27 +90,28 @@ export function emptyBillingValue() {
|
|
|
85
90
|
* when valid. Drives the journey's Continue gate.
|
|
86
91
|
*/
|
|
87
92
|
export function validateBilling(v) {
|
|
93
|
+
const messages = flightsUiEn.flightBillingStep.validation;
|
|
88
94
|
if (!v.email.trim())
|
|
89
|
-
return
|
|
95
|
+
return messages.emailRequired;
|
|
90
96
|
if (!/^\S+@\S+\.\S+$/.test(v.email.trim()))
|
|
91
|
-
return
|
|
97
|
+
return messages.emailInvalid;
|
|
92
98
|
if (!v.line1.trim())
|
|
93
|
-
return
|
|
99
|
+
return messages.streetAddressRequired;
|
|
94
100
|
if (!v.city.trim())
|
|
95
|
-
return
|
|
101
|
+
return messages.cityRequired;
|
|
96
102
|
if (!v.countryCode.trim())
|
|
97
|
-
return
|
|
103
|
+
return messages.countryRequired;
|
|
98
104
|
if (v.mode === "personal") {
|
|
99
105
|
if (!v.firstName.trim())
|
|
100
|
-
return
|
|
106
|
+
return messages.firstNameRequired;
|
|
101
107
|
if (!v.lastName.trim())
|
|
102
|
-
return
|
|
108
|
+
return messages.lastNameRequired;
|
|
103
109
|
}
|
|
104
110
|
else {
|
|
105
111
|
if (!v.companyName?.trim())
|
|
106
|
-
return
|
|
112
|
+
return messages.companyNameRequired;
|
|
107
113
|
if (!v.vatNumber?.trim())
|
|
108
|
-
return
|
|
114
|
+
return messages.vatNumberRequired;
|
|
109
115
|
}
|
|
110
116
|
return null;
|
|
111
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-booking-journey.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-journey.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,WAAW,EAEX,eAAe,EAEhB,MAAM,kCAAkC,CAAA;AAYzC,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,4BAA4B,CAAA;AAYnC,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,KAAK,EAAE,WAAW,CAAA;IAClB,gDAAgD;IAChD,UAAU,EAAE,eAAe,CAAA;IAC3B,2DAA2D;IAC3D,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAA;IAC1E,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,wEAAwE;IACxE,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;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,UAAU,EACV,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,qBAAqB,GACtB,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"flight-booking-journey.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-journey.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,WAAW,EAEX,eAAe,EAEhB,MAAM,kCAAkC,CAAA;AAYzC,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,4BAA4B,CAAA;AAYnC,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,KAAK,EAAE,WAAW,CAAA;IAClB,gDAAgD;IAChD,UAAU,EAAE,eAAe,CAAA;IAC3B,2DAA2D;IAC3D,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAA;IAC1E,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,wEAAwE;IACxE,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;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,UAAU,EACV,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,qBAAqB,GACtB,EAAE,yBAAyB,kDA2H3B"}
|
|
@@ -4,15 +4,16 @@ import { Button } from "@voyantjs/ui/components/button";
|
|
|
4
4
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
5
5
|
import { Check, ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
import { useFlightsUiI18nOrDefault } from "../i18n/index.js";
|
|
7
8
|
import { FlightContactForm, validateContact, } from "./flight-contact-form.js";
|
|
8
9
|
import { FlightOfferDetail } from "./flight-offer-detail.js";
|
|
9
10
|
import { FlightPassengerForm, validatePassengers, } from "./flight-passenger-form.js";
|
|
10
11
|
import { FlightPaymentSelector } from "./flight-payment-selector.js";
|
|
11
12
|
const STEPS = [
|
|
12
|
-
{ id: "review"
|
|
13
|
-
{ id: "passengers"
|
|
14
|
-
{ id: "contact"
|
|
15
|
-
{ id: "confirm"
|
|
13
|
+
{ id: "review" },
|
|
14
|
+
{ id: "passengers" },
|
|
15
|
+
{ id: "contact" },
|
|
16
|
+
{ id: "confirm" },
|
|
16
17
|
];
|
|
17
18
|
/**
|
|
18
19
|
* Multi-step booking journey: Review → Passengers → Contact + Payment →
|
|
@@ -21,6 +22,8 @@ const STEPS = [
|
|
|
21
22
|
* can't advance with incomplete data.
|
|
22
23
|
*/
|
|
23
24
|
export function FlightBookingJourney({ offer, passengers, onBook, onBooked, onCancel, carrierName, airportName, renderPassengerPicker, }) {
|
|
25
|
+
const i18n = useFlightsUiI18nOrDefault();
|
|
26
|
+
const messages = i18n.messages.flightBookingJourney;
|
|
24
27
|
const [stepIdx, setStepIdx] = useState(0);
|
|
25
28
|
const [paxList, setPaxList] = useState([]);
|
|
26
29
|
const [contact, setContact] = useState({});
|
|
@@ -70,45 +73,31 @@ export function FlightBookingJourney({ offer, passengers, onBook, onBooked, onCa
|
|
|
70
73
|
setSubmitting(false);
|
|
71
74
|
}
|
|
72
75
|
};
|
|
73
|
-
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx(Stepper, { currentIdx: stepIdx }), error && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive", children: error })), _jsxs("div", { className: "flex flex-col gap-4", children: [step.id === "review" && (_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "mb-4 text-base font-semibold", children:
|
|
76
|
+
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx(Stepper, { currentIdx: stepIdx, messages: messages.steps }), error && (_jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive", children: error })), _jsxs("div", { className: "flex flex-col gap-4", children: [step.id === "review" && (_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "mb-4 text-base font-semibold", children: messages.reviewTitle }), _jsx(FlightOfferDetail, { offer: offer, carrierName: carrierName, airportName: airportName })] })), step.id === "passengers" && (_jsx(FlightPassengerForm, { counts: passengers, value: paxList, onChange: setPaxList, renderPicker: renderPassengerPicker })), step.id === "contact" && (_jsxs(_Fragment, { children: [_jsx(FlightContactForm, { value: contact, onChange: setContact }), _jsx(FlightPaymentSelector, { value: payment, onChange: setPayment })] })), step.id === "confirm" && (_jsx(ConfirmSummary, { offer: offer, passengers: paxList, contact: contact, payment: payment, i18n: i18n }))] }), _jsxs("div", { className: "flex items-center justify-between border-t pt-4", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => (stepIdx === 0 ? onCancel?.() : goBack()), disabled: submitting, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), stepIdx === 0 ? messages.backToResults : messages.back] }), step.id === "confirm" ? (_jsx(Button, { onClick: submit, disabled: submitting, children: submitting ? messages.booking : messages.confirmBooking })) : (_jsxs(Button, { onClick: goNext, disabled: !canContinue, children: [messages.continue, _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] }))] })] }));
|
|
74
77
|
}
|
|
75
78
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
79
|
// Stepper indicator
|
|
77
80
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
|
-
function Stepper({ currentIdx }) {
|
|
81
|
+
function Stepper({ currentIdx, messages, }) {
|
|
79
82
|
return (_jsx("ol", { className: "flex items-center gap-2", children: STEPS.map((s, i) => {
|
|
80
83
|
const isActive = i === currentIdx;
|
|
81
84
|
const isComplete = i < currentIdx;
|
|
82
|
-
return (_jsxs("li", { className: "flex flex-1 items-center gap-2", children: [_jsx("div", { className: cn("flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-xs font-medium tabular-nums", isComplete && "border-primary bg-primary text-primary-foreground", isActive && !isComplete && "border-primary text-primary", !isActive && !isComplete && "border-border text-muted-foreground"), children: isComplete ? _jsx(Check, { className: "h-3.5 w-3.5" }) : i + 1 }), _jsx("span", { className: cn("truncate text-sm", isActive ? "font-medium text-foreground" : "text-muted-foreground"), children: s.
|
|
85
|
+
return (_jsxs("li", { className: "flex flex-1 items-center gap-2", children: [_jsx("div", { className: cn("flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-xs font-medium tabular-nums", isComplete && "border-primary bg-primary text-primary-foreground", isActive && !isComplete && "border-primary text-primary", !isActive && !isComplete && "border-border text-muted-foreground"), children: isComplete ? _jsx(Check, { className: "h-3.5 w-3.5" }) : i + 1 }), _jsx("span", { className: cn("truncate text-sm", isActive ? "font-medium text-foreground" : "text-muted-foreground"), children: messages[s.id] }), i < STEPS.length - 1 && _jsx("div", { className: "h-px flex-1 bg-border" })] }, s.id));
|
|
83
86
|
}) }));
|
|
84
87
|
}
|
|
85
88
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
86
89
|
// Final confirmation summary
|
|
87
90
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
88
|
-
function ConfirmSummary({ offer, passengers, contact, payment, }) {
|
|
89
|
-
|
|
91
|
+
function ConfirmSummary({ offer, passengers, contact, payment, i18n, }) {
|
|
92
|
+
const messages = i18n.messages.flightBookingJourney;
|
|
93
|
+
return (_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("h2", { className: "mb-4 text-base font-semibold", children: messages.confirmBooking }), _jsx(Row, { label: messages.rows.total, children: _jsx("span", { className: "font-semibold tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency, i18n) }) }), _jsx(Row, { label: messages.rows.passengers, children: passengers.length }), _jsx(Row, { label: messages.rows.contact, children: contact.email ?? i18n.messages.common.noValue }), _jsx(Row, { label: messages.rows.payment, children: _jsx("span", { className: "capitalize", children: payment.type.replace("_", " ") }) }), offer.expiresAt && (_jsx(Row, { label: messages.rows.offerExpires, children: _jsx("time", { dateTime: offer.expiresAt, children: i18n.formatDateTime(offer.expiresAt) }) })), _jsx("p", { className: "mt-4 text-xs text-muted-foreground", children: messages.confirmDescription })] }));
|
|
90
94
|
}
|
|
91
95
|
function Row({ label, children }) {
|
|
92
96
|
return (_jsxs("div", { className: "flex items-baseline justify-between border-b py-2 text-sm last:border-b-0", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { children: children })] }));
|
|
93
97
|
}
|
|
94
|
-
function formatMoney(amount, currency) {
|
|
98
|
+
function formatMoney(amount, currency, i18n) {
|
|
95
99
|
const n = Number(amount);
|
|
96
100
|
if (!Number.isFinite(n))
|
|
97
101
|
return `${amount} ${currency}`;
|
|
98
|
-
return
|
|
99
|
-
style: "currency",
|
|
100
|
-
currency,
|
|
101
|
-
maximumFractionDigits: 0,
|
|
102
|
-
}).format(n);
|
|
103
|
-
}
|
|
104
|
-
function formatDateTime(iso) {
|
|
105
|
-
const d = new Date(iso);
|
|
106
|
-
if (Number.isNaN(d.getTime()))
|
|
107
|
-
return iso;
|
|
108
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
109
|
-
day: "numeric",
|
|
110
|
-
month: "short",
|
|
111
|
-
hour: "2-digit",
|
|
112
|
-
minute: "2-digit",
|
|
113
|
-
}).format(d);
|
|
102
|
+
return i18n.formatCurrency(n, currency, { maximumFractionDigits: 0 });
|
|
114
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flight-booking-ledger.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-ledger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"flight-booking-ledger.d.ts","sourceRoot":"","sources":["../../src/components/flight-booking-ledger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAQ3F;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,WAAW,CAAA;IACrB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,KAAK,CAAA;IACd,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,wBAAwB,CAAA;IACnC,UAAU,EAAE,eAAe,CAAA;IAC3B,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,4DAA4D;IAC5D,cAAc,CAAC,EAAE,cAAc,EAAE,CAAA;IACjC,YAAY,CAAC,EAAE,cAAc,EAAE,CAAA;IAC/B,oEAAoE;IACpE,GAAG,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IACnF,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,CAAA;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,YAAY,GACZ,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,SAAS,GACT,SAAS,CAAA;AAEb;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACZ,GAAG,EACH,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,SAAS,GACV,EAAE,wBAAwB,2CA+D1B"}
|
|
@@ -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 { Button } from "@voyantjs/ui/components/button";
|
|
4
5
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
5
6
|
import { Check, Pencil, Plane, Users } from "lucide-react";
|
|
7
|
+
import { useFlightsUiI18nOrDefault } from "../i18n/index.js";
|
|
6
8
|
import { AirlineLogo } from "./airline-logo.js";
|
|
7
9
|
/**
|
|
8
10
|
* Sticky right-rail price ledger. Mirrors the running total + per-leg
|
|
@@ -12,12 +14,17 @@ import { AirlineLogo } from "./airline-logo.js";
|
|
|
12
14
|
* they were collected.
|
|
13
15
|
*/
|
|
14
16
|
export function FlightBookingLedger({ selection, passengers, carrierName, airportName, outboundExtras, returnExtras, cta, onEditOutbound, onEditReturn, completedSections, className, }) {
|
|
17
|
+
const i18n = useFlightsUiI18nOrDefault();
|
|
18
|
+
const messages = i18n.messages;
|
|
15
19
|
const total = computeTotal(selection, outboundExtras, returnExtras);
|
|
16
20
|
const paxTotal = (passengers.adults ?? 0) + (passengers.children ?? 0) + (passengers.infants ?? 0);
|
|
17
|
-
return (_jsxs("aside", { className: cn("flex w-full max-w-sm flex-col gap-4 rounded-xl border bg-card p-4 shadow-sm", className), children: [_jsx(LegBlock, { label: selection.return
|
|
21
|
+
return (_jsxs("aside", { className: cn("flex w-full max-w-sm flex-col gap-4 rounded-xl border bg-card p-4 shadow-sm", className), children: [_jsx(LegBlock, { label: selection.return
|
|
22
|
+
? messages.flightBookingLedger.outbound
|
|
23
|
+
: messages.flightBookingLedger.flight, offer: selection.outbound, carrierName: carrierName, airportName: airportName, extras: outboundExtras, onEdit: onEditOutbound, complete: completedSections?.has("flights"), i18n: i18n }), selection.return && (_jsx(LegBlock, { label: messages.flightBookingLedger.return, offer: selection.return, carrierName: carrierName, airportName: airportName, extras: returnExtras, onEdit: onEditReturn, complete: completedSections?.has("flights"), i18n: i18n })), _jsx(SectionRow, { icon: _jsx(Users, { className: "h-3.5 w-3.5" }), label: messages.flightBookingLedger.passengers, right: `${paxTotal} ${messages.common.pax}`, complete: completedSections?.has("passengers") }), _jsx(PlaceholderSections, { completed: completedSections, messages: messages }), _jsxs("div", { className: "mt-2 flex items-center justify-between border-t pt-3", children: [_jsx("span", { className: "font-medium text-sm", children: messages.common.total }), _jsx("span", { className: "font-semibold text-lg tabular-nums", children: formatMoney(total.amount, total.currency, i18n) })] }), cta && (_jsx(Button, { className: "w-full", onClick: cta.onClick, disabled: cta.disabled || cta.loading, children: cta.loading ? messages.flightBookingLedger.working : cta.label }))] }));
|
|
18
24
|
}
|
|
19
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
-
function LegBlock({ label, offer, carrierName, airportName, extras, onEdit, complete, }) {
|
|
26
|
+
function LegBlock({ label, offer, carrierName, airportName, extras, onEdit, complete, i18n, }) {
|
|
27
|
+
const messages = i18n.messages;
|
|
21
28
|
const itin = offer.itineraries[0];
|
|
22
29
|
if (!itin)
|
|
23
30
|
return null;
|
|
@@ -28,12 +35,12 @@ function LegBlock({ label, offer, carrierName, airportName, extras, onEdit, comp
|
|
|
28
35
|
return null;
|
|
29
36
|
const carriers = Array.from(new Set(segs.map((s) => s.carrierCode)));
|
|
30
37
|
const stops = segs.length - 1;
|
|
31
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [complete ? (_jsx(Check, { className: "h-3.5 w-3.5 text-emerald-600" })) : (_jsx(Plane, { className: "h-3.5 w-3.5 text-muted-foreground" })), _jsx("span", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-semibold text-sm tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency) }), onEdit && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-6 px-1.5 text-muted-foreground", onClick: onEdit, children: _jsx(Pencil, { className: "h-3 w-3" }) }))] })] }), _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: 18 }, code))) }), _jsxs("div", { className: "flex min-w-0 flex-col leading-tight", children: [_jsxs("span", { className: "truncate font-medium text-sm", children: [airportName?.(first.departure.iataCode) ?? first.departure.iataCode, " \u2192", " ", airportName?.(last.arrival.iataCode) ?? last.arrival.iataCode] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [formatDate(first.departure.at), " \u00B7 ", formatTime(first.departure.at), " \u2013", " ", formatTime(last.arrival.at), " \u00B7
|
|
38
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [complete ? (_jsx(Check, { className: "h-3.5 w-3.5 text-emerald-600" })) : (_jsx(Plane, { className: "h-3.5 w-3.5 text-muted-foreground" })), _jsx("span", { className: "font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: label })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-semibold text-sm tabular-nums", children: formatMoney(offer.totalPrice.amount, offer.totalPrice.currency, i18n) }), onEdit && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-6 px-1.5 text-muted-foreground", onClick: onEdit, children: _jsx(Pencil, { className: "h-3 w-3" }) }))] })] }), _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: 18 }, code))) }), _jsxs("div", { className: "flex min-w-0 flex-col leading-tight", children: [_jsxs("span", { className: "truncate font-medium text-sm", children: [airportName?.(first.departure.iataCode) ?? first.departure.iataCode, " \u2192", " ", airportName?.(last.arrival.iataCode) ?? last.arrival.iataCode] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [formatDate(first.departure.at), " \u00B7 ", formatTime(first.departure.at), " \u2013", " ", formatTime(last.arrival.at), " \u00B7 ", formatStops(stops, messages)] })] })] }), extras && extras.length > 0 && (_jsx("ul", { className: "flex flex-col gap-1 border-t pt-2", children: extras.map((x, i) => (_jsxs("li", { className: "flex items-center justify-between text-muted-foreground text-xs", children: [_jsx("span", { children: x.label }), _jsx("span", { className: "tabular-nums", children: x.amount ? formatMoney(x.amount.amount, x.amount.currency, i18n) : (x.meta ?? "") })] }, i))) }))] }));
|
|
32
39
|
}
|
|
33
40
|
function SectionRow({ icon, label, right, complete, }) {
|
|
34
41
|
return (_jsxs("div", { className: "flex items-center justify-between border-t pt-3", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-medium text-[11px] uppercase tracking-wider text-muted-foreground", children: [complete ? (_jsx(Check, { className: "h-3.5 w-3.5 text-emerald-600" })) : (_jsx("span", { className: "text-muted-foreground", children: icon })), label] }), right && _jsx("span", { className: "text-muted-foreground text-xs", children: right })] }));
|
|
35
42
|
}
|
|
36
|
-
function PlaceholderSections({ completed }) {
|
|
43
|
+
function PlaceholderSections({ completed, messages, }) {
|
|
37
44
|
// Phase 1 only renders Passengers above; later phases will replace this with
|
|
38
45
|
// bags/seats/services/documents/billing rows. The shape stays consistent so
|
|
39
46
|
// the ledger doesn't need to change later.
|
|
@@ -41,9 +48,9 @@ function PlaceholderSections({ completed }) {
|
|
|
41
48
|
return null;
|
|
42
49
|
const items = [];
|
|
43
50
|
if (completed.has("billing"))
|
|
44
|
-
items.push({ id: "billing", label:
|
|
51
|
+
items.push({ id: "billing", label: messages.flightBookingLedger.billing });
|
|
45
52
|
if (completed.has("payment"))
|
|
46
|
-
items.push({ id: "payment", label:
|
|
53
|
+
items.push({ id: "payment", label: messages.flightBookingLedger.payment });
|
|
47
54
|
if (items.length === 0)
|
|
48
55
|
return null;
|
|
49
56
|
return (_jsx(_Fragment, { children: items.map((it) => (_jsx(SectionRow, { icon: _jsx("span", {}), label: it.label, complete: completed.has(it.id) }, it.id))) }));
|
|
@@ -66,15 +73,11 @@ function num(v) {
|
|
|
66
73
|
const n = Number(v);
|
|
67
74
|
return Number.isFinite(n) ? n : 0;
|
|
68
75
|
}
|
|
69
|
-
function formatMoney(amount, currency) {
|
|
76
|
+
function formatMoney(amount, currency, i18n) {
|
|
70
77
|
const n = Number(amount);
|
|
71
78
|
if (!Number.isFinite(n))
|
|
72
79
|
return `${amount} ${currency}`;
|
|
73
|
-
return
|
|
74
|
-
style: "currency",
|
|
75
|
-
currency,
|
|
76
|
-
maximumFractionDigits: 0,
|
|
77
|
-
}).format(n);
|
|
80
|
+
return i18n.formatCurrency(n, currency, { maximumFractionDigits: 0 });
|
|
78
81
|
}
|
|
79
82
|
function formatTime(iso) {
|
|
80
83
|
const d = new Date(iso);
|
|
@@ -82,6 +85,13 @@ function formatTime(iso) {
|
|
|
82
85
|
return iso;
|
|
83
86
|
return new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit" }).format(d);
|
|
84
87
|
}
|
|
88
|
+
function formatStops(stops, messages) {
|
|
89
|
+
if (stops === 0)
|
|
90
|
+
return messages.common.stops.nonstop;
|
|
91
|
+
return formatMessage(stops === 1 ? messages.common.stops.oneStop : messages.common.stops.manyStops, {
|
|
92
|
+
count: stops,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
85
95
|
function formatDate(iso) {
|
|
86
96
|
const d = new Date(iso);
|
|
87
97
|
if (Number.isNaN(d.getTime()))
|
|
@@ -37,7 +37,7 @@ export function FlightBookingPage({ outboundOfferId, returnOfferId, passengers,
|
|
|
37
37
|
if (cancelled)
|
|
38
38
|
return null;
|
|
39
39
|
if (!result.valid)
|
|
40
|
-
return result.invalidReason ??
|
|
40
|
+
return result.invalidReason ?? messages.offerUnavailable;
|
|
41
41
|
setter(result.offer);
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
@@ -65,14 +65,14 @@ export function FlightBookingPage({ outboundOfferId, returnOfferId, passengers,
|
|
|
65
65
|
returnCatalog: returnAncillaries.data?.catalog ?? null,
|
|
66
66
|
loading: outboundAncillaries.isLoading || (returnLeg != null && returnAncillaries.isLoading),
|
|
67
67
|
};
|
|
68
|
-
const seatMaps = useSeatMapFetcher({ outbound, returnLeg, enabled: pricedReady });
|
|
68
|
+
const seatMaps = useSeatMapFetcher({ outbound, returnLeg, enabled: pricedReady, messages });
|
|
69
69
|
const savedMethodsQuery = useSavedPaymentMethods(selectedPersonId, {
|
|
70
70
|
enabled: !!selectedPersonId,
|
|
71
71
|
});
|
|
72
72
|
const savedPaymentMethods = {
|
|
73
73
|
methods: (savedMethodsQuery.data?.data ?? []).map((method) => ({
|
|
74
74
|
id: method.id,
|
|
75
|
-
label: [brandHumanLabel(method.brand), method.last4 ? `....${method.last4}` : null]
|
|
75
|
+
label: [brandHumanLabel(method.brand, messages), method.last4 ? `....${method.last4}` : null]
|
|
76
76
|
.filter(Boolean)
|
|
77
77
|
.join(" "),
|
|
78
78
|
provider: null,
|
|
@@ -103,18 +103,18 @@ export function FlightBookingPage({ outboundOfferId, returnOfferId, passengers,
|
|
|
103
103
|
const defaultPassengerPicker = (_slot, onPicked) => (_jsx(PassengerContactPicker, { onPick: onPicked, onAddContact: onAddPassengerContact, onPersonSelected: setSelectedPersonId }));
|
|
104
104
|
return (_jsxs("div", { className: cn("mx-auto flex w-full max-w-screen-2xl flex-col gap-6 px-6 py-6 lg:px-8", className), children: [_jsxs("header", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "font-semibold text-2xl", children: messages.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: selection.return ? messages.descriptionTrip : messages.descriptionOffer })] }), _jsxs(Button, { variant: "ghost", onClick: onBackToSearch, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), messages.backToResults] })] }), _jsx(FlightBookingShell, { selection: selection, passengers: passengers, carrierName: carrierName, airportName: airportName, ancillaries: ancillaries, seatMaps: seatMaps, savedPaymentMethods: savedPaymentMethods, paymentCapabilities: paymentCapabilities, documentsRequired: documentsRequired, renderPassengerPicker: renderPassengerPicker ?? defaultPassengerPicker, renderBillingPersonPicker: (apply) => renderBillingPersonPicker ? (renderBillingPersonPicker(apply, { onPersonSelected: setSelectedPersonId })) : (_jsx(BillingPersonPicker, { apply: apply, onPersonSelected: setSelectedPersonId })), renderBillingOrgPicker: (apply) => renderBillingOrgPicker ? (renderBillingOrgPicker(apply)) : (_jsx(BillingOrgPicker, { apply: apply })), onSaveBillingDefaults: onSaveBillingDefaults, onCancel: onBackToSearch, onEditOutbound: onEditOutbound ?? onBackToSearch, onEditReturn: onEditReturn ?? onBackToSearch, onBook: onBook, onBooked: onBooked })] }));
|
|
105
105
|
}
|
|
106
|
-
function brandHumanLabel(brand) {
|
|
106
|
+
function brandHumanLabel(brand, messages) {
|
|
107
107
|
switch (brand) {
|
|
108
108
|
case "visa":
|
|
109
|
-
return
|
|
109
|
+
return messages.paymentBrandLabels.visa;
|
|
110
110
|
case "mastercard":
|
|
111
|
-
return
|
|
111
|
+
return messages.paymentBrandLabels.mastercard;
|
|
112
112
|
case "amex":
|
|
113
|
-
return
|
|
113
|
+
return messages.paymentBrandLabels.amex;
|
|
114
114
|
case "revolut":
|
|
115
|
-
return
|
|
115
|
+
return messages.paymentBrandLabels.revolut;
|
|
116
116
|
case "bank_transfer":
|
|
117
|
-
return
|
|
117
|
+
return messages.paymentBrandLabels.bank_transfer;
|
|
118
118
|
default:
|
|
119
119
|
return brand;
|
|
120
120
|
}
|
|
@@ -133,7 +133,7 @@ function detectInternational(offer) {
|
|
|
133
133
|
return false;
|
|
134
134
|
return first.departure.iataCode.slice(0, 1) !== last.arrival.iataCode.slice(0, 1);
|
|
135
135
|
}
|
|
136
|
-
function useSeatMapFetcher({ outbound, returnLeg, enabled, }) {
|
|
136
|
+
function useSeatMapFetcher({ outbound, returnLeg, enabled, messages, }) {
|
|
137
137
|
const client = useVoyantFlightsContext();
|
|
138
138
|
const segmentInputs = useMemo(() => {
|
|
139
139
|
const list = [];
|
|
@@ -170,6 +170,6 @@ function useSeatMapFetcher({ outbound, returnLeg, enabled, }) {
|
|
|
170
170
|
return map;
|
|
171
171
|
}, [segmentInputs, results]);
|
|
172
172
|
return useMemo(() => ({
|
|
173
|
-
getSeatMap: ({ segmentId }) => slotsBySegment.get(segmentId) ?? { seatMap: null, error:
|
|
174
|
-
}), [slotsBySegment]);
|
|
173
|
+
getSeatMap: ({ segmentId }) => slotsBySegment.get(segmentId) ?? { seatMap: null, error: messages.segmentNotFound },
|
|
174
|
+
}), [slotsBySegment, messages.segmentNotFound]);
|
|
175
175
|
}
|