@voyant-travel/bookings-react 0.162.1 → 0.162.2
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/admin/booking-documents-table.js +7 -5
- package/dist/admin/journey-departure-picker.js +7 -16
- package/dist/admin/journey-travel-credit-picker.js +11 -10
- package/dist/admin/person-bookings-widget.js +5 -4
- package/dist/components/booking-list-filters.js +5 -5
- package/dist/components/booking-payments-summary.js +8 -7
- package/dist/components/traveler-list.js +12 -12
- package/dist/extras/i18n/provider.d.ts +2 -1
- package/dist/extras/i18n/provider.js +2 -2
- package/dist/i18n/provider.d.ts +2 -1
- package/dist/i18n/provider.js +2 -2
- package/dist/journey/components/side-panel.d.ts +1 -1
- package/dist/journey/components/side-panel.js +15 -15
- package/dist/requirements/constants.d.ts +36 -36
- package/dist/requirements/constants.js +38 -36
- package/dist/requirements/hooks/use-booking-questions.d.ts +1 -1
- package/dist/requirements/i18n/provider.d.ts +2 -1
- package/dist/requirements/i18n/provider.js +2 -2
- package/dist/requirements/query-options.d.ts +4 -4
- package/dist/requirements/schemas.d.ts +3 -3
- package/dist/storefront/storefront-booking-page.js +15 -13
- package/package.json +30 -30
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { useAdminNavigate, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
5
|
+
import { useAdminNavigate, useLocale, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
6
6
|
import { legalQueryKeys, useLegalContractAttachments, useLegalContracts, useVoyantLegalContext, } from "@voyant-travel/legal-react";
|
|
7
7
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, } from "@voyant-travel/ui/components";
|
|
8
8
|
import { DataTable } from "@voyant-travel/ui/components/data-table";
|
|
@@ -177,6 +177,7 @@ function TravelerStatusCell({ doc, messages, }) {
|
|
|
177
177
|
return (_jsx(StatusBadge, { status: isExpired ? "expired" : "active", children: isExpired ? messages.travelerStatusExpired : messages.travelerStatusOnFile }));
|
|
178
178
|
}
|
|
179
179
|
function ContractDateCell({ contract, messages, }) {
|
|
180
|
+
const { resolvedLocale } = useLocale();
|
|
180
181
|
const generationFailure = resolveContractGenerationFailure(contract);
|
|
181
182
|
const attachmentsQuery = useLegalContractAttachments({ contractId: contract.id });
|
|
182
183
|
const attachments = (attachmentsQuery.data ?? []).filter((a) => a.kind === "document");
|
|
@@ -189,14 +190,15 @@ function ContractDateCell({ contract, messages, }) {
|
|
|
189
190
|
: messages.contractPendingSinceLabel;
|
|
190
191
|
if (!dateIso)
|
|
191
192
|
return _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
|
|
192
|
-
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso)] }));
|
|
193
|
+
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso, resolvedLocale)] }));
|
|
193
194
|
}
|
|
194
195
|
function TravelerDateCell({ doc, messages, }) {
|
|
196
|
+
const { resolvedLocale } = useLocale();
|
|
195
197
|
const dateIso = doc.expiresAt ?? doc.createdAt ?? null;
|
|
196
198
|
const dateLabel = doc.expiresAt ? messages.travelerExpiresLabel : messages.travelerUploadedLabel;
|
|
197
199
|
if (!dateIso)
|
|
198
200
|
return _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
|
|
199
|
-
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso)] }));
|
|
201
|
+
return (_jsxs("span", { className: "text-muted-foreground text-xs", children: [_jsxs("span", { className: "opacity-60", children: [dateLabel, " "] }), formatDate(dateIso, resolvedLocale)] }));
|
|
200
202
|
}
|
|
201
203
|
function ContractActionsCell({ contract, messages, }) {
|
|
202
204
|
const queryClient = useQueryClient();
|
|
@@ -246,12 +248,12 @@ function formatBytes(bytes) {
|
|
|
246
248
|
return `${Math.round(bytes / 1024)} KB`;
|
|
247
249
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
248
250
|
}
|
|
249
|
-
function formatDate(iso) {
|
|
251
|
+
function formatDate(iso, locale) {
|
|
250
252
|
try {
|
|
251
253
|
const d = new Date(iso);
|
|
252
254
|
if (!Number.isFinite(d.getTime()))
|
|
253
255
|
return iso;
|
|
254
|
-
return d.toLocaleDateString(
|
|
256
|
+
return d.toLocaleDateString(locale, { day: "numeric", month: "short", year: "numeric" });
|
|
255
257
|
}
|
|
256
258
|
catch {
|
|
257
259
|
return iso;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLocale } from "@voyant-travel/admin";
|
|
3
4
|
import { useSlots } from "@voyant-travel/operations-react/availability";
|
|
4
5
|
import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyant-travel/ui/components/combobox";
|
|
5
6
|
import { DatePicker } from "@voyant-travel/ui/components/date-picker";
|
|
@@ -7,23 +8,13 @@ import { Label } from "@voyant-travel/ui/components/label";
|
|
|
7
8
|
import { useEffect, useMemo, useState } from "react";
|
|
8
9
|
import { getBookableDepartureSlots } from "../components/booking-create-utils.js";
|
|
9
10
|
import { useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
|
|
10
|
-
/**
|
|
11
|
-
* Admin departure picker for the booking journey's `"departure"`
|
|
12
|
-
* sub-step. Loads the owned product's scheduled departures from
|
|
13
|
-
* availability and lets the operator pick a real one (filtered by the
|
|
14
|
-
* chosen product option). When the product has no scheduled
|
|
15
|
-
* departures, it falls back to a free date so non-scheduled
|
|
16
|
-
* products can still be booked.
|
|
17
|
-
*
|
|
18
|
-
* Wired into `<BookingJourneyHost />` via the journey's
|
|
19
|
-
* `renderDeparturePicker` slot.
|
|
20
|
-
*/
|
|
21
|
-
const dateFormatter = new Intl.DateTimeFormat(undefined, {
|
|
22
|
-
year: "numeric",
|
|
23
|
-
month: "short",
|
|
24
|
-
day: "numeric",
|
|
25
|
-
});
|
|
26
11
|
export function JourneyDeparturePicker({ productId, optionId, slotId, departureDate, onChange, lockDeparture = false, }) {
|
|
12
|
+
const { resolvedLocale } = useLocale();
|
|
13
|
+
const dateFormatter = useMemo(() => new Intl.DateTimeFormat(resolvedLocale, {
|
|
14
|
+
year: "numeric",
|
|
15
|
+
month: "short",
|
|
16
|
+
day: "numeric",
|
|
17
|
+
}), [resolvedLocale]);
|
|
27
18
|
const messages = useBookingsUiMessagesOrDefault().bookingCreateDialog;
|
|
28
19
|
// Stable "now" so the slot query + future filter don't churn every render.
|
|
29
20
|
const [nowIso] = useState(() => new Date().toISOString());
|
|
@@ -6,23 +6,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* redeemed); they never need to know the exact code, unlike storefront
|
|
7
7
|
* customers. Wired into `<BookingJourneyHost />` via `renderTravelCreditPicker`.
|
|
8
8
|
*/
|
|
9
|
-
import { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
9
|
+
import { useLocale, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
10
10
|
import { useTravelCredits } from "@voyant-travel/finance-react";
|
|
11
11
|
import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyant-travel/ui/components/combobox";
|
|
12
12
|
import { Label } from "@voyant-travel/ui/components/label";
|
|
13
13
|
import { useEffect, useState } from "react";
|
|
14
|
-
function formatMoney(cents, currency) {
|
|
14
|
+
function formatMoney(cents, currency, locale) {
|
|
15
15
|
try {
|
|
16
|
-
return new Intl.NumberFormat(
|
|
16
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(cents / 100);
|
|
17
17
|
}
|
|
18
18
|
catch {
|
|
19
19
|
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
function travelCreditLabel(travelCredit) {
|
|
23
|
-
return `${travelCredit.code} · ${formatMoney(travelCredit.remainingAmountCents, travelCredit.currency)}`;
|
|
22
|
+
function travelCreditLabel(travelCredit, locale) {
|
|
23
|
+
return `${travelCredit.code} · ${formatMoney(travelCredit.remainingAmountCents, travelCredit.currency, locale)}`;
|
|
24
24
|
}
|
|
25
25
|
export function JourneyTravelCreditPicker({ value, onApply, }) {
|
|
26
|
+
const { resolvedLocale } = useLocale();
|
|
26
27
|
const t = useOperatorAdminMessages().bookings.detail.bookingJourney;
|
|
27
28
|
const [inputValue, setInputValue] = useState("");
|
|
28
29
|
const [search, setSearch] = useState("");
|
|
@@ -39,12 +40,12 @@ export function JourneyTravelCreditPicker({ value, onApply, }) {
|
|
|
39
40
|
// Reflect a pre-selected Travel Credit's label once its record loads.
|
|
40
41
|
useEffect(() => {
|
|
41
42
|
if (selectedId && byId.has(selectedId)) {
|
|
42
|
-
setInputValue(travelCreditLabel(byId.get(selectedId)));
|
|
43
|
+
setInputValue(travelCreditLabel(byId.get(selectedId), resolvedLocale));
|
|
43
44
|
}
|
|
44
|
-
}, [selectedId, byId]);
|
|
45
|
+
}, [selectedId, byId, resolvedLocale]);
|
|
45
46
|
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: t.travelCreditPickerLabel }), _jsxs(Combobox, { items: travelCredits.map((travelCredit) => travelCredit.id), value: selectedId, inputValue: inputValue, autoHighlight: true, itemToStringLabel: (id) => {
|
|
46
47
|
const v = byId.get(id);
|
|
47
|
-
return v ? travelCreditLabel(v) : id;
|
|
48
|
+
return v ? travelCreditLabel(v, resolvedLocale) : id;
|
|
48
49
|
}, itemToStringValue: (id) => id, onInputValueChange: (next) => {
|
|
49
50
|
setInputValue(next);
|
|
50
51
|
setSearch(next);
|
|
@@ -60,12 +61,12 @@ export function JourneyTravelCreditPicker({ value, onApply, }) {
|
|
|
60
61
|
const v = byId.get(id);
|
|
61
62
|
if (v) {
|
|
62
63
|
onApply({ travelCreditId: v.id, amountCents: v.remainingAmountCents });
|
|
63
|
-
setInputValue(travelCreditLabel(v));
|
|
64
|
+
setInputValue(travelCreditLabel(v, resolvedLocale));
|
|
64
65
|
}
|
|
65
66
|
}, children: [_jsx(ComboboxInput, { placeholder: t.travelCreditSearchPlaceholder, showClear: !!selectedId }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: t.travelCreditEmpty }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
|
|
66
67
|
const v = byId.get(id);
|
|
67
68
|
if (!v)
|
|
68
69
|
return null;
|
|
69
|
-
return (_jsx(ComboboxItem, { value: v.id, children: _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-medium", children: v.code }), _jsx("span", { className: "truncate text-muted-foreground text-xs", children: formatMoney(v.remainingAmountCents, v.currency) })] }) }, v.id));
|
|
70
|
+
return (_jsx(ComboboxItem, { value: v.id, children: _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-medium", children: v.code }), _jsx("span", { className: "truncate text-muted-foreground text-xs", children: formatMoney(v.remainingAmountCents, v.currency, resolvedLocale) })] }) }, v.id));
|
|
70
71
|
} }) })] })] })] }));
|
|
71
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useAdminNavigate, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
3
|
+
import { useAdminNavigate, useLocale, useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
4
4
|
import { Badge } from "@voyant-travel/ui/components/badge";
|
|
5
5
|
import { Skeleton } from "@voyant-travel/ui/components/skeleton";
|
|
6
6
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyant-travel/ui/components/table";
|
|
@@ -15,6 +15,7 @@ import { bookingStatusBadgeVariant, useBookings } from "../index.js";
|
|
|
15
15
|
* semantic `booking.detail` destination.
|
|
16
16
|
*/
|
|
17
17
|
export function PersonBookingsWidget({ personId }) {
|
|
18
|
+
const { resolvedLocale } = useLocale();
|
|
18
19
|
const adminMessages = useOperatorAdminMessages();
|
|
19
20
|
const messages = adminMessages.bookings.list;
|
|
20
21
|
const navigateTo = useAdminNavigate();
|
|
@@ -35,14 +36,14 @@ export function PersonBookingsWidget({ personId }) {
|
|
|
35
36
|
const extraItems = Math.max((booking.items?.length ?? 0) - 1, 0);
|
|
36
37
|
return (_jsxs(TableRow, { onClick: () => navigateTo("booking.detail", { bookingId: booking.id }), className: "cursor-pointer", children: [_jsx(TableCell, { className: "font-medium", children: booking.bookingNumber }), _jsx(TableCell, { children: _jsxs("div", { className: "max-w-[280px] truncate", title: itemLabel, children: [itemLabel, extraItems > 0 ? (_jsxs("span", { className: "ml-1 text-muted-foreground text-xs", children: ["+", extraItems] })) : null] }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: bookingStatusBadgeVariant[booking.status], children: booking.status }) }), _jsx(TableCell, { children: booking.sellAmountCents == null
|
|
37
38
|
? "—"
|
|
38
|
-
: `${(booking.sellAmountCents / 100).toFixed(2)} ${booking.sellCurrency}` }), _jsx(TableCell, { children: booking.pax ?? "—" }), _jsx(TableCell, { children: formatBookingDate(booking.startsAt ?? booking.startDate) })] }, booking.id));
|
|
39
|
+
: `${(booking.sellAmountCents / 100).toFixed(2)} ${booking.sellCurrency}` }), _jsx(TableCell, { children: booking.pax ?? "—" }), _jsx(TableCell, { children: formatBookingDate(booking.startsAt ?? booking.startDate, resolvedLocale) })] }, booking.id));
|
|
39
40
|
}) })] }) }));
|
|
40
41
|
}
|
|
41
|
-
function formatBookingDate(value) {
|
|
42
|
+
function formatBookingDate(value, locale) {
|
|
42
43
|
if (!value)
|
|
43
44
|
return "—";
|
|
44
45
|
const date = new Date(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00` : value);
|
|
45
46
|
if (Number.isNaN(date.getTime()))
|
|
46
47
|
return "—";
|
|
47
|
-
return date.toLocaleDateString();
|
|
48
|
+
return date.toLocaleDateString(locale);
|
|
48
49
|
}
|
|
@@ -15,11 +15,11 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "
|
|
|
15
15
|
import { ListFilter, X } from "lucide-react";
|
|
16
16
|
import * as React from "react";
|
|
17
17
|
import { BOOKING_STATUS_ALL } from "../booking-list-constants.js";
|
|
18
|
-
import {
|
|
18
|
+
import { useBookingsUiI18nOrDefault } from "../i18n/provider.js";
|
|
19
19
|
import { bookingStatuses } from "../index.js";
|
|
20
20
|
export { BOOKING_STATUS_ALL };
|
|
21
21
|
export function BookingListFiltersPopover({ open, onOpenChange, activeFilterCount, status, onStatusChange, productId, onProductIdChange, optionId, onOptionIdChange, availabilitySlotId, onAvailabilitySlotIdChange, supplierId, onSupplierIdChange, productCategoryId, onProductCategoryIdChange, personId, onPersonIdChange, organizationId, onOrganizationIdChange, dateRange, onDateRangeChange, paxMin, onPaxMinChange, paxMax, onPaxMaxChange, onFiltersChanged, hasActiveFilters, onClearFilters, }) {
|
|
22
|
-
const messages =
|
|
22
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
23
23
|
const filterMessages = messages.bookingList.filters;
|
|
24
24
|
const statusLabels = messages.common.bookingStatusLabels;
|
|
25
25
|
const [selectedProduct, setSelectedProduct] = React.useState(null);
|
|
@@ -115,7 +115,7 @@ export function BookingListFiltersPopover({ open, onOpenChange, activeFilterCoun
|
|
|
115
115
|
setSelectedSlot(match);
|
|
116
116
|
}
|
|
117
117
|
markChanged();
|
|
118
|
-
}, items: slots, selectedItem: selectedSlot, getKey: (slot) => slot.id, getLabel: (slot) => formatSlotLabel(slot), getSecondary: (slot) => slot.status, placeholder: filterMessages.departure, emptyText: productId ? filterMessages.departureEmpty : filterMessages.departureNeedsProduct, disabled: productId === null })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "bookings-filter-category", children: filterMessages.categoryLabel }), _jsx(AsyncCombobox, { value: productCategoryId, onChange: (value) => {
|
|
118
|
+
}, items: slots, selectedItem: selectedSlot, getKey: (slot) => slot.id, getLabel: (slot) => formatSlotLabel(slot, locale), getSecondary: (slot) => slot.status, placeholder: filterMessages.departure, emptyText: productId ? filterMessages.departureEmpty : filterMessages.departureNeedsProduct, disabled: productId === null })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "bookings-filter-category", children: filterMessages.categoryLabel }), _jsx(AsyncCombobox, { value: productCategoryId, onChange: (value) => {
|
|
119
119
|
onProductCategoryIdChange(value);
|
|
120
120
|
if (!value)
|
|
121
121
|
setSelectedProductCategory(null);
|
|
@@ -175,9 +175,9 @@ function formatPersonName(person) {
|
|
|
175
175
|
* in the slot's own timezone so the operator sees what the customer
|
|
176
176
|
* sees, not whatever the admin's browser locale converts it to.
|
|
177
177
|
*/
|
|
178
|
-
function formatSlotLabel(slot) {
|
|
178
|
+
function formatSlotLabel(slot, locale) {
|
|
179
179
|
try {
|
|
180
|
-
const formatter = new Intl.DateTimeFormat(
|
|
180
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
181
181
|
day: "numeric",
|
|
182
182
|
month: "short",
|
|
183
183
|
year: "numeric",
|
|
@@ -31,7 +31,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", onInvoic
|
|
|
31
31
|
const publicQuery = usePublicBookingPayments(bookingId, { enabled: variant === "public" });
|
|
32
32
|
const adminQuery = useAdminBookingPayments(bookingId, { enabled: variant === "admin" });
|
|
33
33
|
const data = variant === "admin" ? adminQuery.data : publicQuery.data;
|
|
34
|
-
const { formatDateTime } = useBookingsUiI18nOrDefault();
|
|
34
|
+
const { formatDateTime, locale } = useBookingsUiI18nOrDefault();
|
|
35
35
|
const messages = useBookingsUiMessagesOrDefault();
|
|
36
36
|
const card = messages.bookingPaymentsSummary;
|
|
37
37
|
const payments = data?.data?.payments ?? [];
|
|
@@ -91,7 +91,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", onInvoic
|
|
|
91
91
|
{
|
|
92
92
|
accessorKey: "amountCents",
|
|
93
93
|
header: card.columns.amount,
|
|
94
|
-
cell: ({ row }) => (_jsx("span", { className: "font-mono font-medium", children: formatMoney(row.original.amountCents, row.original.currency) })),
|
|
94
|
+
cell: ({ row }) => (_jsx("span", { className: "font-mono font-medium", children: formatMoney(row.original.amountCents, row.original.currency, locale) })),
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
id: "fx",
|
|
@@ -103,7 +103,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", onInvoic
|
|
|
103
103
|
baseCurrency.toUpperCase() === currency.toUpperCase()) {
|
|
104
104
|
return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
105
105
|
}
|
|
106
|
-
return (_jsxs("span", { className: "font-mono text-xs", children: ["\u2248 ", formatMoney(baseAmountCents, baseCurrency)] }));
|
|
106
|
+
return (_jsxs("span", { className: "font-mono text-xs", children: ["\u2248 ", formatMoney(baseAmountCents, baseCurrency, locale)] }));
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
109
|
{
|
|
@@ -170,6 +170,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", onInvoic
|
|
|
170
170
|
}, [
|
|
171
171
|
card,
|
|
172
172
|
formatDateTime,
|
|
173
|
+
locale,
|
|
173
174
|
getInvoiceHref,
|
|
174
175
|
onInvoiceOpen,
|
|
175
176
|
onConvertProforma,
|
|
@@ -182,18 +183,18 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", onInvoic
|
|
|
182
183
|
if (!next && !deletePending)
|
|
183
184
|
setDeleteTarget(null);
|
|
184
185
|
}, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: card.deleteConfirm.title }), _jsx(AlertDialogDescription, { children: deleteTarget
|
|
185
|
-
? card.deleteConfirm.description.replace("{amount}", formatMoney(deleteTarget.amountCents, deleteTarget.currency))
|
|
186
|
+
? card.deleteConfirm.description.replace("{amount}", formatMoney(deleteTarget.amountCents, deleteTarget.currency, locale))
|
|
186
187
|
: "" })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deletePending, children: card.deleteConfirm.cancel }), _jsx(AlertDialogAction, { variant: "destructive", disabled: deletePending, onClick: () => void handleDeleteConfirm(), children: card.deleteConfirm.confirm })] })] }) })) : null] }));
|
|
187
188
|
}
|
|
188
|
-
function formatMoney(cents, currency) {
|
|
189
|
+
function formatMoney(cents, currency, locale) {
|
|
189
190
|
if (!currency) {
|
|
190
|
-
return new Intl.NumberFormat(
|
|
191
|
+
return new Intl.NumberFormat(locale, {
|
|
191
192
|
minimumFractionDigits: 2,
|
|
192
193
|
maximumFractionDigits: 2,
|
|
193
194
|
}).format(cents / 100);
|
|
194
195
|
}
|
|
195
196
|
try {
|
|
196
|
-
return new Intl.NumberFormat(
|
|
197
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(cents / 100);
|
|
197
198
|
}
|
|
198
199
|
catch {
|
|
199
200
|
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
@@ -7,7 +7,7 @@ import { DataTable } from "@voyant-travel/ui/components/data-table";
|
|
|
7
7
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@voyant-travel/ui/components/sheet";
|
|
8
8
|
import { Eye, EyeOff, Loader2, Pencil, Plus, Trash2, Users } from "lucide-react";
|
|
9
9
|
import * as React from "react";
|
|
10
|
-
import { formatMessage, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
|
|
10
|
+
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider.js";
|
|
11
11
|
import { useBookingTravelerDocuments, useRevealTraveler, useTravelerMutation, useTravelers, } from "../index.js";
|
|
12
12
|
import { IconActionButton } from "./icon-action-button.js";
|
|
13
13
|
import { TravelerDialog } from "./traveler-dialog.js";
|
|
@@ -179,11 +179,11 @@ function TravelerDobCell({ bookingId, traveler, revealed, }) {
|
|
|
179
179
|
const person = usePerson(traveler.personId ?? undefined, {
|
|
180
180
|
enabled: Boolean(traveler.personId),
|
|
181
181
|
}).data;
|
|
182
|
-
const messages =
|
|
182
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
183
183
|
if (loading)
|
|
184
184
|
return _jsx(RowLoading, {});
|
|
185
185
|
const dob = travelDetails?.dateOfBirth ?? person?.dateOfBirth ?? null;
|
|
186
|
-
return _jsx(_Fragment, { children: formatDobAge(dob, messages.travelerList.values.fieldUnavailable) });
|
|
186
|
+
return _jsx(_Fragment, { children: formatDobAge(dob, messages.travelerList.values.fieldUnavailable, locale) });
|
|
187
187
|
}
|
|
188
188
|
function RolePills({ traveler }) {
|
|
189
189
|
const messages = useBookingsUiMessagesOrDefault();
|
|
@@ -220,7 +220,7 @@ function getTravelDocumentLabel(travelDetails, messages) {
|
|
|
220
220
|
return messages.travelerDialog.documentTypeLabels[documentType];
|
|
221
221
|
}
|
|
222
222
|
function TravelerSnapshotBody({ bookingId, traveler, documents, }) {
|
|
223
|
-
const messages =
|
|
223
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
224
224
|
const labels = messages.travelerList.snapshot;
|
|
225
225
|
const empty = labels.empty;
|
|
226
226
|
const { display, travelDetails, loading } = useRevealed(bookingId, traveler, true);
|
|
@@ -243,9 +243,9 @@ function TravelerSnapshotBody({ bookingId, traveler, documents, }) {
|
|
|
243
243
|
if (display.travelerCategory) {
|
|
244
244
|
roles.push(messages.travelerDialog.travelerCategoryLabels[display.travelerCategory] ?? display.travelerCategory);
|
|
245
245
|
}
|
|
246
|
-
return (_jsxs("div", { className: "flex-1 overflow-y-auto px-4 pb-4", children: [loading ? (_jsx("div", { className: "py-4", children: _jsx(RowLoading, {}) })) : null, _jsxs(SnapshotSection, { title: labels.sectionContact, children: [_jsx(SnapshotRow, { label: labels.nameLabel, value: fullName }), _jsx(SnapshotRow, { label: labels.emailLabel, value: email }), _jsx(SnapshotRow, { label: labels.phoneLabel, value: phone }), _jsx(SnapshotRow, { label: labels.languageLabel, value: display.preferredLanguage || empty }), _jsx(SnapshotRow, { label: labels.roleLabel, value: roles.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-1.5", children: roles.map((label) => (_jsx(MiniPill, { children: label }, label))) })) : (empty) })] }), _jsxs(SnapshotSection, { title: labels.sectionTravel, children: [_jsx(SnapshotRow, { label: labels.dobLabel, value: formatDobAge(dob, empty) }), _jsx(SnapshotRow, { label: labels.nationalityLabel, value: travelDetails?.nationality || empty }), _jsx(SnapshotRow, { label: labels.documentLabel, value: documentValue }), _jsx(SnapshotRow, { label: labels.documentExpiryLabel, value: formatDateValue(travelDetails?.documentExpiry) ?? empty }), _jsx(SnapshotRow, { label: labels.dietaryLabel, value: travelDetails?.dietaryRequirements || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.accessibilityLabel, value: travelDetails?.accessibilityNeeds || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.specialRequestsLabel, value: display.specialRequests || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.notesLabel, value: display.notes || empty, multiline: true })] }), _jsx(SnapshotSection, { title: labels.sectionDocuments, children: documents.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: labels.noDocuments })) : (documents.map((document) => (_jsx(SnapshotRow, { label: formatMessage(messages.travelerList.context.documentLabel, {
|
|
246
|
+
return (_jsxs("div", { className: "flex-1 overflow-y-auto px-4 pb-4", children: [loading ? (_jsx("div", { className: "py-4", children: _jsx(RowLoading, {}) })) : null, _jsxs(SnapshotSection, { title: labels.sectionContact, children: [_jsx(SnapshotRow, { label: labels.nameLabel, value: fullName }), _jsx(SnapshotRow, { label: labels.emailLabel, value: email }), _jsx(SnapshotRow, { label: labels.phoneLabel, value: phone }), _jsx(SnapshotRow, { label: labels.languageLabel, value: display.preferredLanguage || empty }), _jsx(SnapshotRow, { label: labels.roleLabel, value: roles.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-1.5", children: roles.map((label) => (_jsx(MiniPill, { children: label }, label))) })) : (empty) })] }), _jsxs(SnapshotSection, { title: labels.sectionTravel, children: [_jsx(SnapshotRow, { label: labels.dobLabel, value: formatDobAge(dob, empty, locale) }), _jsx(SnapshotRow, { label: labels.nationalityLabel, value: travelDetails?.nationality || empty }), _jsx(SnapshotRow, { label: labels.documentLabel, value: documentValue }), _jsx(SnapshotRow, { label: labels.documentExpiryLabel, value: formatDateValue(travelDetails?.documentExpiry, locale) ?? empty }), _jsx(SnapshotRow, { label: labels.dietaryLabel, value: travelDetails?.dietaryRequirements || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.accessibilityLabel, value: travelDetails?.accessibilityNeeds || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.specialRequestsLabel, value: display.specialRequests || empty, multiline: true }), _jsx(SnapshotRow, { label: labels.notesLabel, value: display.notes || empty, multiline: true })] }), _jsx(SnapshotSection, { title: labels.sectionDocuments, children: documents.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: labels.noDocuments })) : (documents.map((document) => (_jsx(SnapshotRow, { label: formatMessage(messages.travelerList.context.documentLabel, {
|
|
247
247
|
type: document.type.replaceAll("_", " "),
|
|
248
|
-
}), value: document.fileName }, document.id)))) }), _jsxs(SnapshotSection, { title: labels.sectionMeta, children: [_jsx(SnapshotRow, { label: labels.createdAtLabel, value: formatTimestamp(traveler.createdAt) ?? empty }), _jsx(SnapshotRow, { label: labels.updatedAtLabel, value: formatTimestamp(traveler.updatedAt) ?? empty })] })] }));
|
|
248
|
+
}), value: document.fileName }, document.id)))) }), _jsxs(SnapshotSection, { title: labels.sectionMeta, children: [_jsx(SnapshotRow, { label: labels.createdAtLabel, value: formatTimestamp(traveler.createdAt, locale) ?? empty }), _jsx(SnapshotRow, { label: labels.updatedAtLabel, value: formatTimestamp(traveler.updatedAt, locale) ?? empty })] })] }));
|
|
249
249
|
}
|
|
250
250
|
function SnapshotSection({ title, children }) {
|
|
251
251
|
return (_jsxs("section", { className: "mb-6", children: [_jsx("h3", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: title }), _jsx("dl", { className: "divide-y divide-border rounded-md border", children: children })] }));
|
|
@@ -260,14 +260,14 @@ function RowLoading() {
|
|
|
260
260
|
function MiniPill({ children }) {
|
|
261
261
|
return (_jsx("span", { className: "inline-flex h-5 items-center rounded-full border px-2 text-[11px] capitalize text-muted-foreground", children: children }));
|
|
262
262
|
}
|
|
263
|
-
function formatTimestamp(iso) {
|
|
263
|
+
function formatTimestamp(iso, locale) {
|
|
264
264
|
if (!iso)
|
|
265
265
|
return null;
|
|
266
266
|
const d = new Date(iso);
|
|
267
267
|
if (!Number.isFinite(d.getTime()))
|
|
268
268
|
return null;
|
|
269
269
|
try {
|
|
270
|
-
return d.toLocaleString(
|
|
270
|
+
return d.toLocaleString(locale, {
|
|
271
271
|
day: "numeric",
|
|
272
272
|
month: "short",
|
|
273
273
|
year: "numeric",
|
|
@@ -279,7 +279,7 @@ function formatTimestamp(iso) {
|
|
|
279
279
|
return d.toISOString();
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
|
-
function formatDobAge(value, unavailable) {
|
|
282
|
+
function formatDobAge(value, unavailable, locale) {
|
|
283
283
|
if (!value)
|
|
284
284
|
return unavailable;
|
|
285
285
|
const date = new Date(value);
|
|
@@ -291,15 +291,15 @@ function formatDobAge(value, unavailable) {
|
|
|
291
291
|
(today.getMonth() === date.getMonth() && today.getDate() >= date.getDate());
|
|
292
292
|
if (!birthdayPassed)
|
|
293
293
|
age -= 1;
|
|
294
|
-
return `${formatDateValue(value)} · ${age}`;
|
|
294
|
+
return `${formatDateValue(value, locale)} · ${age}`;
|
|
295
295
|
}
|
|
296
|
-
function formatDateValue(value) {
|
|
296
|
+
function formatDateValue(value, locale) {
|
|
297
297
|
if (!value)
|
|
298
298
|
return null;
|
|
299
299
|
const date = new Date(value);
|
|
300
300
|
if (Number.isNaN(date.getTime()))
|
|
301
301
|
return value;
|
|
302
|
-
return date.toLocaleDateString(
|
|
302
|
+
return date.toLocaleDateString(locale, { month: "short", day: "numeric", year: "numeric" });
|
|
303
303
|
}
|
|
304
304
|
/**
|
|
305
305
|
* Heuristic check for redaction markers used by `redactTravelerIdentity`
|
|
@@ -114,9 +114,10 @@ export declare function getExtrasUiI18n({ locale, overrides, }: {
|
|
|
114
114
|
locale?: string | null | undefined;
|
|
115
115
|
overrides?: ExtrasUiMessageOverrides | null;
|
|
116
116
|
}): PackageI18nValue<ExtrasUiMessages>;
|
|
117
|
-
export declare function ExtrasUiMessagesProvider({ children, locale, overrides, }: {
|
|
117
|
+
export declare function ExtrasUiMessagesProvider({ children, locale, timeZone, overrides, }: {
|
|
118
118
|
children: ReactNode;
|
|
119
119
|
locale: string | null | undefined;
|
|
120
|
+
timeZone?: string | null;
|
|
120
121
|
overrides?: ExtrasUiMessageOverrides | null;
|
|
121
122
|
}): import("react").JSX.Element;
|
|
122
123
|
export declare const useExtrasUiI18n: () => PackageI18nValue<ExtrasUiMessages>;
|
|
@@ -31,8 +31,8 @@ export function getExtrasUiI18n({ locale, overrides, }) {
|
|
|
31
31
|
...createLocaleFormatters(resolvedLocale),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
export function ExtrasUiMessagesProvider({ children, locale, overrides, }) {
|
|
35
|
-
return (_jsx(extrasUiContext.ResolvedMessagesProvider, { definitions: extrasUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
|
|
34
|
+
export function ExtrasUiMessagesProvider({ children, locale, timeZone, overrides, }) {
|
|
35
|
+
return (_jsx(extrasUiContext.ResolvedMessagesProvider, { definitions: extrasUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, timeZone: timeZone, overrides: overrides, children: children }));
|
|
36
36
|
}
|
|
37
37
|
export const useExtrasUiI18n = extrasUiContext.useI18n;
|
|
38
38
|
export const useExtrasUiMessages = extrasUiContext.useMessages;
|
package/dist/i18n/provider.d.ts
CHANGED
|
@@ -3218,9 +3218,10 @@ export declare function getBookingsUiI18n({ locale, overrides, }: {
|
|
|
3218
3218
|
locale?: string | null | undefined;
|
|
3219
3219
|
overrides?: BookingsUiMessageOverrides | null;
|
|
3220
3220
|
}): PackageI18nValue<BookingsUiMessages>;
|
|
3221
|
-
export declare function BookingsUiMessagesProvider({ children, locale, overrides, }: {
|
|
3221
|
+
export declare function BookingsUiMessagesProvider({ children, locale, timeZone, overrides, }: {
|
|
3222
3222
|
children: ReactNode;
|
|
3223
3223
|
locale: string | null | undefined;
|
|
3224
|
+
timeZone?: string | null;
|
|
3224
3225
|
overrides?: BookingsUiMessageOverrides | null;
|
|
3225
3226
|
}): import("react").JSX.Element;
|
|
3226
3227
|
export declare const useBookingsUiI18n: () => PackageI18nValue<BookingsUiMessages>;
|
package/dist/i18n/provider.js
CHANGED
|
@@ -31,8 +31,8 @@ export function getBookingsUiI18n({ locale, overrides, }) {
|
|
|
31
31
|
...createLocaleFormatters(resolvedLocale),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
export function BookingsUiMessagesProvider({ children, locale, overrides, }) {
|
|
35
|
-
return (_jsx(bookingsUiContext.ResolvedMessagesProvider, { definitions: bookingsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
|
|
34
|
+
export function BookingsUiMessagesProvider({ children, locale, timeZone, overrides, }) {
|
|
35
|
+
return (_jsx(bookingsUiContext.ResolvedMessagesProvider, { definitions: bookingsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, timeZone: timeZone, overrides: overrides, children: children }));
|
|
36
36
|
}
|
|
37
37
|
export const useBookingsUiI18n = bookingsUiContext.useI18n;
|
|
38
38
|
export const useBookingsUiMessages = bookingsUiContext.useMessages;
|
|
@@ -13,4 +13,4 @@ export declare function PriceSidePanel({ pricing, isQuoting, invalidReason, enti
|
|
|
13
13
|
* pricing, where they're most in context. */
|
|
14
14
|
pricingExtras?: React.ReactNode;
|
|
15
15
|
}): React.ReactElement;
|
|
16
|
-
export declare function stepHeadline(step: JourneyStep, draft: NonNullable<SidePanelState["draft"]>, messages: ReturnType<typeof useBookingsUiMessagesOrDefault
|
|
16
|
+
export declare function stepHeadline(step: JourneyStep, draft: NonNullable<SidePanelState["draft"]>, messages: ReturnType<typeof useBookingsUiMessagesOrDefault>, locale: string): string;
|
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@voyant-travel/ui/components/accordion";
|
|
5
5
|
import { Card, CardContent } from "@voyant-travel/ui/components/card";
|
|
6
6
|
import { Skeleton } from "@voyant-travel/ui/components/skeleton";
|
|
7
|
-
import { formatMessage, useBookingsUiMessagesOrDefault } from "../../i18n/index.js";
|
|
7
|
+
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../../i18n/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* Right-rail summary panel. Shows what's being booked, an
|
|
10
10
|
* accordion-per-step recap of the user's input, and the live
|
|
@@ -13,7 +13,7 @@ import { formatMessage, useBookingsUiMessagesOrDefault } from "../../i18n/index.
|
|
|
13
13
|
* they've filled in elsewhere.
|
|
14
14
|
*/
|
|
15
15
|
export function PriceSidePanel({ pricing, isQuoting, invalidReason, entitySummary, currentStep, steps, shape, draft, className, pricingExtras, }) {
|
|
16
|
-
const messages =
|
|
16
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
17
17
|
// Only surface pricing once the user has configured what actually drives
|
|
18
18
|
// the price — otherwise the quote's baseline shows a misleading total
|
|
19
19
|
// (e.g. a per-pax "from" price before any room is picked). Room products
|
|
@@ -30,8 +30,8 @@ export function PriceSidePanel({ pricing, isQuoting, invalidReason, entitySummar
|
|
|
30
30
|
: messages.bookingJourney.sidePanel.pricingHint;
|
|
31
31
|
// Departure shows directly under the product title — it's the most-glanced
|
|
32
32
|
// fact, so it doesn't belong buried in the recap accordion.
|
|
33
|
-
const departureText = draft ? stepHeadline("departure", draft, messages) : "";
|
|
34
|
-
return (_jsxs(Card, { className: className, children: [entitySummary?.heroImageUrl ? (_jsx("img", { src: entitySummary.heroImageUrl, alt: entitySummary.name, className: "aspect-video w-full object-cover" })) : null, entitySummary ? (_jsx(EntityHeader, { summary: entitySummary, departureText: departureText })) : null, _jsxs(CardContent, { className: "space-y-4", children: [steps && steps.length > 0 && currentStep && draft ? (_jsx(StepRecap, { steps: steps, currentStep: currentStep, draft: draft })) : null, invalidReason ? _jsx("p", { className: "text-destructive text-sm", children: invalidReason }) : null, !showPricing ? (_jsx("p", { className: "border-t pt-4 text-muted-foreground text-sm", children: pricingHint })) : isQuoting && !pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx(Skeleton, { className: "h-4 w-24" }), _jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "h-4 w-20" })] })) : null, showPricing && pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx("ul", { className: "space-y-1 text-sm", children: pricing.lines.map((line) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [line.label, line.quantity ? (_jsxs("span", { className: "text-muted-foreground", children: [" \u00D7 ", line.quantity] })) : null] }), _jsx("span", { children: formatMoney(line.totalAmount, pricing.currency) })] }, `${line.kind}-${line.label}-${line.totalAmount}`))) }), pricing.taxes.length > 0 ? (_jsx("ul", { className: "space-y-1 border-t pt-2 text-sm text-muted-foreground", children: pricing.taxes.map((tax) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [tax.label, tax.rate > 0 ? ` (${formatTaxRate(tax.rate)})` : ""] }), _jsx("span", { children: formatMoney(tax.amount, pricing.currency) })] }, tax.code))) })) : null, _jsxs("div", { className: "flex justify-between border-t pt-2 font-medium", children: [_jsx("span", { children: messages.bookingJourney.sidePanel.total }), _jsx("span", { children: formatMoney(pricing.total, pricing.currency) })] })] })) : null, pricingExtras ? _jsx("div", { className: "border-t pt-4", children: pricingExtras }) : null] })] }));
|
|
33
|
+
const departureText = draft ? stepHeadline("departure", draft, messages, locale) : "";
|
|
34
|
+
return (_jsxs(Card, { className: className, children: [entitySummary?.heroImageUrl ? (_jsx("img", { src: entitySummary.heroImageUrl, alt: entitySummary.name, className: "aspect-video w-full object-cover" })) : null, entitySummary ? (_jsx(EntityHeader, { summary: entitySummary, departureText: departureText })) : null, _jsxs(CardContent, { className: "space-y-4", children: [steps && steps.length > 0 && currentStep && draft ? (_jsx(StepRecap, { steps: steps, currentStep: currentStep, draft: draft })) : null, invalidReason ? _jsx("p", { className: "text-destructive text-sm", children: invalidReason }) : null, !showPricing ? (_jsx("p", { className: "border-t pt-4 text-muted-foreground text-sm", children: pricingHint })) : isQuoting && !pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx(Skeleton, { className: "h-4 w-24" }), _jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "h-4 w-20" })] })) : null, showPricing && pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx("ul", { className: "space-y-1 text-sm", children: pricing.lines.map((line) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [line.label, line.quantity ? (_jsxs("span", { className: "text-muted-foreground", children: [" \u00D7 ", line.quantity] })) : null] }), _jsx("span", { children: formatMoney(line.totalAmount, pricing.currency, locale) })] }, `${line.kind}-${line.label}-${line.totalAmount}`))) }), pricing.taxes.length > 0 ? (_jsx("ul", { className: "space-y-1 border-t pt-2 text-sm text-muted-foreground", children: pricing.taxes.map((tax) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [tax.label, tax.rate > 0 ? ` (${formatTaxRate(tax.rate)})` : ""] }), _jsx("span", { children: formatMoney(tax.amount, pricing.currency, locale) })] }, tax.code))) })) : null, _jsxs("div", { className: "flex justify-between border-t pt-2 font-medium", children: [_jsx("span", { children: messages.bookingJourney.sidePanel.total }), _jsx("span", { children: formatMoney(pricing.total, pricing.currency, locale) })] })] })) : null, pricingExtras ? _jsx("div", { className: "border-t pt-4", children: pricingExtras }) : null] })] }));
|
|
35
35
|
}
|
|
36
36
|
function EntityHeader({ summary, departureText, }) {
|
|
37
37
|
// Text block only — the hero image is rendered as a direct Card child above.
|
|
@@ -52,21 +52,21 @@ function StepRecap({ steps, currentStep, draft, }) {
|
|
|
52
52
|
return (_jsx(Accordion, { defaultValue: [currentStep], multiple: true, children: recapSteps.map((step) => (_jsxs(AccordionItem, { value: step, children: [_jsx(AccordionTrigger, { className: "py-3", children: _jsxs("div", { className: "flex flex-1 flex-col text-left", children: [_jsx("span", { className: step === currentStep ? "font-semibold" : "text-muted-foreground font-medium", children: stepLabel(step, messages) }), _jsx(StepSummaryLine, { step: step, draft: draft })] }) }), _jsx(AccordionContent, { children: _jsx(StepDetails, { step: step, draft: draft }) })] }, step))) }, currentStep));
|
|
53
53
|
}
|
|
54
54
|
function StepSummaryLine({ step, draft, }) {
|
|
55
|
-
const messages =
|
|
56
|
-
const text = stepHeadline(step, draft, messages);
|
|
55
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
56
|
+
const text = stepHeadline(step, draft, messages, locale);
|
|
57
57
|
if (!text)
|
|
58
58
|
return null;
|
|
59
59
|
return _jsx("span", { className: "text-muted-foreground text-xs", children: text });
|
|
60
60
|
}
|
|
61
|
-
export function stepHeadline(step, draft, messages) {
|
|
61
|
+
export function stepHeadline(step, draft, messages, locale) {
|
|
62
62
|
switch (step) {
|
|
63
63
|
case "departure": {
|
|
64
64
|
const range = draft.configure?.dateRange;
|
|
65
65
|
if (range?.checkIn && range?.checkOut)
|
|
66
|
-
return `${formatConfigureDate(range.checkIn)} → ${formatConfigureDate(range.checkOut)}`;
|
|
66
|
+
return `${formatConfigureDate(range.checkIn, locale)} → ${formatConfigureDate(range.checkOut, locale)}`;
|
|
67
67
|
// Never surface the raw slot id — show the departure date.
|
|
68
68
|
return draft.configure?.departureDate
|
|
69
|
-
? formatConfigureDate(draft.configure.departureDate)
|
|
69
|
+
? formatConfigureDate(draft.configure.departureDate, locale)
|
|
70
70
|
: "";
|
|
71
71
|
}
|
|
72
72
|
case "options": {
|
|
@@ -146,10 +146,10 @@ function StepDetails({ step, draft, }) {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
function DepartureDetails({ draft, }) {
|
|
149
|
-
const messages =
|
|
149
|
+
const { locale, messages } = useBookingsUiI18nOrDefault();
|
|
150
150
|
const cfg = draft.configure ?? {};
|
|
151
151
|
const range = cfg.dateRange;
|
|
152
|
-
return (_jsxs("dl", { className: "space-y-1 text-xs", children: [cfg.departureDate ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.departure, value: formatConfigureDate(cfg.departureDate) })) : null, range?.checkIn ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.checkIn, value: formatConfigureDate(range.checkIn) })) : null, range?.checkOut ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.checkOut, value: formatConfigureDate(range.checkOut) })) : null] }));
|
|
152
|
+
return (_jsxs("dl", { className: "space-y-1 text-xs", children: [cfg.departureDate ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.departure, value: formatConfigureDate(cfg.departureDate, locale) })) : null, range?.checkIn ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.checkIn, value: formatConfigureDate(range.checkIn, locale) })) : null, range?.checkOut ? (_jsx(Row, { label: messages.bookingJourney.sidePanel.checkOut, value: formatConfigureDate(range.checkOut, locale) })) : null] }));
|
|
153
153
|
}
|
|
154
154
|
function OptionsDetails({ draft, }) {
|
|
155
155
|
const messages = useBookingsUiMessagesOrDefault();
|
|
@@ -221,9 +221,9 @@ function stepLabel(step, messages) {
|
|
|
221
221
|
return messages.bookingJourney.steps.billingAndContact;
|
|
222
222
|
return messages.bookingJourney.steps[step];
|
|
223
223
|
}
|
|
224
|
-
function formatMoney(cents, currency) {
|
|
224
|
+
function formatMoney(cents, currency, locale) {
|
|
225
225
|
try {
|
|
226
|
-
return new Intl.NumberFormat(
|
|
226
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(cents / 100);
|
|
227
227
|
}
|
|
228
228
|
catch {
|
|
229
229
|
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
@@ -235,14 +235,14 @@ function formatTaxRate(rate) {
|
|
|
235
235
|
}
|
|
236
236
|
/** Format an ISO date (YYYY-MM-DD or full ISO) for the recap, falling back
|
|
237
237
|
* to the raw value when unparseable. Never surfaces a raw slot id. */
|
|
238
|
-
function formatConfigureDate(iso) {
|
|
238
|
+
function formatConfigureDate(iso, locale) {
|
|
239
239
|
const date = new Date(iso);
|
|
240
240
|
if (Number.isNaN(date.getTime()))
|
|
241
241
|
return iso;
|
|
242
242
|
// Date-only strings parse as UTC midnight; render them in UTC so the
|
|
243
243
|
// calendar date is not shifted for viewers in timezones west of UTC.
|
|
244
244
|
const timeZone = /^\d{4}-\d{2}-\d{2}$/.test(iso) ? "UTC" : undefined;
|
|
245
|
-
return new Intl.DateTimeFormat(
|
|
245
|
+
return new Intl.DateTimeFormat(locale, {
|
|
246
246
|
year: "numeric",
|
|
247
247
|
month: "short",
|
|
248
248
|
day: "numeric",
|
|
@@ -1,113 +1,113 @@
|
|
|
1
1
|
export declare const SELECT_TYPES: Set<string>;
|
|
2
2
|
export declare const QUESTION_TARGETS: readonly [{
|
|
3
3
|
readonly value: "booking";
|
|
4
|
-
readonly label:
|
|
4
|
+
readonly label: string;
|
|
5
5
|
}, {
|
|
6
6
|
readonly value: "traveler";
|
|
7
|
-
readonly label:
|
|
7
|
+
readonly label: string;
|
|
8
8
|
}, {
|
|
9
9
|
readonly value: "lead_traveler";
|
|
10
|
-
readonly label:
|
|
10
|
+
readonly label: string;
|
|
11
11
|
}, {
|
|
12
12
|
readonly value: "booker";
|
|
13
|
-
readonly label:
|
|
13
|
+
readonly label: string;
|
|
14
14
|
}, {
|
|
15
15
|
readonly value: "extra";
|
|
16
|
-
readonly label:
|
|
16
|
+
readonly label: string;
|
|
17
17
|
}, {
|
|
18
18
|
readonly value: "service";
|
|
19
|
-
readonly label:
|
|
19
|
+
readonly label: string;
|
|
20
20
|
}];
|
|
21
21
|
export declare const QUESTION_FIELD_TYPES: readonly [{
|
|
22
22
|
readonly value: "text";
|
|
23
|
-
readonly label:
|
|
23
|
+
readonly label: string;
|
|
24
24
|
}, {
|
|
25
25
|
readonly value: "textarea";
|
|
26
|
-
readonly label:
|
|
26
|
+
readonly label: string;
|
|
27
27
|
}, {
|
|
28
28
|
readonly value: "number";
|
|
29
|
-
readonly label:
|
|
29
|
+
readonly label: string;
|
|
30
30
|
}, {
|
|
31
31
|
readonly value: "email";
|
|
32
|
-
readonly label:
|
|
32
|
+
readonly label: string;
|
|
33
33
|
}, {
|
|
34
34
|
readonly value: "phone";
|
|
35
|
-
readonly label:
|
|
35
|
+
readonly label: string;
|
|
36
36
|
}, {
|
|
37
37
|
readonly value: "date";
|
|
38
|
-
readonly label:
|
|
38
|
+
readonly label: string;
|
|
39
39
|
}, {
|
|
40
40
|
readonly value: "datetime";
|
|
41
|
-
readonly label:
|
|
41
|
+
readonly label: string;
|
|
42
42
|
}, {
|
|
43
43
|
readonly value: "boolean";
|
|
44
|
-
readonly label:
|
|
44
|
+
readonly label: string;
|
|
45
45
|
}, {
|
|
46
46
|
readonly value: "single_select";
|
|
47
|
-
readonly label:
|
|
47
|
+
readonly label: string;
|
|
48
48
|
}, {
|
|
49
49
|
readonly value: "multi_select";
|
|
50
|
-
readonly label:
|
|
50
|
+
readonly label: string;
|
|
51
51
|
}, {
|
|
52
52
|
readonly value: "file";
|
|
53
|
-
readonly label:
|
|
53
|
+
readonly label: string;
|
|
54
54
|
}, {
|
|
55
55
|
readonly value: "country";
|
|
56
|
-
readonly label:
|
|
56
|
+
readonly label: string;
|
|
57
57
|
}, {
|
|
58
58
|
readonly value: "other";
|
|
59
|
-
readonly label:
|
|
59
|
+
readonly label: string;
|
|
60
60
|
}];
|
|
61
61
|
export declare const CONTACT_FIELDS: readonly [{
|
|
62
62
|
readonly value: "first_name";
|
|
63
|
-
readonly label:
|
|
63
|
+
readonly label: string;
|
|
64
64
|
}, {
|
|
65
65
|
readonly value: "last_name";
|
|
66
|
-
readonly label:
|
|
66
|
+
readonly label: string;
|
|
67
67
|
}, {
|
|
68
68
|
readonly value: "email";
|
|
69
|
-
readonly label:
|
|
69
|
+
readonly label: string;
|
|
70
70
|
}, {
|
|
71
71
|
readonly value: "phone";
|
|
72
|
-
readonly label:
|
|
72
|
+
readonly label: string;
|
|
73
73
|
}, {
|
|
74
74
|
readonly value: "date_of_birth";
|
|
75
|
-
readonly label:
|
|
75
|
+
readonly label: string;
|
|
76
76
|
}, {
|
|
77
77
|
readonly value: "nationality";
|
|
78
|
-
readonly label:
|
|
78
|
+
readonly label: string;
|
|
79
79
|
}, {
|
|
80
80
|
readonly value: "passport_number";
|
|
81
|
-
readonly label:
|
|
81
|
+
readonly label: string;
|
|
82
82
|
}, {
|
|
83
83
|
readonly value: "passport_expiry";
|
|
84
|
-
readonly label:
|
|
84
|
+
readonly label: string;
|
|
85
85
|
}, {
|
|
86
86
|
readonly value: "dietary_requirements";
|
|
87
|
-
readonly label:
|
|
87
|
+
readonly label: string;
|
|
88
88
|
}, {
|
|
89
89
|
readonly value: "accessibility_needs";
|
|
90
|
-
readonly label:
|
|
90
|
+
readonly label: string;
|
|
91
91
|
}, {
|
|
92
92
|
readonly value: "special_requests";
|
|
93
|
-
readonly label:
|
|
93
|
+
readonly label: string;
|
|
94
94
|
}, {
|
|
95
95
|
readonly value: "address";
|
|
96
|
-
readonly label:
|
|
96
|
+
readonly label: string;
|
|
97
97
|
}, {
|
|
98
98
|
readonly value: "other";
|
|
99
|
-
readonly label:
|
|
99
|
+
readonly label: string;
|
|
100
100
|
}];
|
|
101
101
|
export declare const CONTACT_SCOPES: readonly [{
|
|
102
102
|
readonly value: "booking";
|
|
103
|
-
readonly label:
|
|
103
|
+
readonly label: string;
|
|
104
104
|
}, {
|
|
105
105
|
readonly value: "lead_traveler";
|
|
106
|
-
readonly label:
|
|
106
|
+
readonly label: string;
|
|
107
107
|
}, {
|
|
108
108
|
readonly value: "traveler";
|
|
109
|
-
readonly label:
|
|
109
|
+
readonly label: string;
|
|
110
110
|
}, {
|
|
111
111
|
readonly value: "booker";
|
|
112
|
-
readonly label:
|
|
112
|
+
readonly label: string;
|
|
113
113
|
}];
|
|
@@ -1,45 +1,47 @@
|
|
|
1
|
+
import { bookingRequirementsUiEn } from "./i18n/en.js";
|
|
2
|
+
const labels = bookingRequirementsUiEn.common;
|
|
1
3
|
export const SELECT_TYPES = new Set(["single_select", "multi_select"]);
|
|
2
4
|
export const QUESTION_TARGETS = [
|
|
3
|
-
{ value: "booking", label:
|
|
4
|
-
{ value: "traveler", label:
|
|
5
|
-
{ value: "lead_traveler", label:
|
|
6
|
-
{ value: "booker", label:
|
|
7
|
-
{ value: "extra", label:
|
|
8
|
-
{ value: "service", label:
|
|
5
|
+
{ value: "booking", label: labels.questionTargetLabels.booking },
|
|
6
|
+
{ value: "traveler", label: labels.questionTargetLabels.traveler },
|
|
7
|
+
{ value: "lead_traveler", label: labels.questionTargetLabels.lead_traveler },
|
|
8
|
+
{ value: "booker", label: labels.questionTargetLabels.booker },
|
|
9
|
+
{ value: "extra", label: labels.questionTargetLabels.extra },
|
|
10
|
+
{ value: "service", label: labels.questionTargetLabels.service },
|
|
9
11
|
];
|
|
10
12
|
export const QUESTION_FIELD_TYPES = [
|
|
11
|
-
{ value: "text", label:
|
|
12
|
-
{ value: "textarea", label:
|
|
13
|
-
{ value: "number", label:
|
|
14
|
-
{ value: "email", label:
|
|
15
|
-
{ value: "phone", label:
|
|
16
|
-
{ value: "date", label:
|
|
17
|
-
{ value: "datetime", label:
|
|
18
|
-
{ value: "boolean", label:
|
|
19
|
-
{ value: "single_select", label:
|
|
20
|
-
{ value: "multi_select", label:
|
|
21
|
-
{ value: "file", label:
|
|
22
|
-
{ value: "country", label:
|
|
23
|
-
{ value: "other", label:
|
|
13
|
+
{ value: "text", label: labels.questionFieldTypeLabels.text },
|
|
14
|
+
{ value: "textarea", label: labels.questionFieldTypeLabels.textarea },
|
|
15
|
+
{ value: "number", label: labels.questionFieldTypeLabels.number },
|
|
16
|
+
{ value: "email", label: labels.questionFieldTypeLabels.email },
|
|
17
|
+
{ value: "phone", label: labels.questionFieldTypeLabels.phone },
|
|
18
|
+
{ value: "date", label: labels.questionFieldTypeLabels.date },
|
|
19
|
+
{ value: "datetime", label: labels.questionFieldTypeLabels.datetime },
|
|
20
|
+
{ value: "boolean", label: labels.questionFieldTypeLabels.boolean },
|
|
21
|
+
{ value: "single_select", label: labels.questionFieldTypeLabels.single_select },
|
|
22
|
+
{ value: "multi_select", label: labels.questionFieldTypeLabels.multi_select },
|
|
23
|
+
{ value: "file", label: labels.questionFieldTypeLabels.file },
|
|
24
|
+
{ value: "country", label: labels.questionFieldTypeLabels.country },
|
|
25
|
+
{ value: "other", label: labels.questionFieldTypeLabels.other },
|
|
24
26
|
];
|
|
25
27
|
export const CONTACT_FIELDS = [
|
|
26
|
-
{ value: "first_name", label:
|
|
27
|
-
{ value: "last_name", label:
|
|
28
|
-
{ value: "email", label:
|
|
29
|
-
{ value: "phone", label:
|
|
30
|
-
{ value: "date_of_birth", label:
|
|
31
|
-
{ value: "nationality", label:
|
|
32
|
-
{ value: "passport_number", label:
|
|
33
|
-
{ value: "passport_expiry", label:
|
|
34
|
-
{ value: "dietary_requirements", label:
|
|
35
|
-
{ value: "accessibility_needs", label:
|
|
36
|
-
{ value: "special_requests", label:
|
|
37
|
-
{ value: "address", label:
|
|
38
|
-
{ value: "other", label:
|
|
28
|
+
{ value: "first_name", label: labels.fieldKeyLabels.first_name },
|
|
29
|
+
{ value: "last_name", label: labels.fieldKeyLabels.last_name },
|
|
30
|
+
{ value: "email", label: labels.fieldKeyLabels.email },
|
|
31
|
+
{ value: "phone", label: labels.fieldKeyLabels.phone },
|
|
32
|
+
{ value: "date_of_birth", label: labels.fieldKeyLabels.date_of_birth },
|
|
33
|
+
{ value: "nationality", label: labels.fieldKeyLabels.nationality },
|
|
34
|
+
{ value: "passport_number", label: labels.fieldKeyLabels.passport_number },
|
|
35
|
+
{ value: "passport_expiry", label: labels.fieldKeyLabels.passport_expiry },
|
|
36
|
+
{ value: "dietary_requirements", label: labels.fieldKeyLabels.dietary_requirements },
|
|
37
|
+
{ value: "accessibility_needs", label: labels.fieldKeyLabels.accessibility_needs },
|
|
38
|
+
{ value: "special_requests", label: labels.fieldKeyLabels.special_requests },
|
|
39
|
+
{ value: "address", label: labels.fieldKeyLabels.address },
|
|
40
|
+
{ value: "other", label: labels.fieldKeyLabels.other },
|
|
39
41
|
];
|
|
40
42
|
export const CONTACT_SCOPES = [
|
|
41
|
-
{ value: "booking", label:
|
|
42
|
-
{ value: "lead_traveler", label:
|
|
43
|
-
{ value: "traveler", label:
|
|
44
|
-
{ value: "booker", label:
|
|
43
|
+
{ value: "booking", label: labels.scopeLabels.booking },
|
|
44
|
+
{ value: "lead_traveler", label: labels.scopeLabels.lead_traveler },
|
|
45
|
+
{ value: "traveler", label: labels.scopeLabels.traveler },
|
|
46
|
+
{ value: "booker", label: labels.scopeLabels.booker },
|
|
45
47
|
];
|
|
@@ -10,7 +10,7 @@ export declare function useBookingQuestions(options?: UseBookingQuestionsOptions
|
|
|
10
10
|
label: string;
|
|
11
11
|
description: string | null;
|
|
12
12
|
target: "extra" | "service" | "traveler" | "booking" | "lead_traveler" | "booker";
|
|
13
|
-
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "
|
|
13
|
+
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
|
|
14
14
|
placeholder: string | null;
|
|
15
15
|
helpText: string | null;
|
|
16
16
|
isRequired: boolean;
|
|
@@ -198,9 +198,10 @@ export declare function getBookingRequirementsUiI18n({ locale, overrides, }: {
|
|
|
198
198
|
locale?: string | null | undefined;
|
|
199
199
|
overrides?: BookingRequirementsUiMessageOverrides | null;
|
|
200
200
|
}): PackageI18nValue<BookingRequirementsUiMessages>;
|
|
201
|
-
export declare function BookingRequirementsUiMessagesProvider({ children, locale, overrides, }: {
|
|
201
|
+
export declare function BookingRequirementsUiMessagesProvider({ children, locale, timeZone, overrides, }: {
|
|
202
202
|
children: ReactNode;
|
|
203
203
|
locale: string | null | undefined;
|
|
204
|
+
timeZone?: string | null;
|
|
204
205
|
overrides?: BookingRequirementsUiMessageOverrides | null;
|
|
205
206
|
}): import("react").JSX.Element;
|
|
206
207
|
export declare const useBookingRequirementsUiI18n: () => PackageI18nValue<BookingRequirementsUiMessages>;
|
|
@@ -31,8 +31,8 @@ export function getBookingRequirementsUiI18n({ locale, overrides, }) {
|
|
|
31
31
|
...createLocaleFormatters(resolvedLocale),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
export function BookingRequirementsUiMessagesProvider({ children, locale, overrides, }) {
|
|
35
|
-
return (_jsx(bookingRequirementsUiContext.ResolvedMessagesProvider, { definitions: bookingRequirementsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
|
|
34
|
+
export function BookingRequirementsUiMessagesProvider({ children, locale, timeZone, overrides, }) {
|
|
35
|
+
return (_jsx(bookingRequirementsUiContext.ResolvedMessagesProvider, { definitions: bookingRequirementsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, timeZone: timeZone, overrides: overrides, children: children }));
|
|
36
36
|
}
|
|
37
37
|
export const useBookingRequirementsUiI18n = bookingRequirementsUiContext.useI18n;
|
|
38
38
|
export const useBookingRequirementsUiMessages = bookingRequirementsUiContext.useMessages;
|
|
@@ -132,7 +132,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
|
|
|
132
132
|
label: string;
|
|
133
133
|
description: string | null;
|
|
134
134
|
target: "extra" | "service" | "traveler" | "booking" | "lead_traveler" | "booker";
|
|
135
|
-
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "
|
|
135
|
+
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
|
|
136
136
|
placeholder: string | null;
|
|
137
137
|
helpText: string | null;
|
|
138
138
|
isRequired: boolean;
|
|
@@ -150,7 +150,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
|
|
|
150
150
|
label: string;
|
|
151
151
|
description: string | null;
|
|
152
152
|
target: "extra" | "service" | "traveler" | "booking" | "lead_traveler" | "booker";
|
|
153
|
-
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "
|
|
153
|
+
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
|
|
154
154
|
placeholder: string | null;
|
|
155
155
|
helpText: string | null;
|
|
156
156
|
isRequired: boolean;
|
|
@@ -169,7 +169,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
|
|
|
169
169
|
label: string;
|
|
170
170
|
description: string | null;
|
|
171
171
|
target: "extra" | "service" | "traveler" | "booking" | "lead_traveler" | "booker";
|
|
172
|
-
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "
|
|
172
|
+
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
|
|
173
173
|
placeholder: string | null;
|
|
174
174
|
helpText: string | null;
|
|
175
175
|
isRequired: boolean;
|
|
@@ -190,7 +190,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
|
|
|
190
190
|
label: string;
|
|
191
191
|
description: string | null;
|
|
192
192
|
target: "extra" | "service" | "traveler" | "booking" | "lead_traveler" | "booker";
|
|
193
|
-
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "
|
|
193
|
+
fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
|
|
194
194
|
placeholder: string | null;
|
|
195
195
|
helpText: string | null;
|
|
196
196
|
isRequired: boolean;
|
|
@@ -26,9 +26,9 @@ export declare const questionFieldTypeSchema: z.ZodEnum<{
|
|
|
26
26
|
phone: "phone";
|
|
27
27
|
textarea: "textarea";
|
|
28
28
|
text: "text";
|
|
29
|
+
datetime: "datetime";
|
|
29
30
|
single_select: "single_select";
|
|
30
31
|
multi_select: "multi_select";
|
|
31
|
-
datetime: "datetime";
|
|
32
32
|
country: "country";
|
|
33
33
|
}>;
|
|
34
34
|
export declare const bookingQuestionSchema: z.ZodObject<{
|
|
@@ -55,9 +55,9 @@ export declare const bookingQuestionSchema: z.ZodObject<{
|
|
|
55
55
|
phone: "phone";
|
|
56
56
|
textarea: "textarea";
|
|
57
57
|
text: "text";
|
|
58
|
+
datetime: "datetime";
|
|
58
59
|
single_select: "single_select";
|
|
59
60
|
multi_select: "multi_select";
|
|
60
|
-
datetime: "datetime";
|
|
61
61
|
country: "country";
|
|
62
62
|
}>;
|
|
63
63
|
placeholder: z.ZodNullable<z.ZodString>;
|
|
@@ -166,9 +166,9 @@ export declare const bookingQuestionListResponse: z.ZodObject<{
|
|
|
166
166
|
phone: "phone";
|
|
167
167
|
textarea: "textarea";
|
|
168
168
|
text: "text";
|
|
169
|
+
datetime: "datetime";
|
|
169
170
|
single_select: "single_select";
|
|
170
171
|
multi_select: "multi_select";
|
|
171
|
-
datetime: "datetime";
|
|
172
172
|
country: "country";
|
|
173
173
|
}>;
|
|
174
174
|
placeholder: z.ZodNullable<z.ZodString>;
|
|
@@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
3
3
|
import { useStorefrontUi } from "@voyant-travel/storefront-react/storefront";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { useBookingsUiI18nOrDefault } from "../i18n/provider.js";
|
|
6
7
|
import { StorefrontBookingJourney, } from "./storefront-booking-journey.js";
|
|
7
8
|
export const storefrontBookingSearchSchema = z.object({
|
|
8
9
|
departureSlotId: z.string().optional(),
|
|
@@ -76,6 +77,7 @@ function buildInitialAccommodation(search) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
function useEntityContent(apiUrl, entityModule, entityId, search) {
|
|
80
|
+
const { locale } = useBookingsUiI18nOrDefault();
|
|
79
81
|
const url = entityContentUrl(apiUrl, entityModule, entityId);
|
|
80
82
|
const { data } = useQuery({
|
|
81
83
|
queryKey: ["public-entity-summary", entityModule, entityId],
|
|
@@ -93,7 +95,7 @@ function useEntityContent(apiUrl, entityModule, entityId, search) {
|
|
|
93
95
|
});
|
|
94
96
|
const content = data?.content ?? null;
|
|
95
97
|
const source = useMemo(() => resolveEntitySource(entityModule, data?.provenance, content), [entityModule, data?.provenance, content]);
|
|
96
|
-
const summary = useMemo(() => resolveEntitySummary(entityModule, content, search), [content, entityModule, search]);
|
|
98
|
+
const summary = useMemo(() => resolveEntitySummary(entityModule, content, search, locale), [content, entityModule, search, locale]);
|
|
97
99
|
return { summary, source };
|
|
98
100
|
}
|
|
99
101
|
function entityContentUrl(apiUrl, entityModule, entityId) {
|
|
@@ -107,19 +109,19 @@ function entityContentUrl(apiUrl, entityModule, entityId) {
|
|
|
107
109
|
return `${apiUrl}/v1/public/products/${encodedId}/content`;
|
|
108
110
|
return null;
|
|
109
111
|
}
|
|
110
|
-
function resolveEntitySummary(entityModule, content, search) {
|
|
112
|
+
function resolveEntitySummary(entityModule, content, search, locale) {
|
|
111
113
|
if (!content)
|
|
112
114
|
return undefined;
|
|
113
115
|
if (entityModule === "products")
|
|
114
|
-
return resolveProductSummary(content, search);
|
|
116
|
+
return resolveProductSummary(content, search, locale);
|
|
115
117
|
if (entityModule === "cruises")
|
|
116
|
-
return resolveCruiseSummary(content, search);
|
|
118
|
+
return resolveCruiseSummary(content, search, locale);
|
|
117
119
|
if (entityModule === "accommodations") {
|
|
118
|
-
return resolveAccommodationSummary(content, search);
|
|
120
|
+
return resolveAccommodationSummary(content, search, locale);
|
|
119
121
|
}
|
|
120
122
|
return undefined;
|
|
121
123
|
}
|
|
122
|
-
function resolveProductSummary(content, search) {
|
|
124
|
+
function resolveProductSummary(content, search, locale) {
|
|
123
125
|
const subtitle = [
|
|
124
126
|
content.product.duration_days
|
|
125
127
|
? `${content.product.duration_days} day${content.product.duration_days === 1 ? "" : "s"}`
|
|
@@ -132,14 +134,14 @@ function resolveProductSummary(content, search) {
|
|
|
132
134
|
subtitle: subtitle.join(" · ") || undefined,
|
|
133
135
|
heroImageUrl: content.product.hero_image_url ?? content.media?.[0]?.url ?? undefined,
|
|
134
136
|
vertical: "products",
|
|
135
|
-
whenLabel: departure ? formatDate(departure.starts_at) : undefined,
|
|
137
|
+
whenLabel: departure ? formatDate(departure.starts_at, locale) : undefined,
|
|
136
138
|
locationLabel: content.product.departure_city ?? content.product.country ?? undefined,
|
|
137
139
|
startDate: departure?.starts_at ?? undefined,
|
|
138
140
|
endDate: departure?.ends_at ?? undefined,
|
|
139
141
|
destination: content.product.country ?? content.product.departure_city ?? undefined,
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
|
-
function resolveCruiseSummary(content, search) {
|
|
144
|
+
function resolveCruiseSummary(content, search, locale) {
|
|
143
145
|
const sailing = content.sailings.find((item) => item.id === search.departureSlotId);
|
|
144
146
|
const subtitle = [
|
|
145
147
|
content.cruise.duration_nights
|
|
@@ -157,14 +159,14 @@ function resolveCruiseSummary(content, search) {
|
|
|
157
159
|
subtitle: subtitle.join(" · ") || undefined,
|
|
158
160
|
heroImageUrl: content.cruise.hero_image_url ?? undefined,
|
|
159
161
|
vertical: "cruises",
|
|
160
|
-
whenLabel: sailing ? formatDate(sailing.start_date) : undefined,
|
|
162
|
+
whenLabel: sailing ? formatDate(sailing.start_date, locale) : undefined,
|
|
161
163
|
locationLabel: route ?? undefined,
|
|
162
164
|
startDate: sailing?.start_date ?? undefined,
|
|
163
165
|
endDate: sailing?.end_date ?? undefined,
|
|
164
166
|
destination: route ?? undefined,
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
|
-
function resolveAccommodationSummary(content, search) {
|
|
169
|
+
function resolveAccommodationSummary(content, search, locale) {
|
|
168
170
|
const stars = content.hotel.star_rating ? "★".repeat(Math.floor(content.hotel.star_rating)) : null;
|
|
169
171
|
return {
|
|
170
172
|
name: content.hotel.name,
|
|
@@ -172,7 +174,7 @@ function resolveAccommodationSummary(content, search) {
|
|
|
172
174
|
heroImageUrl: content.hotel.hero_image_url ?? undefined,
|
|
173
175
|
vertical: "accommodations",
|
|
174
176
|
whenLabel: search.checkIn && search.checkOut
|
|
175
|
-
? `${formatDate(search.checkIn)} → ${formatDate(search.checkOut)}`
|
|
177
|
+
? `${formatDate(search.checkIn, locale)} → ${formatDate(search.checkOut, locale)}`
|
|
176
178
|
: undefined,
|
|
177
179
|
startDate: search.checkIn ?? undefined,
|
|
178
180
|
endDate: search.checkOut ?? undefined,
|
|
@@ -192,9 +194,9 @@ function resolveEntitySource(entityModule, provenance, content) {
|
|
|
192
194
|
supplier: { id: "", name: supplierName },
|
|
193
195
|
};
|
|
194
196
|
}
|
|
195
|
-
function formatDate(iso) {
|
|
197
|
+
function formatDate(iso, locale) {
|
|
196
198
|
try {
|
|
197
|
-
return new Date(iso).toLocaleDateString(
|
|
199
|
+
return new Date(iso).toLocaleDateString(locale, {
|
|
198
200
|
weekday: "short",
|
|
199
201
|
day: "numeric",
|
|
200
202
|
month: "short",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/bookings-react",
|
|
3
|
-
"version": "0.162.
|
|
3
|
+
"version": "0.162.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -151,23 +151,23 @@
|
|
|
151
151
|
"react-hook-form": "^7.80.0",
|
|
152
152
|
"zod": "^4.0.0",
|
|
153
153
|
"@voyant-travel/accommodations": "^0.122.0",
|
|
154
|
-
"@voyant-travel/admin": "^0.126.
|
|
155
|
-
"@voyant-travel/bookings": "^0.162.
|
|
156
|
-
"@voyant-travel/catalog": "^0.160.
|
|
157
|
-
"@voyant-travel/catalog-react": "^0.160.
|
|
158
|
-
"@voyant-travel/distribution-react": "^0.152.
|
|
159
|
-
"@voyant-travel/relationships-react": "^0.162.
|
|
160
|
-
"@voyant-travel/finance": "^0.162.
|
|
161
|
-
"@voyant-travel/finance-react": "^0.162.
|
|
162
|
-
"@voyant-travel/identity-react": "^0.162.
|
|
163
|
-
"@voyant-travel/legal-react": "^0.162.
|
|
164
|
-
"@voyant-travel/commerce-react": "^0.44.
|
|
165
|
-
"@voyant-travel/cruises": "^0.161.
|
|
166
|
-
"@voyant-travel/inventory-react": "^0.44.
|
|
154
|
+
"@voyant-travel/admin": "^0.126.1",
|
|
155
|
+
"@voyant-travel/bookings": "^0.162.2",
|
|
156
|
+
"@voyant-travel/catalog": "^0.160.1",
|
|
157
|
+
"@voyant-travel/catalog-react": "^0.160.1",
|
|
158
|
+
"@voyant-travel/distribution-react": "^0.152.1",
|
|
159
|
+
"@voyant-travel/relationships-react": "^0.162.1",
|
|
160
|
+
"@voyant-travel/finance": "^0.162.2",
|
|
161
|
+
"@voyant-travel/finance-react": "^0.162.2",
|
|
162
|
+
"@voyant-travel/identity-react": "^0.162.1",
|
|
163
|
+
"@voyant-travel/legal-react": "^0.162.2",
|
|
164
|
+
"@voyant-travel/commerce-react": "^0.44.1",
|
|
165
|
+
"@voyant-travel/cruises": "^0.161.1",
|
|
166
|
+
"@voyant-travel/inventory-react": "^0.44.1",
|
|
167
167
|
"@voyant-travel/inventory": "^0.12.1",
|
|
168
168
|
"@voyant-travel/storefront-react": "^0.164.0",
|
|
169
169
|
"@voyant-travel/ui": "^0.109.2",
|
|
170
|
-
"@voyant-travel/operations-react": "^0.43.
|
|
170
|
+
"@voyant-travel/operations-react": "^0.43.1"
|
|
171
171
|
},
|
|
172
172
|
"peerDependenciesMeta": {
|
|
173
173
|
"@tanstack/react-table": {
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
"dependencies": {
|
|
235
235
|
"sonner": "^2.0.7",
|
|
236
236
|
"@voyant-travel/catalog-contracts": "^0.111.1",
|
|
237
|
-
"@voyant-travel/i18n": "^0.111.
|
|
237
|
+
"@voyant-travel/i18n": "^0.111.3",
|
|
238
238
|
"@voyant-travel/react": "^0.104.2",
|
|
239
239
|
"@voyant-travel/types": "^0.109.2"
|
|
240
240
|
},
|
|
@@ -252,25 +252,25 @@
|
|
|
252
252
|
"vitest": "^4.1.9",
|
|
253
253
|
"zod": "^4.4.3",
|
|
254
254
|
"@voyant-travel/accommodations": "^0.122.0",
|
|
255
|
-
"@voyant-travel/admin": "^0.126.
|
|
256
|
-
"@voyant-travel/bookings": "^0.162.
|
|
257
|
-
"@voyant-travel/catalog": "^0.160.
|
|
258
|
-
"@voyant-travel/catalog-react": "^0.160.
|
|
259
|
-
"@voyant-travel/distribution-react": "^0.152.
|
|
260
|
-
"@voyant-travel/relationships-react": "^0.162.
|
|
261
|
-
"@voyant-travel/finance": "^0.162.
|
|
262
|
-
"@voyant-travel/finance-react": "^0.162.
|
|
263
|
-
"@voyant-travel/identity-react": "^0.162.
|
|
264
|
-
"@voyant-travel/legal-react": "^0.162.
|
|
265
|
-
"@voyant-travel/commerce-react": "^0.44.
|
|
266
|
-
"@voyant-travel/cruises": "^0.161.
|
|
267
|
-
"@voyant-travel/inventory-react": "^0.44.
|
|
255
|
+
"@voyant-travel/admin": "^0.126.1",
|
|
256
|
+
"@voyant-travel/bookings": "^0.162.2",
|
|
257
|
+
"@voyant-travel/catalog": "^0.160.1",
|
|
258
|
+
"@voyant-travel/catalog-react": "^0.160.1",
|
|
259
|
+
"@voyant-travel/distribution-react": "^0.152.1",
|
|
260
|
+
"@voyant-travel/relationships-react": "^0.162.1",
|
|
261
|
+
"@voyant-travel/finance": "^0.162.2",
|
|
262
|
+
"@voyant-travel/finance-react": "^0.162.2",
|
|
263
|
+
"@voyant-travel/identity-react": "^0.162.1",
|
|
264
|
+
"@voyant-travel/legal-react": "^0.162.2",
|
|
265
|
+
"@voyant-travel/commerce-react": "^0.44.1",
|
|
266
|
+
"@voyant-travel/cruises": "^0.161.1",
|
|
267
|
+
"@voyant-travel/inventory-react": "^0.44.1",
|
|
268
268
|
"@voyant-travel/inventory": "^0.12.1",
|
|
269
269
|
"@voyant-travel/react": "^0.104.2",
|
|
270
270
|
"@voyant-travel/storefront-react": "^0.164.0",
|
|
271
271
|
"@voyant-travel/ui": "^0.109.2",
|
|
272
272
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
273
|
-
"@voyant-travel/operations-react": "^0.43.
|
|
273
|
+
"@voyant-travel/operations-react": "^0.43.1"
|
|
274
274
|
},
|
|
275
275
|
"files": [
|
|
276
276
|
"dist",
|