@voyant-travel/trips-react 0.110.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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/admin/admin-trips-page-controls.d.ts +28 -0
- package/dist/admin/admin-trips-page-controls.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-controls.js +28 -0
- package/dist/admin/admin-trips-page-model.d.ts +87 -0
- package/dist/admin/admin-trips-page-model.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-model.js +457 -0
- package/dist/admin/admin-trips-page.d.ts +6 -0
- package/dist/admin/admin-trips-page.d.ts.map +1 -0
- package/dist/admin/admin-trips-page.js +322 -0
- package/dist/admin/admin-trips-panels.d.ts +11 -0
- package/dist/admin/admin-trips-panels.d.ts.map +1 -0
- package/dist/admin/admin-trips-panels.js +11 -0
- package/dist/admin/index.d.ts +63 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +119 -0
- package/dist/admin/pages/trip-detail-page.d.ts +10 -0
- package/dist/admin/pages/trip-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/trip-detail-page.js +12 -0
- package/dist/admin/trip-component-display.d.ts +10 -0
- package/dist/admin/trip-component-display.d.ts.map +1 -0
- package/dist/admin/trip-component-display.js +137 -0
- package/dist/admin/trip-detail-host.d.ts +12 -0
- package/dist/admin/trip-detail-host.d.ts.map +1 -0
- package/dist/admin/trip-detail-host.js +37 -0
- package/dist/admin/trip-detail-record-model.d.ts +30 -0
- package/dist/admin/trip-detail-record-model.d.ts.map +1 -0
- package/dist/admin/trip-detail-record-model.js +56 -0
- package/dist/admin/trip-detail-record.d.ts +47 -0
- package/dist/admin/trip-detail-record.d.ts.map +1 -0
- package/dist/admin/trip-detail-record.js +170 -0
- package/dist/admin/trip-list-filters.d.ts +33 -0
- package/dist/admin/trip-list-filters.d.ts.map +1 -0
- package/dist/admin/trip-list-filters.js +94 -0
- package/dist/admin/trips-host.d.ts +15 -0
- package/dist/admin/trips-host.d.ts.map +1 -0
- package/dist/admin/trips-host.js +233 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-configurator.js +200 -0
- package/dist/admin/trips-panels/catalog-options.d.ts +58 -0
- package/dist/admin/trips-panels/catalog-options.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-options.js +124 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts +27 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/committed-component-card.js +44 -0
- package/dist/admin/trips-panels/display.d.ts +51 -0
- package/dist/admin/trips-panels/display.d.ts.map +1 -0
- package/dist/admin/trips-panels/display.js +336 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/flight-configurator.js +208 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts +16 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts.map +1 -0
- package/dist/admin/trips-panels/manual-configurators.js +41 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts +16 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/pending-component-card.js +29 -0
- package/dist/admin/trips-panels/shared.d.ts +122 -0
- package/dist/admin/trips-panels/shared.d.ts.map +1 -0
- package/dist/admin/trips-panels/shared.js +152 -0
- package/dist/admin/trips-panels/travelers-section.d.ts +53 -0
- package/dist/admin/trips-panels/travelers-section.d.ts.map +1 -0
- package/dist/admin/trips-panels/travelers-section.js +183 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts +52 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts.map +1 -0
- package/dist/admin/trips-panels/trip-preview-rail.js +122 -0
- package/dist/cache.d.ts +9 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +21 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-price-trip.d.ts +3 -0
- package/dist/hooks/use-price-trip.d.ts.map +1 -0
- package/dist/hooks/use-price-trip.js +17 -0
- package/dist/hooks/use-reserve-trip.d.ts +3 -0
- package/dist/hooks/use-reserve-trip.d.ts.map +1 -0
- package/dist/hooks/use-reserve-trip.js +17 -0
- package/dist/hooks/use-trip-checkout.d.ts +3 -0
- package/dist/hooks/use-trip-checkout.d.ts.map +1 -0
- package/dist/hooks/use-trip-checkout.js +17 -0
- package/dist/hooks/use-trip-components.d.ts +40 -0
- package/dist/hooks/use-trip-components.d.ts.map +1 -0
- package/dist/hooks/use-trip-components.js +13 -0
- package/dist/hooks/use-trip.d.ts +5 -0
- package/dist/hooks/use-trip.d.ts.map +1 -0
- package/dist/hooks/use-trip.js +13 -0
- package/dist/hooks/use-trips.d.ts +6 -0
- package/dist/hooks/use-trips.d.ts.map +1 -0
- package/dist/hooks/use-trips.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/operations.d.ts +212 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +92 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/query-keys.d.ts +10 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +9 -0
- package/dist/query-options.d.ts +167 -0
- package/dist/query-options.d.ts.map +1 -0
- package/dist/query-options.js +22 -0
- package/dist/schemas.d.ts +69 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +16 -0
- package/package.json +133 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { emptyPersonPickerValue } from "@voyant-travel/bookings-react/components/person-picker-section";
|
|
2
|
+
import { emptyVoucherPickerValue } from "@voyant-travel/bookings-react/components/voucher-picker-section";
|
|
3
|
+
import { formatMessage } from "@voyant-travel/i18n";
|
|
4
|
+
import { computePlaceholderTotals, flightPricingFromPending, } from "./admin-trips-panels.js";
|
|
5
|
+
export const defaultPaymentCurrency = "EUR";
|
|
6
|
+
export function metadataWithComponentBookingSetup(component, setup) {
|
|
7
|
+
const metadata = { ...(readRecord(component.metadata) ?? {}) };
|
|
8
|
+
const bookingDraft = { ...(readRecord(metadata.bookingDraftV1) ?? {}) };
|
|
9
|
+
const documentGeneration = {
|
|
10
|
+
contractDocument: setup.generateContractDocument,
|
|
11
|
+
invoiceDocument: setup.generateInvoiceDocument,
|
|
12
|
+
};
|
|
13
|
+
metadata.bookingSetup = {
|
|
14
|
+
paymentSchedule: setup.paymentSchedule,
|
|
15
|
+
documentGeneration,
|
|
16
|
+
};
|
|
17
|
+
metadata.bookingDraftV1 = {
|
|
18
|
+
...bookingDraft,
|
|
19
|
+
paymentSchedules: paymentScheduleToRows(setup.paymentSchedule, component.componentCurrency || defaultPaymentCurrency, component.componentTotalAmountCents ?? null),
|
|
20
|
+
documentGeneration,
|
|
21
|
+
};
|
|
22
|
+
return metadata;
|
|
23
|
+
}
|
|
24
|
+
export function paymentScheduleToRows(value, scheduleCurrency, totalAmountCents) {
|
|
25
|
+
if (value.mode === "full") {
|
|
26
|
+
const installment = value.installments[0];
|
|
27
|
+
if (!installment?.dueDate || totalAmountCents === null)
|
|
28
|
+
return [];
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
scheduleType: "balance",
|
|
32
|
+
status: installment.alreadyPaid ? "paid" : "due",
|
|
33
|
+
dueDate: installment.dueDate,
|
|
34
|
+
currency: scheduleCurrency,
|
|
35
|
+
amountCents: totalAmountCents,
|
|
36
|
+
notes: paidScheduleNotes(installment.alreadyPaid, installment.paymentDate, installment.paymentMethod, installment.paymentReference),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const rows = [];
|
|
41
|
+
for (const installment of value.installments) {
|
|
42
|
+
if (!installment.dueDate || installment.amountCents == null)
|
|
43
|
+
continue;
|
|
44
|
+
rows.push({
|
|
45
|
+
scheduleType: "installment",
|
|
46
|
+
status: installment.alreadyPaid ? "paid" : "due",
|
|
47
|
+
dueDate: installment.dueDate,
|
|
48
|
+
currency: scheduleCurrency,
|
|
49
|
+
amountCents: installment.amountCents,
|
|
50
|
+
notes: paidScheduleNotes(installment.alreadyPaid, installment.paymentDate, installment.paymentMethod, installment.paymentReference),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
// Returns a single-line audit note persisted on the booking's payment schedule
|
|
56
|
+
// when the operator marks an installment as already-paid in the composer.
|
|
57
|
+
// Operator-facing free text — kept terse and in English at the data layer so
|
|
58
|
+
// the persisted note stays comparable across deploys / locales.
|
|
59
|
+
export function paidScheduleNotes(alreadyPaid, paymentDate, paymentMethod, paymentReference) {
|
|
60
|
+
if (!alreadyPaid)
|
|
61
|
+
return null;
|
|
62
|
+
return [
|
|
63
|
+
// i18n-literal-ok: persisted audit note, see comment above.
|
|
64
|
+
"Marked paid in trips",
|
|
65
|
+
paymentDate ? `date: ${paymentDate}` : null,
|
|
66
|
+
paymentMethod ? `method: ${paymentMethod}` : null,
|
|
67
|
+
paymentReference.trim() ? `reference: ${paymentReference.trim()}` : null,
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join("; ");
|
|
71
|
+
}
|
|
72
|
+
export function pendingToAddInput(pending, ctx, messages) {
|
|
73
|
+
const billingPayload = serializeBilling(ctx.billing, ctx.payerName, ctx.payerEmail);
|
|
74
|
+
const travelersPayload = serializeTravelersForBookingDraft(ctx.travelers, messages);
|
|
75
|
+
const paxAdult = countAdults(ctx.travelers) || 1;
|
|
76
|
+
if (pending.kind === "product" || pending.kind === "stay") {
|
|
77
|
+
if (!pending.catalogEntityId || !pending.catalogSourceKind)
|
|
78
|
+
return null;
|
|
79
|
+
const vertical = pending.kind === "stay" ? "accommodations" : "products";
|
|
80
|
+
const draft = pending.bookingDraft;
|
|
81
|
+
const configure = {
|
|
82
|
+
...(draft?.configure ?? {}),
|
|
83
|
+
pax: {
|
|
84
|
+
...(draft?.configure.pax ?? {}),
|
|
85
|
+
adult: paxAdult,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
if (pending.startsAt) {
|
|
89
|
+
configure.departureDate = pending.startsAt.slice(0, 10);
|
|
90
|
+
}
|
|
91
|
+
if (pending.startsAt && pending.endsAt) {
|
|
92
|
+
configure.dateRange = {
|
|
93
|
+
checkIn: pending.startsAt.slice(0, 10),
|
|
94
|
+
checkOut: pending.endsAt.slice(0, 10),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
kind: "catalog_booking",
|
|
99
|
+
catalogRef: {
|
|
100
|
+
entityModule: vertical,
|
|
101
|
+
entityId: pending.catalogEntityId,
|
|
102
|
+
sourceKind: pending.catalogSourceKind,
|
|
103
|
+
...(pending.catalogSourceConnectionId
|
|
104
|
+
? { sourceConnectionId: pending.catalogSourceConnectionId }
|
|
105
|
+
: {}),
|
|
106
|
+
...(pending.catalogSourceRef ? { sourceRef: pending.catalogSourceRef } : {}),
|
|
107
|
+
},
|
|
108
|
+
metadata: {
|
|
109
|
+
scheduledStartsAt: pending.startsAt || null,
|
|
110
|
+
scheduledEndsAt: pending.endsAt || null,
|
|
111
|
+
catalogItem: {
|
|
112
|
+
vertical,
|
|
113
|
+
name: pending.catalogEntityName,
|
|
114
|
+
thumbnailUrl: pending.catalogThumbnailUrl,
|
|
115
|
+
sourceKind: pending.catalogSourceKind,
|
|
116
|
+
sourceConnectionId: pending.catalogSourceConnectionId,
|
|
117
|
+
sourceRef: pending.catalogSourceRef,
|
|
118
|
+
},
|
|
119
|
+
bookingDraftV1: {
|
|
120
|
+
...(draft ?? {}),
|
|
121
|
+
entity: draft?.entity ?? {
|
|
122
|
+
module: vertical,
|
|
123
|
+
id: pending.catalogEntityId,
|
|
124
|
+
sourceKind: pending.catalogSourceKind,
|
|
125
|
+
...(pending.catalogSourceConnectionId
|
|
126
|
+
? { sourceConnectionId: pending.catalogSourceConnectionId }
|
|
127
|
+
: {}),
|
|
128
|
+
...(pending.catalogSourceRef ? { sourceRef: pending.catalogSourceRef } : {}),
|
|
129
|
+
},
|
|
130
|
+
configure,
|
|
131
|
+
billing: billingPayload,
|
|
132
|
+
travelers: travelersPayload,
|
|
133
|
+
payment: draft?.payment ?? { intent: "hold" },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (pending.kind === "flight") {
|
|
139
|
+
const pricing = flightPricingFromPending(pending);
|
|
140
|
+
const firstItinerary = pending.selectedOffer?.itineraries[0];
|
|
141
|
+
const lastItinerary = pending.selectedOffer?.itineraries[pending.selectedOffer.itineraries.length - 1];
|
|
142
|
+
const firstSegment = firstItinerary?.segments[0];
|
|
143
|
+
const lastSegment = lastItinerary?.segments[lastItinerary.segments.length - 1];
|
|
144
|
+
return {
|
|
145
|
+
kind: "flight_placeholder",
|
|
146
|
+
description: undefined,
|
|
147
|
+
estimatedPricing: {
|
|
148
|
+
currency: pricing.currency,
|
|
149
|
+
subtotalAmountCents: pricing.subtotalAmountCents,
|
|
150
|
+
taxAmountCents: pricing.taxAmountCents,
|
|
151
|
+
totalAmountCents: pricing.totalAmountCents,
|
|
152
|
+
},
|
|
153
|
+
metadata: {
|
|
154
|
+
scheduledStartsAt: firstSegment?.departure.at ?? pending.departDate ?? null,
|
|
155
|
+
scheduledEndsAt: lastSegment?.arrival.at ?? pending.returnDate ?? null,
|
|
156
|
+
flightDraft: {
|
|
157
|
+
origin: pending.origin,
|
|
158
|
+
destination: pending.destination,
|
|
159
|
+
departDate: pending.departDate,
|
|
160
|
+
returnDate: pending.returnDate || null,
|
|
161
|
+
tripType: pending.tripType,
|
|
162
|
+
cabin: pending.cabin,
|
|
163
|
+
offerId: pending.selectedOffer?.offerId ?? null,
|
|
164
|
+
source: pending.selectedOffer?.source ?? null,
|
|
165
|
+
selectedOffer: pending.selectedOffer,
|
|
166
|
+
ancillaries: {
|
|
167
|
+
fareBundle: pending.fareBundlePicks,
|
|
168
|
+
baggage: pending.baggagePicks,
|
|
169
|
+
assistance: pending.assistancePicks,
|
|
170
|
+
extras: pending.extrasPicks,
|
|
171
|
+
},
|
|
172
|
+
pricing,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (pending.kind === "cruise") {
|
|
178
|
+
const amountCents = parseAmountCents(pending.estimatedAmount);
|
|
179
|
+
return {
|
|
180
|
+
kind: "manual_placeholder",
|
|
181
|
+
description: pending.description || undefined,
|
|
182
|
+
estimatedPricing: pricingFromAmount(amountCents, ctx.paymentCurrency),
|
|
183
|
+
metadata: {
|
|
184
|
+
scheduledStartsAt: pending.embarkationDate || null,
|
|
185
|
+
scheduledEndsAt: null,
|
|
186
|
+
cruiseDraft: {
|
|
187
|
+
cabin: pending.cabin || null,
|
|
188
|
+
embarkationDate: pending.embarkationDate || null,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const totals = computePlaceholderTotals(pending.subtotalCents, pending.taxRatePct);
|
|
194
|
+
return {
|
|
195
|
+
kind: "manual_placeholder",
|
|
196
|
+
description: pending.description || undefined,
|
|
197
|
+
estimatedPricing: {
|
|
198
|
+
currency: pending.currency,
|
|
199
|
+
subtotalAmountCents: totals.subtotal,
|
|
200
|
+
taxAmountCents: totals.tax,
|
|
201
|
+
totalAmountCents: totals.total,
|
|
202
|
+
},
|
|
203
|
+
metadata: {
|
|
204
|
+
scheduledStartsAt: pending.startsAt || null,
|
|
205
|
+
scheduledEndsAt: pending.endsAt || null,
|
|
206
|
+
manualService: {
|
|
207
|
+
name: pending.name,
|
|
208
|
+
},
|
|
209
|
+
taxRatePct: pending.taxRatePct || null,
|
|
210
|
+
template: pending.kind,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export function pricingFromAmount(amountCents, pricingCurrency) {
|
|
215
|
+
return {
|
|
216
|
+
currency: pricingCurrency,
|
|
217
|
+
subtotalAmountCents: amountCents,
|
|
218
|
+
taxAmountCents: 0,
|
|
219
|
+
totalAmountCents: amountCents,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export function parseAmountCents(raw) {
|
|
223
|
+
const parsed = Number.parseFloat(raw || "0");
|
|
224
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed * 100) : 0;
|
|
225
|
+
}
|
|
226
|
+
export function countAdults(travelers) {
|
|
227
|
+
return travelers.filter((t) => t.role === "lead" || t.role === "adult").length;
|
|
228
|
+
}
|
|
229
|
+
export function serializeBilling(billing, payerNameFallback, payerEmailFallback) {
|
|
230
|
+
if (billing.mode === "new") {
|
|
231
|
+
return {
|
|
232
|
+
buyerType: billing.billTo === "organization" ? "B2B" : "B2C",
|
|
233
|
+
contact: {
|
|
234
|
+
firstName: billing.newPerson.firstName.trim(),
|
|
235
|
+
lastName: billing.newPerson.lastName.trim(),
|
|
236
|
+
email: billing.newPerson.email.trim(),
|
|
237
|
+
phone: billing.newPerson.phone || undefined,
|
|
238
|
+
},
|
|
239
|
+
address: {},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// For an existing person we still need a contact block — the booking engine
|
|
243
|
+
// validates `billing.contact` even when an id is present. Names/emails come
|
|
244
|
+
// from the resolved person (payerName / payerEmail).
|
|
245
|
+
const [firstName, ...rest] = (payerNameFallback ?? "").trim().split(/\s+/);
|
|
246
|
+
return {
|
|
247
|
+
buyerType: billing.billTo === "organization" ? "B2B" : "B2C",
|
|
248
|
+
...(billing.personId ? { personId: billing.personId } : {}),
|
|
249
|
+
...(billing.organizationId ? { organizationId: billing.organizationId } : {}),
|
|
250
|
+
contact: {
|
|
251
|
+
firstName: firstName || "",
|
|
252
|
+
lastName: rest.join(" ") || "",
|
|
253
|
+
email: payerEmailFallback || "",
|
|
254
|
+
},
|
|
255
|
+
address: {},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export function assertTripCreationRequirements(ctx, messages) {
|
|
259
|
+
const errors = [];
|
|
260
|
+
const { errors: errorMessages } = messages;
|
|
261
|
+
if (ctx.billing.mode === "new") {
|
|
262
|
+
if (!ctx.billing.newPerson.firstName.trim() || !ctx.billing.newPerson.lastName.trim()) {
|
|
263
|
+
errors.push(errorMessages.requirementBillingName);
|
|
264
|
+
}
|
|
265
|
+
if (!isRealTripEmail(ctx.billing.newPerson.email)) {
|
|
266
|
+
errors.push(errorMessages.requirementBillingEmail);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const hasBillingRecord = ctx.billing.billTo === "organization"
|
|
271
|
+
? Boolean(ctx.billing.organizationId)
|
|
272
|
+
: Boolean(ctx.billing.personId);
|
|
273
|
+
if (!hasBillingRecord)
|
|
274
|
+
errors.push(errorMessages.requirementBillingPersonOrOrg);
|
|
275
|
+
if (!ctx.payerName.trim())
|
|
276
|
+
errors.push(errorMessages.requirementBillingName);
|
|
277
|
+
if (!isRealTripEmail(ctx.payerEmail))
|
|
278
|
+
errors.push(errorMessages.requirementBillingEmail);
|
|
279
|
+
}
|
|
280
|
+
if (ctx.travelers.length === 0) {
|
|
281
|
+
errors.push(errorMessages.requirementAtLeastOneTraveler);
|
|
282
|
+
}
|
|
283
|
+
ctx.travelers.forEach((traveler, index) => {
|
|
284
|
+
if (!traveler.personId && (!traveler.firstName.trim() || !traveler.lastName.trim())) {
|
|
285
|
+
errors.push(formatMessage(errorMessages.requirementTravelerName, { position: index + 1 }));
|
|
286
|
+
}
|
|
287
|
+
if (traveler.email && !isRealTripEmail(traveler.email)) {
|
|
288
|
+
errors.push(formatMessage(errorMessages.requirementTravelerEmail, { position: index + 1 }));
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
if (errors.length > 0) {
|
|
292
|
+
throw new Error(formatMessage(errorMessages.completeRequirements, { fields: errors.join(", ") }));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export function isRealTripEmail(value) {
|
|
296
|
+
const normalized = value?.trim().toLowerCase() ?? "";
|
|
297
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized))
|
|
298
|
+
return false;
|
|
299
|
+
return !["noreply@example.com", "tbd@example.com", "traveler@example.com"].includes(normalized);
|
|
300
|
+
}
|
|
301
|
+
export function hydrateBilling(travelerParty) {
|
|
302
|
+
const billing = readRecord(travelerParty.billing);
|
|
303
|
+
if (!billing)
|
|
304
|
+
return emptyPersonPickerValue;
|
|
305
|
+
const contact = readRecord(billing.contact);
|
|
306
|
+
const personId = stringFromRecord(billing, "personId") ?? "";
|
|
307
|
+
const organizationId = stringFromRecord(billing, "organizationId") ?? null;
|
|
308
|
+
const billTo = organizationId || stringFromRecord(billing, "buyerType") === "B2B" ? "organization" : "person";
|
|
309
|
+
if (personId || organizationId) {
|
|
310
|
+
return {
|
|
311
|
+
billTo,
|
|
312
|
+
mode: "existing",
|
|
313
|
+
personId,
|
|
314
|
+
organizationId,
|
|
315
|
+
newPerson: emptyPersonPickerValue.newPerson,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
billTo,
|
|
320
|
+
mode: "new",
|
|
321
|
+
personId: "",
|
|
322
|
+
organizationId,
|
|
323
|
+
newPerson: {
|
|
324
|
+
firstName: stringFromRecord(contact, "firstName") ?? "",
|
|
325
|
+
lastName: stringFromRecord(contact, "lastName") ?? "",
|
|
326
|
+
email: stringFromRecord(contact, "email") ?? "",
|
|
327
|
+
phone: stringFromRecord(contact, "phone") ?? "",
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
export function hydrateTravelers(travelerParty) {
|
|
332
|
+
const travelers = travelerParty.travelers;
|
|
333
|
+
if (!Array.isArray(travelers))
|
|
334
|
+
return [];
|
|
335
|
+
return travelers.filter(readRecord).map((traveler, index) => ({
|
|
336
|
+
localId: stringFromRecord(traveler, "localId") ?? `tt_existing_${index}`,
|
|
337
|
+
personId: stringFromRecord(traveler, "personId") ?? null,
|
|
338
|
+
firstName: stringFromRecord(traveler, "firstName") ?? "",
|
|
339
|
+
lastName: stringFromRecord(traveler, "lastName") ?? "",
|
|
340
|
+
email: stringFromRecord(traveler, "email") ?? "",
|
|
341
|
+
dateOfBirth: stringFromRecord(traveler, "dateOfBirth") ?? null,
|
|
342
|
+
role: tripTravelerRoleFromStored(stringFromRecord(traveler, "role"), index),
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
export function hydrateVoucher(travelerParty) {
|
|
346
|
+
const voucher = readRecord(travelerParty.voucher);
|
|
347
|
+
if (!voucher)
|
|
348
|
+
return emptyVoucherPickerValue;
|
|
349
|
+
const id = stringFromRecord(voucher, "id");
|
|
350
|
+
const code = stringFromRecord(voucher, "code");
|
|
351
|
+
const currencyCode = stringFromRecord(voucher, "currency");
|
|
352
|
+
const remainingAmountCents = numberFromRecord(voucher, "remainingAmountCents");
|
|
353
|
+
if (!id || !code || !currencyCode || remainingAmountCents == null)
|
|
354
|
+
return emptyVoucherPickerValue;
|
|
355
|
+
return {
|
|
356
|
+
code,
|
|
357
|
+
picked: {
|
|
358
|
+
id,
|
|
359
|
+
code,
|
|
360
|
+
label: null,
|
|
361
|
+
currency: currencyCode,
|
|
362
|
+
remainingAmountCents,
|
|
363
|
+
expiresAt: null,
|
|
364
|
+
},
|
|
365
|
+
error: null,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
export function tripTravelerRoleFromStored(value, index) {
|
|
369
|
+
if (value === "lead" || value === "adult" || value === "child" || value === "infant") {
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
return index === 0 ? "lead" : "adult";
|
|
373
|
+
}
|
|
374
|
+
// Map our roster shape onto the catalog booking engine's `travelerEntryV1`:
|
|
375
|
+
// drop empty/null fields it can't validate, translate `role` (lead/adult/...)
|
|
376
|
+
// into `band` (adult/child/infant) + `isPrimary`.
|
|
377
|
+
export function serializeTravelersForBookingDraft(travelers, messages) {
|
|
378
|
+
return travelers.map((traveler) => {
|
|
379
|
+
const band = traveler.role === "child" ? "child" : traveler.role === "infant" ? "infant" : "adult";
|
|
380
|
+
const firstName = traveler.firstName.trim();
|
|
381
|
+
const lastName = traveler.lastName.trim();
|
|
382
|
+
const email = traveler.email.trim();
|
|
383
|
+
const dateOfBirth = traveler.dateOfBirth?.trim() || "";
|
|
384
|
+
const entry = {
|
|
385
|
+
firstName: firstName || messages.travelerFallbackName,
|
|
386
|
+
lastName: lastName || messages.travelerFallbackLastName,
|
|
387
|
+
band,
|
|
388
|
+
};
|
|
389
|
+
if (email)
|
|
390
|
+
entry.email = email;
|
|
391
|
+
if (dateOfBirth)
|
|
392
|
+
entry.dateOfBirth = dateOfBirth;
|
|
393
|
+
if (traveler.role === "lead")
|
|
394
|
+
entry.isPrimary = true;
|
|
395
|
+
return entry;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
export function failuresToString(failures, messages) {
|
|
399
|
+
if (!failures || failures.length === 0)
|
|
400
|
+
return null;
|
|
401
|
+
if (failures.some((failure) => failure.code === "price_changed")) {
|
|
402
|
+
return messages.failureMessages.priceChanged;
|
|
403
|
+
}
|
|
404
|
+
if (failures.some((failure) => failure.code === "expired")) {
|
|
405
|
+
return messages.failureMessages.expired;
|
|
406
|
+
}
|
|
407
|
+
if (failures.some((failure) => failure.code === "unavailable")) {
|
|
408
|
+
return messages.failureMessages.unavailable;
|
|
409
|
+
}
|
|
410
|
+
return failures.map((failure) => failure.reason).join(", ");
|
|
411
|
+
}
|
|
412
|
+
export function apiError(error, messages) {
|
|
413
|
+
const candidate = error;
|
|
414
|
+
if (typeof candidate.message === "string")
|
|
415
|
+
return candidate.message;
|
|
416
|
+
return error instanceof Error ? error.message : messages.errors.requestFailed;
|
|
417
|
+
}
|
|
418
|
+
export function derivePayerName(billing, person, messages) {
|
|
419
|
+
if (billing.mode === "new") {
|
|
420
|
+
const name = [billing.newPerson.firstName, billing.newPerson.lastName]
|
|
421
|
+
.filter((part) => part.trim().length > 0)
|
|
422
|
+
.join(" ")
|
|
423
|
+
.trim();
|
|
424
|
+
return name || billing.newPerson.email.trim() || messages.travelerFallbackName;
|
|
425
|
+
}
|
|
426
|
+
if (person) {
|
|
427
|
+
const name = [person.firstName, person.lastName]
|
|
428
|
+
.filter((part) => (part ?? "").trim().length > 0)
|
|
429
|
+
.join(" ")
|
|
430
|
+
.trim();
|
|
431
|
+
return name || (person.email ?? "") || messages.travelerFallbackName;
|
|
432
|
+
}
|
|
433
|
+
return messages.travelerFallbackName;
|
|
434
|
+
}
|
|
435
|
+
export function derivePayerEmail(billing, person) {
|
|
436
|
+
if (billing.mode === "new") {
|
|
437
|
+
return billing.newPerson.email.trim();
|
|
438
|
+
}
|
|
439
|
+
return person?.email ?? "";
|
|
440
|
+
}
|
|
441
|
+
export function readRecord(value) {
|
|
442
|
+
return isRecord(value) ? value : null;
|
|
443
|
+
}
|
|
444
|
+
export function isRecord(value) {
|
|
445
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
446
|
+
}
|
|
447
|
+
export function stringFromRecord(record, key) {
|
|
448
|
+
const value = record?.[key];
|
|
449
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
450
|
+
}
|
|
451
|
+
export function numberFromRecord(record, key) {
|
|
452
|
+
const value = record?.[key];
|
|
453
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
454
|
+
}
|
|
455
|
+
export function booleanFromRecord(record, key) {
|
|
456
|
+
return record[key] === true;
|
|
457
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Trip } from "@voyant-travel/trips";
|
|
2
|
+
export interface AdminTripsPageProps {
|
|
3
|
+
initialTrip?: Trip | null;
|
|
4
|
+
}
|
|
5
|
+
export declare function AdminTripsPage({ initialTrip }: AdminTripsPageProps): React.ReactElement;
|
|
6
|
+
//# sourceMappingURL=admin-trips-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-trips-page.d.ts","sourceRoot":"","sources":["../../src/admin/admin-trips-page.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,IAAI,EAAiB,MAAM,sBAAsB,CAAA;AAgE/D,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAAC,EAAE,WAAkB,EAAE,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAye9F"}
|