@qite/tide-components 1.0.14-preview.3 → 1.0.14-preview.5
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/build/build-cjs/index.js +410 -110
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +16 -3
- package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +7 -1
- package/build/build-cjs/src/shared/booking/travelers-form.d.ts +12 -3
- package/build/build-esm/index.js +410 -110
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +16 -3
- package/build/build-esm/src/search-results/utils/traject-utils.d.ts +7 -1
- package/build/build-esm/src/shared/booking/travelers-form.d.ts +12 -3
- package/package.json +2 -2
package/build/build-cjs/index.js
CHANGED
|
@@ -16133,6 +16133,7 @@ var ENDPOINT$1 = "/api/web/packaging";
|
|
|
16133
16133
|
var ENDPOINT_START_TRANSACTION = ENDPOINT$1 + "/start";
|
|
16134
16134
|
var ENDPOINT_ACCOMMODATIONS = ENDPOINT$1 + "/accommodations";
|
|
16135
16135
|
var ENDPOINT_EXCURSIONS = ENDPOINT$1 + "/excursions";
|
|
16136
|
+
var ENDPOINT_TRANSFERS = ENDPOINT$1 + "/transfers";
|
|
16136
16137
|
var ENDPOINT_FLIGHTS = ENDPOINT$1 + "/flights";
|
|
16137
16138
|
var ENDPOINT_PRICE_DETAILS = ENDPOINT$1 + "/price-details";
|
|
16138
16139
|
var ENDPOINT_BOOK = ENDPOINT$1 + "/book";
|
|
@@ -16165,6 +16166,12 @@ var searchPackagingExcursions = function (config, request, signal) {
|
|
|
16165
16166
|
var body = JSON.stringify(request);
|
|
16166
16167
|
return post(url, apiKey, body, config.token, signal, true);
|
|
16167
16168
|
};
|
|
16169
|
+
var searchPackagingTransfers = function (config, request, signal) {
|
|
16170
|
+
var url = "" + config.host + ENDPOINT_TRANSFERS;
|
|
16171
|
+
var apiKey = config.apiKey;
|
|
16172
|
+
var body = JSON.stringify(request);
|
|
16173
|
+
return post(url, apiKey, body, config.token, signal, true);
|
|
16174
|
+
};
|
|
16168
16175
|
var getEntry = function (config, magicLinkCode, signal) {
|
|
16169
16176
|
var apiKey = config.apiKey;
|
|
16170
16177
|
var url = "" + config.host + ENDPOINT_ENTRY(magicLinkCode);
|
|
@@ -16342,6 +16349,7 @@ exports.searchOords = searchOords;
|
|
|
16342
16349
|
exports.searchPackagingAccommodations = searchPackagingAccommodations;
|
|
16343
16350
|
exports.searchPackagingExcursions = searchPackagingExcursions;
|
|
16344
16351
|
exports.searchPackagingFlights = searchPackagingFlights;
|
|
16352
|
+
exports.searchPackagingTransfers = searchPackagingTransfers;
|
|
16345
16353
|
exports.searchRegions = searchRegions;
|
|
16346
16354
|
exports.searchWithErrorMapping = searchWithErrorMapping;
|
|
16347
16355
|
exports.sendContactForm = sendContactForm;
|
|
@@ -19834,10 +19842,30 @@ function formatBirthDate$1(birthDate) {
|
|
|
19834
19842
|
// A field can have separate "main-booker"/other-pax variants sharing the same identity
|
|
19835
19843
|
// (customFieldConfigurationId for custom fields, fieldName for built-in fields).
|
|
19836
19844
|
const MAIN_BOOKER_GROUP = 'main-booker';
|
|
19845
|
+
const MAIN_BOOKER_FIELD_PREFIX = 'mainBooker';
|
|
19837
19846
|
const isCustomPaxField = (field) => field.customFieldConfigurationId != null;
|
|
19847
|
+
// Tide names the built-in main-booker fields with a prefix ('mainBookerGender', 'mainBookerCountryId', ...),
|
|
19848
|
+
// the other-pax ones without ('gender', 'countryId'). Strip it so both are variants of the same field.
|
|
19849
|
+
function normalizeBuiltInFieldName(field) {
|
|
19850
|
+
if (isCustomPaxField(field) || !field.fieldName?.startsWith(MAIN_BOOKER_FIELD_PREFIX))
|
|
19851
|
+
return field;
|
|
19852
|
+
const stripped = field.fieldName.slice(MAIN_BOOKER_FIELD_PREFIX.length);
|
|
19853
|
+
if (!stripped)
|
|
19854
|
+
return field;
|
|
19855
|
+
return { ...field, fieldName: stripped.charAt(0).toLowerCase() + stripped.slice(1) };
|
|
19856
|
+
}
|
|
19857
|
+
// Once Tide sends built-in fields, that configuration decides which built-in fields are shown/required;
|
|
19858
|
+
// the hardcoded formFields / core defaults are only the fallback for when it doesn't.
|
|
19859
|
+
function hasConfiguredBuiltInPaxFields(paxFields = []) {
|
|
19860
|
+
return paxFields.some((f) => !isCustomPaxField(f));
|
|
19861
|
+
}
|
|
19862
|
+
// The main booker's address/contact details are then asked on the main booker's own traveler block.
|
|
19863
|
+
function hasConfiguredMainBookerFields(paxFields = []) {
|
|
19864
|
+
return paxFields.some((f) => !isCustomPaxField(f) && f.group === MAIN_BOOKER_GROUP);
|
|
19865
|
+
}
|
|
19838
19866
|
function resolveApplicablePaxFields(fields, isMainBooker) {
|
|
19839
19867
|
const variantsByKey = new Map();
|
|
19840
|
-
fields.forEach((field) => {
|
|
19868
|
+
fields.map(normalizeBuiltInFieldName).forEach((field) => {
|
|
19841
19869
|
const key = field.customFieldConfigurationId ?? field.fieldName;
|
|
19842
19870
|
const variants = variantsByKey.get(key) ?? [];
|
|
19843
19871
|
variants.push(field);
|
|
@@ -19899,11 +19927,50 @@ const BUILT_IN_FIELD_PATHS = {
|
|
|
19899
19927
|
// The 4 fields that always render/validate regardless of paxFields config (fallback behavior).
|
|
19900
19928
|
const CORE_FIELD_NAMES = new Set(['gender', 'firstName', 'lastName', 'dateOfBirth']);
|
|
19901
19929
|
const ADDRESS_FIELD_NAMES = ['street', 'location', 'countryId', 'postalCode', 'number'];
|
|
19930
|
+
// Built-in fields have no dataType from Tide; their control type and label follow from the fieldName.
|
|
19931
|
+
// gender, hasInsurance, countryId, weight and height have a dedicated control and only need a label here.
|
|
19932
|
+
function getBuiltInFieldControlMeta(translations) {
|
|
19933
|
+
return {
|
|
19934
|
+
gender: { label: translations.TRAVELERS_FORM.GENDER_ID },
|
|
19935
|
+
firstName: { label: translations.TRAVELERS_FORM.FIRST_NAME },
|
|
19936
|
+
lastName: { label: translations.TRAVELERS_FORM.LAST_NAME },
|
|
19937
|
+
dateOfBirth: { type: 'date', label: translations.TRAVELERS_FORM.BIRTHDATE },
|
|
19938
|
+
nickname: { label: translations.TRAVELERS_FORM.NICKNAME },
|
|
19939
|
+
birthplace: { label: translations.TRAVELERS_FORM.BIRTHPLACE },
|
|
19940
|
+
nationality: { label: translations.TRAVELERS_FORM.NATIONALITY },
|
|
19941
|
+
preferredLanguageId: { type: 'number', label: translations.TRAVELERS_FORM.PREFERRED_LANGUAGE },
|
|
19942
|
+
email: { type: 'email', label: translations.TRAVELERS_FORM.TRAVELER_EMAIL },
|
|
19943
|
+
phone: { type: 'tel', label: translations.TRAVELERS_FORM.TRAVELER_PHONE },
|
|
19944
|
+
mobile: { type: 'tel', label: translations.TRAVELERS_FORM.TRAVELER_MOBILE },
|
|
19945
|
+
passportNumber: { label: translations.TRAVELERS_FORM.PASSPORT_NUMBER },
|
|
19946
|
+
passportIssueDate: { type: 'date', label: translations.TRAVELERS_FORM.PASSPORT_ISSUE_DATE },
|
|
19947
|
+
passportIssuedLocation: { label: translations.TRAVELERS_FORM.PASSPORT_ISSUED_LOCATION },
|
|
19948
|
+
passportExpiryDate: { type: 'date', label: translations.TRAVELERS_FORM.PASSPORT_EXPIRY_DATE },
|
|
19949
|
+
emergencyNumber: { type: 'tel', label: translations.TRAVELERS_FORM.EMERGENCY_NUMBER },
|
|
19950
|
+
hasInsurance: { label: translations.TRAVELERS_FORM.HAS_INSURANCE },
|
|
19951
|
+
insuranceName: { label: translations.TRAVELERS_FORM.INSURANCE_NAME },
|
|
19952
|
+
info: { label: translations.TRAVELERS_FORM.INFO },
|
|
19953
|
+
weight: { label: translations.TRAVELERS_FORM.WEIGHT },
|
|
19954
|
+
height: { label: translations.TRAVELERS_FORM.HEIGHT },
|
|
19955
|
+
street: { label: translations.TRAVELERS_FORM.STREET },
|
|
19956
|
+
location: { label: translations.TRAVELERS_FORM.CITY },
|
|
19957
|
+
countryId: { label: translations.TRAVELERS_FORM.COUNTRY },
|
|
19958
|
+
postalCode: { label: translations.TRAVELERS_FORM.ZIPCODE },
|
|
19959
|
+
number: { label: translations.TRAVELERS_FORM.HOUSE_NUMBER }
|
|
19960
|
+
};
|
|
19961
|
+
}
|
|
19962
|
+
// The form's gender radio values ('m'/'f'/'x') <-> Tide's Gender enum.
|
|
19963
|
+
const GENDER_BY_FORM_VALUE = { m: build.Gender.male, f: build.Gender.female, x: build.Gender.other };
|
|
19964
|
+
function formValueFromGender(gender) {
|
|
19965
|
+
return Object.keys(GENDER_BY_FORM_VALUE).find((key) => GENDER_BY_FORM_VALUE[key] === gender) ?? '';
|
|
19966
|
+
}
|
|
19902
19967
|
// Shared by buildPax (new-booking) and applyTravelersFormValuesToEditablePackagingEntry
|
|
19903
19968
|
// (edit-booking), now that PackagingEntryPax and BookingPackagePax have the same shape.
|
|
19904
19969
|
function applyTravelerToPax(pax, traveler, mainBookerId) {
|
|
19905
19970
|
return {
|
|
19906
19971
|
...pax,
|
|
19972
|
+
// Unanswered (optional) gender keeps whatever the pax already had.
|
|
19973
|
+
gender: GENDER_BY_FORM_VALUE[traveler.gender] ?? pax.gender,
|
|
19907
19974
|
firstName: traveler.firstName ?? '',
|
|
19908
19975
|
lastName: traveler.lastName ?? '',
|
|
19909
19976
|
dateOfBirth: traveler.birthDate || null,
|
|
@@ -20063,7 +20130,13 @@ function mapPackagingEntryPaxToTraveler(roomTraveler) {
|
|
|
20063
20130
|
customFields: roomTraveler.customFields ?? []
|
|
20064
20131
|
};
|
|
20065
20132
|
}
|
|
20066
|
-
|
|
20133
|
+
// Keeps the values already on the entry's pax and adds an empty slot for every configured custom field it lacks,
|
|
20134
|
+
// so the form renders/validates the same set as createTraveler does for a new booking.
|
|
20135
|
+
function mergeCustomFields(existing = [], paxFields) {
|
|
20136
|
+
const missing = buildEmptyCustomFields(paxFields).filter((empty) => !existing.some((c) => c.customFieldConfigurationId === empty.customFieldConfigurationId));
|
|
20137
|
+
return [...existing, ...missing];
|
|
20138
|
+
}
|
|
20139
|
+
function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry, agentAdressId, paxFields = editablePackagingEntry?.paxFields ?? []) {
|
|
20067
20140
|
const pax = editablePackagingEntry?.pax ?? [];
|
|
20068
20141
|
const rooms = editablePackagingEntry.rooms.map((room) => {
|
|
20069
20142
|
const roomPax = pax.filter((x) => room.paxIds.includes(x.id));
|
|
@@ -20076,8 +20149,9 @@ function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry, a
|
|
|
20076
20149
|
firstName: roomTraveler.firstName ?? '',
|
|
20077
20150
|
lastName: roomTraveler.lastName ?? '',
|
|
20078
20151
|
birthDate: roomTraveler.dateOfBirth ? dateFns.format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '',
|
|
20079
|
-
gender:
|
|
20080
|
-
...mapPackagingEntryPaxToTraveler(roomTraveler)
|
|
20152
|
+
gender: formValueFromGender(roomTraveler.gender),
|
|
20153
|
+
...mapPackagingEntryPaxToTraveler(roomTraveler),
|
|
20154
|
+
customFields: mergeCustomFields(roomTraveler.customFields, paxFields)
|
|
20081
20155
|
};
|
|
20082
20156
|
}),
|
|
20083
20157
|
children: children.map((roomTraveler) => {
|
|
@@ -20086,9 +20160,10 @@ function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry, a
|
|
|
20086
20160
|
firstName: roomTraveler.firstName ?? '',
|
|
20087
20161
|
lastName: roomTraveler.lastName ?? '',
|
|
20088
20162
|
birthDate: roomTraveler.dateOfBirth ? dateFns.format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '',
|
|
20089
|
-
gender:
|
|
20163
|
+
gender: formValueFromGender(roomTraveler.gender),
|
|
20090
20164
|
isBaby: isBabyAge(roomTraveler.age),
|
|
20091
|
-
...mapPackagingEntryPaxToTraveler(roomTraveler)
|
|
20165
|
+
...mapPackagingEntryPaxToTraveler(roomTraveler),
|
|
20166
|
+
customFields: mergeCustomFields(roomTraveler.customFields, paxFields)
|
|
20092
20167
|
};
|
|
20093
20168
|
})
|
|
20094
20169
|
};
|
|
@@ -20113,7 +20188,8 @@ function applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry
|
|
|
20113
20188
|
const travelers = values.rooms.flatMap((room) => [...room.adults, ...room.children]);
|
|
20114
20189
|
// The backend treats the main booker's per-pax address as authoritative, so keep the
|
|
20115
20190
|
// booking-level address in sync with it rather than sending two independent values.
|
|
20116
|
-
const
|
|
20191
|
+
const mainBookerTraveler = travelers.find((x) => x.id === values.mainBookerId);
|
|
20192
|
+
const mainBookerAddress = mainBookerTraveler?.address;
|
|
20117
20193
|
return {
|
|
20118
20194
|
...editablePackagingEntry,
|
|
20119
20195
|
pax: (editablePackagingEntry.pax ?? []).map((pax) => {
|
|
@@ -20129,8 +20205,9 @@ function applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry
|
|
|
20129
20205
|
place: mainBookerAddress?.location || values.place,
|
|
20130
20206
|
country: mainBookerAddress?.country || values.country,
|
|
20131
20207
|
travelAgentId: values.travelAgentId,
|
|
20132
|
-
|
|
20133
|
-
|
|
20208
|
+
// When Tide configures the main-booker fields, contact details live on the main booker's traveler block.
|
|
20209
|
+
phone: values.phone || mainBookerTraveler?.mobile || mainBookerTraveler?.phone,
|
|
20210
|
+
email: values.email || mainBookerTraveler?.email
|
|
20134
20211
|
}
|
|
20135
20212
|
};
|
|
20136
20213
|
}
|
|
@@ -20287,30 +20364,7 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20287
20364
|
return (React__default["default"].createElement("div", { key: field.customFieldConfigurationId, style: { gridColumn: `span ${field.col || 12}` } }, getCustomFieldControl(field, value, name)));
|
|
20288
20365
|
})));
|
|
20289
20366
|
};
|
|
20290
|
-
|
|
20291
|
-
const BUILT_IN_FIELD_CONTROL_META = {
|
|
20292
|
-
firstName: { label: translations.TRAVELERS_FORM.FIRST_NAME },
|
|
20293
|
-
lastName: { label: translations.TRAVELERS_FORM.LAST_NAME },
|
|
20294
|
-
dateOfBirth: { type: 'date', label: translations.TRAVELERS_FORM.BIRTHDATE },
|
|
20295
|
-
nickname: { label: translations.TRAVELERS_FORM.NICKNAME },
|
|
20296
|
-
birthplace: { label: translations.TRAVELERS_FORM.BIRTHPLACE },
|
|
20297
|
-
nationality: { label: translations.TRAVELERS_FORM.NATIONALITY },
|
|
20298
|
-
preferredLanguageId: { type: 'number', label: translations.TRAVELERS_FORM.PREFERRED_LANGUAGE },
|
|
20299
|
-
email: { type: 'email', label: translations.TRAVELERS_FORM.TRAVELER_EMAIL },
|
|
20300
|
-
phone: { label: translations.TRAVELERS_FORM.TRAVELER_PHONE },
|
|
20301
|
-
mobile: { label: translations.TRAVELERS_FORM.TRAVELER_MOBILE },
|
|
20302
|
-
passportNumber: { label: translations.TRAVELERS_FORM.PASSPORT_NUMBER },
|
|
20303
|
-
passportIssueDate: { type: 'date', label: translations.TRAVELERS_FORM.PASSPORT_ISSUE_DATE },
|
|
20304
|
-
passportIssuedLocation: { label: translations.TRAVELERS_FORM.PASSPORT_ISSUED_LOCATION },
|
|
20305
|
-
passportExpiryDate: { type: 'date', label: translations.TRAVELERS_FORM.PASSPORT_EXPIRY_DATE },
|
|
20306
|
-
emergencyNumber: { label: translations.TRAVELERS_FORM.EMERGENCY_NUMBER },
|
|
20307
|
-
insuranceName: { label: translations.TRAVELERS_FORM.INSURANCE_NAME },
|
|
20308
|
-
info: { label: translations.TRAVELERS_FORM.INFO },
|
|
20309
|
-
street: { label: translations.TRAVELERS_FORM.STREET },
|
|
20310
|
-
location: { label: translations.TRAVELERS_FORM.CITY },
|
|
20311
|
-
postalCode: { label: translations.TRAVELERS_FORM.ZIPCODE },
|
|
20312
|
-
number: { label: translations.TRAVELERS_FORM.HOUSE_NUMBER }
|
|
20313
|
-
};
|
|
20367
|
+
const BUILT_IN_FIELD_CONTROL_META = getBuiltInFieldControlMeta(translations);
|
|
20314
20368
|
const renderBuiltInFieldControl = (fieldName, isRequired, travelerValues, namePrefix) => {
|
|
20315
20369
|
const path = BUILT_IN_FIELD_PATHS[fieldName] ?? fieldName;
|
|
20316
20370
|
const name = `${namePrefix}.${path}`;
|
|
@@ -20345,7 +20399,7 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20345
20399
|
const resolveBuiltInField = (fieldName, travelerValues) => {
|
|
20346
20400
|
const isMainBooker = travelerValues.id === formik.values.mainBookerId;
|
|
20347
20401
|
const configured = getBuiltInPaxField(paxFields, fieldName, isMainBooker);
|
|
20348
|
-
const fallback = CORE_FIELD_DEFAULTS[fieldName];
|
|
20402
|
+
const fallback = hasConfiguredBuiltInPaxFields(paxFields) ? undefined : CORE_FIELD_DEFAULTS[fieldName];
|
|
20349
20403
|
if (!configured && !fallback)
|
|
20350
20404
|
return undefined;
|
|
20351
20405
|
return { isRequired: configured?.isRequired ?? fallback.isRequired, col: configured?.col ?? fallback.col };
|
|
@@ -20357,7 +20411,14 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20357
20411
|
return (React__default["default"].createElement("div", { key: fieldName, style: { gridColumn: `span ${resolved.col || 12}` } }, renderBuiltInFieldControl(fieldName, resolved.isRequired, travelerValues, namePrefix)));
|
|
20358
20412
|
};
|
|
20359
20413
|
const renderTravelerAddressFields = (travelerValues, namePrefix) => {
|
|
20360
|
-
const
|
|
20414
|
+
const isMainBooker = travelerValues.id === formik.values.mainBookerId;
|
|
20415
|
+
// Configured order when Tide sends one, so the configured col widths line up as intended.
|
|
20416
|
+
const fieldNames = hasConfiguredBuiltInPaxFields(paxFields)
|
|
20417
|
+
? getApplicableBuiltInPaxFields(paxFields, isMainBooker)
|
|
20418
|
+
.map((field) => field.fieldName)
|
|
20419
|
+
.filter((fieldName) => ADDRESS_FIELD_NAMES.includes(fieldName))
|
|
20420
|
+
: ADDRESS_FIELD_NAMES;
|
|
20421
|
+
const fields = fieldNames.map((fieldName) => renderBuiltInField(fieldName, travelerValues, namePrefix)).filter(Boolean);
|
|
20361
20422
|
if (!fields.length)
|
|
20362
20423
|
return null;
|
|
20363
20424
|
return React__default["default"].createElement("div", { className: "travelers-form__custom-fields-grid" }, fields);
|
|
@@ -20400,15 +20461,18 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20400
20461
|
return (React__default["default"].createElement("div", { className: "form__row" },
|
|
20401
20462
|
React__default["default"].createElement(LabeledInput, { hasError: hasVisibleError(`${namePrefix}.age`), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.AGE, required: true, name: `${namePrefix}.age`, onChange: formik.handleChange, onBlur: formik.handleBlur, value: travelerValues.age })));
|
|
20402
20463
|
}
|
|
20403
|
-
// 'birthDate' (the legacy formFields type string) is normalized to the paxFields catalog's
|
|
20404
|
-
// 'dateOfBirth' fieldName for config lookups.
|
|
20405
|
-
const baseFieldNames = (travellersSettings?.formFields?.length ? travellersSettings.formFields.map((field) => field.type) : ['gender', 'firstName', 'lastName', 'dateOfBirth']).map((fieldName) => (fieldName === 'birthDate' ? 'dateOfBirth' : fieldName));
|
|
20406
20464
|
const isMainBooker = travelerValues.id === formik.values.mainBookerId;
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20465
|
+
// With a Tide field configuration, exactly the configured fields render, in their configured order.
|
|
20466
|
+
// Without one, fall back to formFields ('birthDate', the legacy type string, maps to 'dateOfBirth').
|
|
20467
|
+
const fieldNames = hasConfiguredBuiltInPaxFields(paxFields)
|
|
20468
|
+
? getApplicableBuiltInPaxFields(paxFields, isMainBooker)
|
|
20469
|
+
.map((field) => field.fieldName)
|
|
20470
|
+
.filter((fieldName) => !ADDRESS_FIELD_NAMES.includes(fieldName))
|
|
20471
|
+
: (travellersSettings?.formFields?.length
|
|
20472
|
+
? travellersSettings.formFields.map((field) => field.type)
|
|
20473
|
+
: ['gender', 'firstName', 'lastName', 'dateOfBirth']).map((fieldName) => (fieldName === 'birthDate' ? 'dateOfBirth' : fieldName));
|
|
20410
20474
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
20411
|
-
React__default["default"].createElement("div", { className: "travelers-form__custom-fields-grid" },
|
|
20475
|
+
React__default["default"].createElement("div", { className: "travelers-form__custom-fields-grid" }, fieldNames.map((fieldName) => renderBuiltInField(fieldName, travelerValues, namePrefix))),
|
|
20412
20476
|
renderTravelerAddressFields(travelerValues, namePrefix),
|
|
20413
20477
|
renderCustomFields(travelerValues, namePrefix),
|
|
20414
20478
|
travelersFirstStep && isAdult && formik.values.rooms[roomIndex].adults.length > 1 && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleRemoveTraveler(roomIndex, travelerIndex) }, translations.TRAVELERS_FORM.REMOVE_TRAVELER))));
|
|
@@ -20446,7 +20510,7 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20446
20510
|
renderTravelerFields(travelerValues, `rooms[${roomIndex}].children[${index}]`, false, roomIndex, index)))),
|
|
20447
20511
|
!useCompactForm && travelersFirstStep && (React__default["default"].createElement("div", { className: "form__region" },
|
|
20448
20512
|
React__default["default"].createElement("button", { type: "button", className: "cta cta--select", onClick: () => handleAddTraveler(roomIndex) }, translations.TRAVELERS_FORM.ADD_TRAVELER)))))),
|
|
20449
|
-
|
|
20513
|
+
useCompactForm || hasConfiguredMainBookerFields(paxFields) ? null : bookingType !== 'b2b' || travellersSettings?.mainBookerFormFields?.length ? (React__default["default"].createElement("div", { className: "form__region" },
|
|
20450
20514
|
React__default["default"].createElement("div", { className: "form__region-header" },
|
|
20451
20515
|
React__default["default"].createElement("h5", { className: "form__region-heading" }, translations.TRAVELERS_FORM.MAIN_BOOKER),
|
|
20452
20516
|
React__default["default"].createElement("p", { className: "form__region-label" }, lodash.compact([lodash.compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '), formatBirthDate$1(mainBooker?.birthDate)]).join(', '))),
|
|
@@ -20465,9 +20529,9 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
20465
20529
|
React__default["default"].createElement("div", { className: "form__row" },
|
|
20466
20530
|
React__default["default"].createElement(LabeledInput, { hasError: hasVisibleError('phone'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.PHONE, required: true, name: "phone", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.phone }),
|
|
20467
20531
|
React__default["default"].createElement(LabeledInput, { type: "email", hasError: hasVisibleError('email'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.EMAIL, required: true, name: "email", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.email }),
|
|
20468
|
-
React__default["default"].createElement(LabeledInput, { type: "email", hasError: hasVisibleError('emailConfirmation'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.REPEAT_EMAIL, required: true, name: "emailConfirmation", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.emailConfirmation })))))) :
|
|
20532
|
+
React__default["default"].createElement(LabeledInput, { type: "email", hasError: hasVisibleError('emailConfirmation'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.REPEAT_EMAIL, required: true, name: "emailConfirmation", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.emailConfirmation })))))) : (React__default["default"].createElement("div", { className: "form__region" },
|
|
20469
20533
|
React__default["default"].createElement("div", { className: "form__row" },
|
|
20470
|
-
React__default["default"].createElement(LabeledInput, { hasError: hasVisibleError('phone'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.PHONE, required: true, name: "phone", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.phone }))))
|
|
20534
|
+
React__default["default"].createElement(LabeledInput, { hasError: hasVisibleError('phone'), extraClassName: "form__group--md-33", label: translations.TRAVELERS_FORM.PHONE, required: true, name: "phone", onChange: formik.handleChange, onBlur: formik.handleBlur, value: formik.values.phone })))),
|
|
20471
20535
|
!useCompactForm && showAgentSelection && (React__default["default"].createElement("div", { className: "form__region" },
|
|
20472
20536
|
React__default["default"].createElement("div", { className: "form__region-header" },
|
|
20473
20537
|
React__default["default"].createElement("h5", { className: "form__region-heading" }, translations.TRAVELERS_FORM.BOOK_WITH_AGENT),
|
|
@@ -21078,8 +21142,9 @@ const selectBookingPackagePax = toolkit.createSelector(selectTravelersFormValues
|
|
|
21078
21142
|
formValues?.rooms.forEach((r) => r.adults.forEach((x) => {
|
|
21079
21143
|
const adultPax = buildPax(x, formValues?.mainBookerId);
|
|
21080
21144
|
if (adultPax.isMainBooker) {
|
|
21081
|
-
|
|
21082
|
-
adultPax.
|
|
21145
|
+
// Without the main booker block (Tide configures main-booker fields), keep the traveler's own details.
|
|
21146
|
+
adultPax.mobilePhone = formValues?.phone || adultPax.mobilePhone;
|
|
21147
|
+
adultPax.email = formValues?.email || adultPax.email;
|
|
21083
21148
|
}
|
|
21084
21149
|
pax.push(adultPax);
|
|
21085
21150
|
}));
|
|
@@ -21103,8 +21168,8 @@ const selectBookingAddress = toolkit.createSelector(selectTravelersFormValues, s
|
|
|
21103
21168
|
postalCode: mainBookerAddress?.postalCode || formValues?.zipCode,
|
|
21104
21169
|
location: mainBookerAddress?.location || formValues?.place,
|
|
21105
21170
|
country: mainBookerAddress?.country || formValues?.country,
|
|
21106
|
-
mobilePhone:
|
|
21107
|
-
email:
|
|
21171
|
+
mobilePhone: mainBooker.mobilePhone,
|
|
21172
|
+
email: mainBooker.email
|
|
21108
21173
|
};
|
|
21109
21174
|
});
|
|
21110
21175
|
const selectSubFormAnswers = (state) => state.booking.subFormAnswers;
|
|
@@ -26241,6 +26306,10 @@ const Summary = () => {
|
|
|
26241
26306
|
React__default["default"].createElement("button", { title: settings.isOffer ? translations.STEPS.SUBMIT_OFFER : translations.STEPS.SUBMIT_BOOKING, className: buildClassName(['cta', !userValidated && 'cta--disabled']), disabled: !userValidated, name: "default" }, settings.isOffer ? translations.STEPS.SUBMIT_OFFER : translations.STEPS.SUBMIT_BOOKING))))));
|
|
26242
26307
|
};
|
|
26243
26308
|
|
|
26309
|
+
// lodash isEmpty is true for every number, which would flag a filled-in preferredLanguageId as missing.
|
|
26310
|
+
function isBlank(value) {
|
|
26311
|
+
return value === null || value === undefined || (typeof value === 'string' && value.trim() === '');
|
|
26312
|
+
}
|
|
26244
26313
|
function isValidEmail(email) {
|
|
26245
26314
|
return !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,24}$/i.test(email);
|
|
26246
26315
|
}
|
|
@@ -26264,9 +26333,14 @@ function getAge(birthDateText, startDateText) {
|
|
|
26264
26333
|
}
|
|
26265
26334
|
const validateForm = (values, agentRequired, bookingType, translations, formFields, mainBookerFormFields, maxChildAge = CHILD_MAX_AGE, maxBabyAge = BABY_MAX_AGE, paxFields = []) => {
|
|
26266
26335
|
const errors = {};
|
|
26336
|
+
const usesFieldConfiguration = hasConfiguredBuiltInPaxFields(paxFields);
|
|
26337
|
+
const builtInFieldMeta = getBuiltInFieldControlMeta(translations);
|
|
26267
26338
|
const isFormFieldPresent = (type) => {
|
|
26268
26339
|
return formFields?.some((f) => f.type === type) ?? true;
|
|
26269
26340
|
};
|
|
26341
|
+
// The 4 core fields: with a Tide field configuration they're required only when configured so for the
|
|
26342
|
+
// traveler's role (main booker / other pax); without one, whenever formFields lists them (legacy behavior).
|
|
26343
|
+
const isCoreFieldRequired = (traveler, legacyType, fieldName = legacyType) => usesFieldConfiguration ? !!getBuiltInPaxField(paxFields, fieldName, traveler.id === values.mainBookerId)?.isRequired : isFormFieldPresent(legacyType);
|
|
26270
26344
|
const isMainBookerFormFieldPresent = (type) => {
|
|
26271
26345
|
return mainBookerFormFields?.some((f) => f.type === type) ?? true;
|
|
26272
26346
|
};
|
|
@@ -26318,16 +26392,16 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26318
26392
|
const fieldPath = BUILT_IN_FIELD_PATHS[field.fieldName];
|
|
26319
26393
|
if (!fieldPath)
|
|
26320
26394
|
return;
|
|
26321
|
-
if (
|
|
26322
|
-
lodash.set(errors, `${path}.${fieldPath}`, formatTravelerField(room, pax, field.fieldName));
|
|
26395
|
+
if (isBlank(lodash.get(traveler, fieldPath))) {
|
|
26396
|
+
lodash.set(errors, `${path}.${fieldPath}`, formatTravelerField(room, pax, builtInFieldMeta[field.fieldName]?.label ?? field.fieldName));
|
|
26323
26397
|
}
|
|
26324
26398
|
});
|
|
26325
26399
|
};
|
|
26326
26400
|
values.rooms.forEach((r, rIndex) => r.adults.forEach((adult, index) => {
|
|
26327
|
-
if (lodash.isEmpty(adult.gender) &&
|
|
26401
|
+
if (lodash.isEmpty(adult.gender) && isCoreFieldRequired(adult, 'gender')) {
|
|
26328
26402
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].gender`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.GENDER));
|
|
26329
26403
|
}
|
|
26330
|
-
if (
|
|
26404
|
+
if (isCoreFieldRequired(adult, 'firstName') || !lodash.isEmpty(adult.firstName)) {
|
|
26331
26405
|
if (lodash.isEmpty(adult.firstName)) {
|
|
26332
26406
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].firstName`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.FIRST_NAME));
|
|
26333
26407
|
}
|
|
@@ -26335,7 +26409,7 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26335
26409
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].firstName`, formatTravelerFieldInvalidCharacters(rIndex + 1, index + 1, translations.TRAVELERS_FORM.FIRST_NAME));
|
|
26336
26410
|
}
|
|
26337
26411
|
}
|
|
26338
|
-
if (
|
|
26412
|
+
if (isCoreFieldRequired(adult, 'lastName') || !lodash.isEmpty(adult.lastName)) {
|
|
26339
26413
|
if (lodash.isEmpty(adult.lastName)) {
|
|
26340
26414
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].lastName`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.LAST_NAME));
|
|
26341
26415
|
}
|
|
@@ -26343,7 +26417,7 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26343
26417
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].lastName`, formatTravelerFieldInvalidCharacters(rIndex + 1, index + 1, translations.TRAVELERS_FORM.LAST_NAME));
|
|
26344
26418
|
}
|
|
26345
26419
|
}
|
|
26346
|
-
if (
|
|
26420
|
+
if (isCoreFieldRequired(adult, 'birthDate', 'dateOfBirth') || !lodash.isEmpty(adult.birthDate)) {
|
|
26347
26421
|
if (lodash.isEmpty(adult.birthDate) || !isValidBirthDate(adult.birthDate)) {
|
|
26348
26422
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].birthDate`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
26349
26423
|
}
|
|
@@ -26359,10 +26433,10 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26359
26433
|
validateRequiredBuiltInFields(adult, `rooms[${rIndex}].adults[${index}]`, rIndex + 1, index + 1);
|
|
26360
26434
|
}));
|
|
26361
26435
|
values.rooms.forEach((r, rIndex) => r.children.forEach((child, index) => {
|
|
26362
|
-
if (lodash.isEmpty(child.gender) &&
|
|
26436
|
+
if (lodash.isEmpty(child.gender) && isCoreFieldRequired(child, 'gender')) {
|
|
26363
26437
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].gender`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.GENDER));
|
|
26364
26438
|
}
|
|
26365
|
-
if (
|
|
26439
|
+
if (isCoreFieldRequired(child, 'firstName') || !lodash.isEmpty(child.firstName)) {
|
|
26366
26440
|
if (lodash.isEmpty(child.firstName)) {
|
|
26367
26441
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].firstName`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.FIRST_NAME));
|
|
26368
26442
|
}
|
|
@@ -26370,7 +26444,7 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26370
26444
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].firstName`, formatTravelerFieldInvalidCharacters(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.FIRST_NAME));
|
|
26371
26445
|
}
|
|
26372
26446
|
}
|
|
26373
|
-
if (
|
|
26447
|
+
if (isCoreFieldRequired(child, 'lastName') || !lodash.isEmpty(child.lastName)) {
|
|
26374
26448
|
if (lodash.isEmpty(child.lastName)) {
|
|
26375
26449
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].lastName`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.LAST_NAME));
|
|
26376
26450
|
}
|
|
@@ -26378,7 +26452,7 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26378
26452
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].lastName`, formatTravelerFieldInvalidCharacters(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.LAST_NAME));
|
|
26379
26453
|
}
|
|
26380
26454
|
}
|
|
26381
|
-
if (
|
|
26455
|
+
if (isCoreFieldRequired(child, 'birthDate', 'dateOfBirth') || !lodash.isEmpty(child.birthDate)) {
|
|
26382
26456
|
if (lodash.isEmpty(child.birthDate) || !isValidBirthDate(child.birthDate)) {
|
|
26383
26457
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].birthDate`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
26384
26458
|
}
|
|
@@ -26404,7 +26478,8 @@ const validateForm = (values, agentRequired, bookingType, translations, formFiel
|
|
|
26404
26478
|
if (values.mainBookerId < 0) {
|
|
26405
26479
|
errors.mainBookerId = translations.TRAVELERS_FORM.VALIDATION.NO_MAIN_BOOKER_SELECTED;
|
|
26406
26480
|
}
|
|
26407
|
-
if (
|
|
26481
|
+
if (hasConfiguredMainBookerFields(paxFields)) ;
|
|
26482
|
+
else if (bookingType != 'b2b' || mainBookerFormFields?.length) {
|
|
26408
26483
|
if (lodash.isEmpty(values.street) && isMainBookerFormFieldPresent('street')) {
|
|
26409
26484
|
errors.street = format$1(translations.TRAVELERS_FORM.VALIDATION.MAIN_BOOKER_FIELD, [translations.TRAVELERS_FORM.STREET]);
|
|
26410
26485
|
}
|
|
@@ -29183,6 +29258,7 @@ const initialState$1 = {
|
|
|
29183
29258
|
bookingNumber: undefined,
|
|
29184
29259
|
trajectNodeAvailability: {},
|
|
29185
29260
|
trajectFlightAvailability: {},
|
|
29261
|
+
trajectTransferAvailability: {},
|
|
29186
29262
|
trajectDaySuggestions: {},
|
|
29187
29263
|
trajectEditLineGuid: null,
|
|
29188
29264
|
trajectActivitiesDayOrder: null,
|
|
@@ -29395,6 +29471,12 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29395
29471
|
node.selectedCode = code;
|
|
29396
29472
|
node.status = 'resolved';
|
|
29397
29473
|
},
|
|
29474
|
+
clearTrajectNodeSelection(state, action) {
|
|
29475
|
+
const node = state.trajectNodeAvailability[action.payload];
|
|
29476
|
+
if (!node)
|
|
29477
|
+
return;
|
|
29478
|
+
node.selectedCode = null;
|
|
29479
|
+
},
|
|
29398
29480
|
updateEditableEntryLine(state, action) {
|
|
29399
29481
|
if (!state.editablePackagingEntry)
|
|
29400
29482
|
return;
|
|
@@ -29408,6 +29490,32 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29408
29490
|
return;
|
|
29409
29491
|
state.editablePackagingEntry.lines = [...(state.editablePackagingEntry.lines ?? []), ...action.payload];
|
|
29410
29492
|
},
|
|
29493
|
+
upsertEditableEntryLines(state, action) {
|
|
29494
|
+
if (!state.editablePackagingEntry)
|
|
29495
|
+
return;
|
|
29496
|
+
const lines = [...(state.editablePackagingEntry.lines ?? [])];
|
|
29497
|
+
action.payload.forEach((line) => {
|
|
29498
|
+
const index = lines.findIndex((x) => x.guid === line.guid);
|
|
29499
|
+
if (index === -1)
|
|
29500
|
+
lines.push(line);
|
|
29501
|
+
else
|
|
29502
|
+
lines[index] = line;
|
|
29503
|
+
});
|
|
29504
|
+
state.editablePackagingEntry.lines = lines;
|
|
29505
|
+
const restored = new Set(action.payload.map((line) => line.trajectNodeId).filter((x) => x != null));
|
|
29506
|
+
if (restored.size && state.editablePackagingEntry.removedTrajectNodeIds?.length) {
|
|
29507
|
+
state.editablePackagingEntry.removedTrajectNodeIds = state.editablePackagingEntry.removedTrajectNodeIds.filter((x) => !restored.has(x));
|
|
29508
|
+
}
|
|
29509
|
+
},
|
|
29510
|
+
removeTrajectNode(state, action) {
|
|
29511
|
+
if (!state.editablePackagingEntry)
|
|
29512
|
+
return;
|
|
29513
|
+
const guids = new Set(action.payload.lineGuids);
|
|
29514
|
+
state.editablePackagingEntry.lines = (state.editablePackagingEntry.lines ?? []).filter((x) => !guids.has(x.guid));
|
|
29515
|
+
const removed = state.editablePackagingEntry.removedTrajectNodeIds ?? [];
|
|
29516
|
+
if (!removed.includes(action.payload.nodeId))
|
|
29517
|
+
state.editablePackagingEntry.removedTrajectNodeIds = [...removed, action.payload.nodeId];
|
|
29518
|
+
},
|
|
29411
29519
|
removeEditableEntryLines(state, action) {
|
|
29412
29520
|
if (!state.editablePackagingEntry)
|
|
29413
29521
|
return;
|
|
@@ -29439,6 +29547,19 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29439
29547
|
flight.selectedGuid = action.payload.guid;
|
|
29440
29548
|
flight.status = 'resolved';
|
|
29441
29549
|
},
|
|
29550
|
+
clearTrajectFlightSelection(state, action) {
|
|
29551
|
+
const flight = state.trajectFlightAvailability[action.payload];
|
|
29552
|
+
if (!flight)
|
|
29553
|
+
return;
|
|
29554
|
+
flight.selectedGuid = null;
|
|
29555
|
+
},
|
|
29556
|
+
setTrajectTransferLoading(state, action) {
|
|
29557
|
+
state.trajectTransferAvailability[action.payload] = { nodeId: action.payload, status: 'loading', result: null };
|
|
29558
|
+
},
|
|
29559
|
+
setTrajectTransferAvailability(state, action) {
|
|
29560
|
+
const { nodeId, result } = action.payload;
|
|
29561
|
+
state.trajectTransferAvailability[nodeId] = { nodeId, status: result ? 'resolved' : 'unavailable', result };
|
|
29562
|
+
},
|
|
29442
29563
|
setTrajectDaySuggestionsLoading(state, action) {
|
|
29443
29564
|
const dayOrder = action.payload;
|
|
29444
29565
|
state.trajectDaySuggestions[dayOrder] = {
|
|
@@ -29475,6 +29596,7 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29475
29596
|
resetTrajectAvailability(state) {
|
|
29476
29597
|
state.trajectNodeAvailability = {};
|
|
29477
29598
|
state.trajectFlightAvailability = {};
|
|
29599
|
+
state.trajectTransferAvailability = {};
|
|
29478
29600
|
state.trajectDaySuggestions = {};
|
|
29479
29601
|
state.trajectEditLineGuid = null;
|
|
29480
29602
|
state.trajectActivitiesDayOrder = null;
|
|
@@ -29483,7 +29605,7 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29483
29605
|
}
|
|
29484
29606
|
}
|
|
29485
29607
|
});
|
|
29486
|
-
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectResolving, setTrajectItineraryView, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
29608
|
+
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, clearTrajectNodeSelection, setTrajectResolving, setTrajectItineraryView, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, clearTrajectFlightSelection, setTrajectTransferLoading, setTrajectTransferAvailability, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, upsertEditableEntryLines, removeTrajectNode, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
29487
29609
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
29488
29610
|
|
|
29489
29611
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -29698,7 +29820,7 @@ const numberOfNights = (segment) => {
|
|
|
29698
29820
|
const CHARTER_SERVICE_TYPE = 10;
|
|
29699
29821
|
const CRUISE_SERVICE_TYPE = 11;
|
|
29700
29822
|
const FERRY_SERVICE_TYPE = 12;
|
|
29701
|
-
const TRANSFER_SERVICE_TYPE = 13;
|
|
29823
|
+
const TRANSFER_SERVICE_TYPE$1 = 13;
|
|
29702
29824
|
const BUS_SERVICE_TYPE = 14;
|
|
29703
29825
|
const CAR_SERVICE_TYPE = 17;
|
|
29704
29826
|
const CAMPER_SERVICE_TYPE = 18;
|
|
@@ -29730,7 +29852,7 @@ const getServiceTypeIcon = (serviceType) => {
|
|
|
29730
29852
|
return 'ui-bus';
|
|
29731
29853
|
case TRAIN_SERVICE_TYPE:
|
|
29732
29854
|
return 'ui-train';
|
|
29733
|
-
case TRANSFER_SERVICE_TYPE:
|
|
29855
|
+
case TRANSFER_SERVICE_TYPE$1:
|
|
29734
29856
|
return 'ui-transfer';
|
|
29735
29857
|
case EVENT_SERVICE_TYPE:
|
|
29736
29858
|
return 'ui-ticket';
|
|
@@ -29866,6 +29988,81 @@ const getTrajectNodesWithLines = (trajectEntry) => {
|
|
|
29866
29988
|
})
|
|
29867
29989
|
.filter((x) => !!x.line);
|
|
29868
29990
|
};
|
|
29991
|
+
// Qite.Offer.Enums.ServiceType.Transfer: a traject's connecting node is booked as one.
|
|
29992
|
+
const TRANSFER_SERVICE_TYPE = 13;
|
|
29993
|
+
// Qite.Offer.Enums.RequirementType. A Mandatory (0) stop, and one RequiredInGroup (3) — one of the node's
|
|
29994
|
+
// alternatives has to be booked — always keep a selection; only these two may be taken off the trip.
|
|
29995
|
+
const OPTIONAL_REQUIREMENT_TYPE = 1;
|
|
29996
|
+
const OPTIONAL_IN_GROUP_REQUIREMENT_TYPE = 2;
|
|
29997
|
+
const canRemoveTrajectNode = (node) => node.requirementType === OPTIONAL_REQUIREMENT_TYPE || node.requirementType === OPTIONAL_IN_GROUP_REQUIREMENT_TYPE;
|
|
29998
|
+
const getDroppedTrajectNodeIds = (trajectEntry, entry) => {
|
|
29999
|
+
const dropped = new Set();
|
|
30000
|
+
if (!entry || !!trajectEntry.entry.magicLink)
|
|
30001
|
+
return dropped;
|
|
30002
|
+
const removed = new Set(entry.removedTrajectNodeIds ?? []);
|
|
30003
|
+
if (!removed.size)
|
|
30004
|
+
return dropped;
|
|
30005
|
+
const byDay = new Map();
|
|
30006
|
+
[...(trajectEntry.nodes ?? [])]
|
|
30007
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30008
|
+
.forEach((node) => byDay.set(node.dayOrder, [...(byDay.get(node.dayOrder) ?? []), node]));
|
|
30009
|
+
byDay.forEach((nodes) => nodes.forEach((node, index) => {
|
|
30010
|
+
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE || !canRemoveTrajectNode(node) || !removed.has(node.nodeId))
|
|
30011
|
+
return;
|
|
30012
|
+
dropped.add(node.nodeId);
|
|
30013
|
+
[nodes[index - 1], nodes[index + 1]]
|
|
30014
|
+
.filter((x) => !!x && x.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE)
|
|
30015
|
+
.forEach((x) => dropped.add(x.nodeId));
|
|
30016
|
+
}));
|
|
30017
|
+
return dropped;
|
|
30018
|
+
};
|
|
30019
|
+
const isTrajectTransferNode = (node) => node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE && !!node.pickUp && !!node.dropOff;
|
|
30020
|
+
const buildTrajectTransferRequest = (node, context) => ({
|
|
30021
|
+
transactionId: context.transactionId,
|
|
30022
|
+
officeId: context.officeId,
|
|
30023
|
+
agentId: context.agentId ?? null,
|
|
30024
|
+
catalogueId: context.catalogueId,
|
|
30025
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
30026
|
+
language: context.language,
|
|
30027
|
+
date: node.from,
|
|
30028
|
+
pickUp: node.pickUp,
|
|
30029
|
+
dropOff: node.dropOff,
|
|
30030
|
+
travellers: context.rooms.flatMap((room) => room.travellers)
|
|
30031
|
+
});
|
|
30032
|
+
const buildTrajectTransferLine = (node, result) => {
|
|
30033
|
+
const option = result.rooms?.[0]?.options?.find((x) => x.isSelected) ?? result.rooms?.[0]?.options?.[0];
|
|
30034
|
+
if (!option?.guid)
|
|
30035
|
+
return null;
|
|
30036
|
+
return {
|
|
30037
|
+
guid: option.guid,
|
|
30038
|
+
parentGuid: null,
|
|
30039
|
+
order: 0,
|
|
30040
|
+
trajectNodeId: node.nodeId,
|
|
30041
|
+
isChanged: true,
|
|
30042
|
+
from: result.fromDate ?? node.from,
|
|
30043
|
+
to: result.toDate ?? node.to,
|
|
30044
|
+
moment: node.moment ?? '',
|
|
30045
|
+
serviceType: TRANSFER_SERVICE_TYPE,
|
|
30046
|
+
productName: result.name,
|
|
30047
|
+
productCode: result.code,
|
|
30048
|
+
accommodationName: option.accommodationName,
|
|
30049
|
+
accommodationCode: option.accommodationCode,
|
|
30050
|
+
regimeName: option.regimeName,
|
|
30051
|
+
regimeCode: option.regimeCode,
|
|
30052
|
+
vendor: result.vendorType ?? null,
|
|
30053
|
+
externalVendorId: result.vendorId ?? null,
|
|
30054
|
+
groupKey: '',
|
|
30055
|
+
isActive: true,
|
|
30056
|
+
country: result.countryId ? { id: result.countryId, name: result.countryName, localizations: [] } : null,
|
|
30057
|
+
region: result.regionId ? { id: result.regionId, name: result.regionName, localizations: [] } : null,
|
|
30058
|
+
oord: result.oordId ? { id: result.oordId, name: result.oordName, localizations: [] } : null,
|
|
30059
|
+
location: result.locationId ? { id: result.locationId, name: result.locationName, localizations: [] } : null,
|
|
30060
|
+
longitude: result.longitude ?? null,
|
|
30061
|
+
latitude: result.latitude ?? null,
|
|
30062
|
+
pax: (option.paxIds ?? []).map((paxId, paxIndex) => ({ paxId, room: 0, order: paxIndex })),
|
|
30063
|
+
flightInformation: null
|
|
30064
|
+
};
|
|
30065
|
+
};
|
|
29869
30066
|
const getCoordinates = (line, node, result) => {
|
|
29870
30067
|
const sources = [
|
|
29871
30068
|
...(result ? [result] : []),
|
|
@@ -30293,9 +30490,15 @@ const nightsOf = (node, line) => dateFns.differenceInCalendarDays(new Date(line?
|
|
|
30293
30490
|
const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
30294
30491
|
const timeline = React.useMemo(() => {
|
|
30295
30492
|
const linesByGuid = new Map((packagingEntry.lines ?? []).map((line) => [line.guid, line]));
|
|
30493
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, packagingEntry);
|
|
30296
30494
|
const nodes = [...(trajectEntry.nodes ?? [])]
|
|
30495
|
+
.filter((node) => !dropped.has(node.nodeId))
|
|
30297
30496
|
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30298
30497
|
.map((node) => {
|
|
30498
|
+
// A connecting node has no line of its own; the transfer found for it carries its node id.
|
|
30499
|
+
if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
30500
|
+
return { node, lines: (packagingEntry.lines ?? []).filter((line) => line.trajectNodeId === node.nodeId) };
|
|
30501
|
+
}
|
|
30299
30502
|
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30300
30503
|
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30301
30504
|
const groupKey = lines.length ? getLineGroupKey(lines[0]) : '';
|
|
@@ -30365,10 +30568,11 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
30365
30568
|
React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
30366
30569
|
React__default["default"].createElement("strong", null, dateFns.format(new Date(value), 'd'))),
|
|
30367
30570
|
React__default["default"].createElement("p", null, dateFns.format(new Date(value), 'MMM'))));
|
|
30368
|
-
const renderConnectingNode = ({ node }) => {
|
|
30571
|
+
const renderConnectingNode = ({ node, lines }) => {
|
|
30369
30572
|
const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
|
|
30370
30573
|
// How long the hop takes and how far it is, the way the traject editor puts them.
|
|
30371
30574
|
const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
|
|
30575
|
+
const title = lines[0]?.productName || node.name;
|
|
30372
30576
|
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
30373
30577
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30374
30578
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
@@ -30377,8 +30581,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
30377
30581
|
React__default["default"].createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
30378
30582
|
React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
30379
30583
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30380
|
-
React__default["default"].createElement("h6", null,
|
|
30381
|
-
!!
|
|
30584
|
+
React__default["default"].createElement("h6", null, title || journey),
|
|
30585
|
+
!!title && !!journey && React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
|
|
30382
30586
|
};
|
|
30383
30587
|
const renderOvernight = (item, index) => {
|
|
30384
30588
|
const { dayOrder } = item.node;
|
|
@@ -30491,8 +30695,13 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
30491
30695
|
*/
|
|
30492
30696
|
const isBookedEntryTraject = (trajectEntry) => !!trajectEntry?.entry?.magicLink;
|
|
30493
30697
|
const NODE_SERVICE_TYPES = new Set([ACCOMMODATION_SERVICE_TYPE, EXCURSION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE]);
|
|
30698
|
+
const isBookedRoomLineOf = (line, root) => !!line.parentGuid &&
|
|
30699
|
+
(line.parentGuid === root.guid || line.parentGuid === root.parentGuid) &&
|
|
30700
|
+
line.serviceType === root.serviceType &&
|
|
30701
|
+
line.productCode === root.productCode &&
|
|
30702
|
+
dateFns.isSameDay(new Date(line.from), new Date(root.from));
|
|
30494
30703
|
/**
|
|
30495
|
-
* A room or option line joins its
|
|
30704
|
+
* A room or option line joins its service's node; a flight line never does — an outward and its
|
|
30496
30705
|
* return are separate legs of the trip even though the return points at the outward as parent.
|
|
30497
30706
|
*
|
|
30498
30707
|
* Lines that share a group key are one choice (a couple of hotels of which one is picked), so
|
|
@@ -30500,16 +30709,13 @@ const NODE_SERVICE_TYPES = new Set([ACCOMMODATION_SERVICE_TYPE, EXCURSION_SERVIC
|
|
|
30500
30709
|
*/
|
|
30501
30710
|
const groupNodeLines = (lines) => {
|
|
30502
30711
|
const groups = [];
|
|
30503
|
-
const groupByGuid = new Map();
|
|
30504
30712
|
lines.forEach((line) => {
|
|
30505
|
-
const
|
|
30506
|
-
if (
|
|
30507
|
-
|
|
30713
|
+
const service = line.serviceType !== FLIGHT_SERVICE_TYPE ? groups.find((group) => isBookedRoomLineOf(line, group[0])) : undefined;
|
|
30714
|
+
if (service) {
|
|
30715
|
+
service.push(line);
|
|
30508
30716
|
return;
|
|
30509
30717
|
}
|
|
30510
|
-
|
|
30511
|
-
groups.push(group);
|
|
30512
|
-
groupByGuid.set(line.guid, group);
|
|
30718
|
+
groups.push([line]);
|
|
30513
30719
|
});
|
|
30514
30720
|
const nodes = [];
|
|
30515
30721
|
const nodeByGroupKey = new Map();
|
|
@@ -30566,11 +30772,13 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30566
30772
|
date: toDateOnlyString(dateFns.addDays(start, order))
|
|
30567
30773
|
}));
|
|
30568
30774
|
const dayOrderOf = (line) => Math.max(dateFns.differenceInCalendarDays(new Date(line.from), start), 0);
|
|
30569
|
-
|
|
30570
|
-
|
|
30775
|
+
const flights = lodash.sortBy(lines.filter((line) => line.serviceType === FLIGHT_SERVICE_TYPE && isActiveEntryLine(line)), (line) => new Date(line.from).getTime(), (line) => line.order);
|
|
30776
|
+
const outward = lodash.first(flights);
|
|
30777
|
+
const homeward = flights.length > 1 ? lodash.last(flights) : undefined;
|
|
30778
|
+
const placeInDay = (line) => (line === outward ? -1 : line === homeward ? 1 : 0);
|
|
30571
30779
|
const orderedGroups = groupNodeLines(lines).sort((a, b) => {
|
|
30572
30780
|
const [lineA, lineB] = [a.lines[0], b.lines[0]];
|
|
30573
|
-
return dayOrderOf(lineA) - dayOrderOf(lineB) ||
|
|
30781
|
+
return dayOrderOf(lineA) - dayOrderOf(lineB) || placeInDay(lineA) - placeInDay(lineB) || lineA.order - lineB.order;
|
|
30574
30782
|
});
|
|
30575
30783
|
const nodes = orderedGroups.map(({ lines: groupLines, alternatives }, index) => {
|
|
30576
30784
|
const line = groupLines[0];
|
|
@@ -33629,7 +33837,7 @@ const TrajectResultsFlyIn = ({ isLoading }) => {
|
|
|
33629
33837
|
if (!result)
|
|
33630
33838
|
return;
|
|
33631
33839
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
33632
|
-
buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])
|
|
33840
|
+
dispatch(upsertEditableEntryLines(buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])));
|
|
33633
33841
|
dispatch(setTrajectEditLineGuid(null));
|
|
33634
33842
|
dispatch(setFlyInIsOpen(false));
|
|
33635
33843
|
};
|
|
@@ -33674,7 +33882,7 @@ const TrajectFlightsFlyIn = ({ isLoading }) => {
|
|
|
33674
33882
|
if (!flight)
|
|
33675
33883
|
return;
|
|
33676
33884
|
dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
|
|
33677
|
-
dispatch(
|
|
33885
|
+
dispatch(upsertEditableEntryLines([applyFlightToLine(item.line, flight)]));
|
|
33678
33886
|
dispatch(setTrajectEditLineGuid(null));
|
|
33679
33887
|
dispatch(setFlyInIsOpen(false));
|
|
33680
33888
|
};
|
|
@@ -33737,10 +33945,16 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
33737
33945
|
if (!item)
|
|
33738
33946
|
return;
|
|
33739
33947
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code: result.code }));
|
|
33740
|
-
buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])
|
|
33948
|
+
dispatch(upsertEditableEntryLines(buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])));
|
|
33741
33949
|
dispatch(setTrajectActivitiesDayOrder(null));
|
|
33742
33950
|
dispatch(setFlyInIsOpen(false));
|
|
33743
33951
|
};
|
|
33952
|
+
const handleRemove = ({ item }) => {
|
|
33953
|
+
if (!item)
|
|
33954
|
+
return;
|
|
33955
|
+
dispatch(clearTrajectNodeSelection(item.line.guid));
|
|
33956
|
+
dispatch(removeTrajectNode({ nodeId: item.node.nodeId, lineGuids: item.lines.map((line) => line.guid) }));
|
|
33957
|
+
};
|
|
33744
33958
|
if (isLoading || suggestions?.status === 'loading') {
|
|
33745
33959
|
return React__default["default"].createElement(React__default["default"].Fragment, null, context.customSpinner ?? React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }));
|
|
33746
33960
|
}
|
|
@@ -33754,7 +33968,8 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
33754
33968
|
const { result, item } = card;
|
|
33755
33969
|
const isSelected = item ? trajectNodeAvailability[item.line.guid]?.selectedCode === result.code : getAddedLines(result).length > 0;
|
|
33756
33970
|
const isBookedCard = !!item && bookedMode && matchesCode(result, item.node.code ?? '');
|
|
33757
|
-
|
|
33971
|
+
const canRemove = !!item && isSelected && !bookedMode && canRemoveTrajectNode(item.node);
|
|
33972
|
+
return (React__default["default"].createElement(TrajectNodeCard, { key: `activity-${result.code}`, node: item?.node, result: result, isSelected: isSelected, languageCode: context.languageCode, translations: translations, onSelect: () => (!item ? handleToggle(result) : canRemove ? handleRemove(card) : handleSelect(card)), selectLabel: item ? undefined : translations.SRP.ADD, selectedLabel: item && !canRemove ? (isBookedCard ? translations.SHARED.BOOKED : undefined) : translations.SRP.REMOVE }));
|
|
33758
33973
|
})))));
|
|
33759
33974
|
};
|
|
33760
33975
|
|
|
@@ -36148,16 +36363,18 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36148
36363
|
apiKey: context.tideConnection.apiKey
|
|
36149
36364
|
};
|
|
36150
36365
|
}, [context]);
|
|
36366
|
+
// The field configuration on the entry is leading for which traveler fields are shown and required.
|
|
36367
|
+
const paxFields = editablePackagingEntry?.paxFields ?? [];
|
|
36151
36368
|
const initialValues = React.useMemo(() => {
|
|
36152
36369
|
if (!editablePackagingEntry) {
|
|
36153
36370
|
return {};
|
|
36154
36371
|
}
|
|
36155
|
-
return createInitialValuesFromEditablePackagingEntry(editablePackagingEntry);
|
|
36372
|
+
return createInitialValuesFromEditablePackagingEntry(editablePackagingEntry, undefined, paxFields);
|
|
36156
36373
|
}, [editablePackagingEntry?.transactionId]);
|
|
36157
36374
|
const formik$1 = formik.useFormik({
|
|
36158
36375
|
initialValues,
|
|
36159
36376
|
enableReinitialize: true,
|
|
36160
|
-
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields, undefined, undefined,
|
|
36377
|
+
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields, undefined, undefined, paxFields),
|
|
36161
36378
|
onSubmit: (values) => {
|
|
36162
36379
|
if (!editablePackagingEntry)
|
|
36163
36380
|
return;
|
|
@@ -36242,7 +36459,7 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36242
36459
|
".\u00A0",
|
|
36243
36460
|
stepLabels[step])) },
|
|
36244
36461
|
isConfirmationPage && isLoading && React__default["default"].createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING }),
|
|
36245
|
-
currentStep === 0 && (React__default["default"].createElement(SharedTravelersForm, { formik: formik$1, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields:
|
|
36462
|
+
currentStep === 0 && (React__default["default"].createElement(SharedTravelersForm, { formik: formik$1, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields: paxFields })),
|
|
36246
36463
|
currentStep === 1 && (React__default["default"].createElement(SharedSummary, { translations: translations, travelerFormValues: formik$1.values, isSubmitting: isSubmitting, skipPayment: !context.generatePaymentUrl, userValidated: userValidated, remarks: remarks, enableVoucher: false, allowOption: false, isOffer: false, onUserValidatedChange: setUserValidated, onRemarksChange: setRemarks, onSubmit: handleSummarySubmit, renderOptions: () => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations, selectedPackagingAccoResult?.currencyCode), renderPreviousButton: () => (React__default["default"].createElement("button", { type: "button", title: translations.STEPS.PREVIOUS, onClick: () => dispatch(setCurrentStep(0)), className: "cta cta--secondary" }, translations.STEPS.PREVIOUS)) })),
|
|
36247
36464
|
currentStep === 2 && (React__default["default"].createElement(SharedConfirmation, { bookingNumber: bookingNumber ?? editablePackagingEntry?.dossierNumber ?? '', isOption: false, isOffer: false, translations: translations.CONFIRMATION })),
|
|
36248
36465
|
currentStep === 3 && React__default["default"].createElement("div", null))),
|
|
@@ -36342,11 +36559,13 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36342
36559
|
});
|
|
36343
36560
|
return map;
|
|
36344
36561
|
}, [trajectEntry]);
|
|
36562
|
+
const droppedNodeIds = trajectEntry ? getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry) : new Set();
|
|
36563
|
+
const droppedKey = [...droppedNodeIds].sort((a, b) => a - b).join(',');
|
|
36345
36564
|
const { stayByDay, stayStartByNodeId } = React.useMemo(() => {
|
|
36346
36565
|
const byDay = new Map();
|
|
36347
36566
|
const byNodeId = new Map();
|
|
36348
36567
|
nodesWithLines
|
|
36349
|
-
.filter((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
36568
|
+
.filter((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE && !droppedNodeIds.has(x.node.nodeId))
|
|
36350
36569
|
.forEach((item) => {
|
|
36351
36570
|
const nights = Math.max(dateFns.differenceInCalendarDays(new Date(item.line.to), new Date(item.line.from)), 1);
|
|
36352
36571
|
byNodeId.set(item.node.nodeId, { item, night: 0, nights });
|
|
@@ -36355,7 +36574,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36355
36574
|
}
|
|
36356
36575
|
});
|
|
36357
36576
|
return { stayByDay: byDay, stayStartByNodeId: byNodeId };
|
|
36358
|
-
}, [nodesWithLines]);
|
|
36577
|
+
}, [nodesWithLines, droppedKey]);
|
|
36359
36578
|
/** Whether every node of the trip has an answer, one way or the other. */
|
|
36360
36579
|
const tripResolved = React.useMemo(() => nodesWithLines
|
|
36361
36580
|
.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node))
|
|
@@ -36382,7 +36601,20 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36382
36601
|
if (!result)
|
|
36383
36602
|
return;
|
|
36384
36603
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
36385
|
-
buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])
|
|
36604
|
+
dispatch(upsertEditableEntryLines(buildNodeSelectionLines(item, result, bookedMode, editablePackagingEntry?.lines ?? [])));
|
|
36605
|
+
};
|
|
36606
|
+
/**
|
|
36607
|
+
* An optional stop can be taken off the trip; a mandatory one, or one of which the group needs a pick,
|
|
36608
|
+
* only swapped. A booked entry's services are never taken off here.
|
|
36609
|
+
*/
|
|
36610
|
+
const canRemove = (item) => !bookedMode && canRemoveTrajectNode(item.node);
|
|
36611
|
+
const handleRemoveNode = (item) => {
|
|
36612
|
+
dispatch(clearTrajectNodeSelection(item.line.guid));
|
|
36613
|
+
dispatch(removeTrajectNode({ nodeId: item.node.nodeId, lineGuids: item.lines.map((line) => line.guid) }));
|
|
36614
|
+
};
|
|
36615
|
+
const handleRemoveFlight = (item) => {
|
|
36616
|
+
dispatch(clearTrajectFlightSelection(item.line.guid));
|
|
36617
|
+
dispatch(removeTrajectNode({ nodeId: item.node.nodeId, lineGuids: item.lines.map((line) => line.guid) }));
|
|
36386
36618
|
};
|
|
36387
36619
|
/** The remaining legs for a flight node open in the fly-in, as the regular flight results do. */
|
|
36388
36620
|
const handleShowMoreFlights = (item) => {
|
|
@@ -36450,9 +36682,17 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36450
36682
|
// A hotel row holds the hotels configured on the node; a portal can also let it fill up with
|
|
36451
36683
|
// non-preferred hotels found in the area, the rest staying behind "show more".
|
|
36452
36684
|
const fill = showNonPreferredHotels && node.serviceType === ACCOMMODATION_SERVICE_TYPE ? others.slice(0, Math.max(MAX_HOTEL_RESULTS - 1 - configured.length, 0)) : [];
|
|
36453
|
-
const remaining = others.length - fill.length;
|
|
36454
36685
|
// No entry yet means the node's search has been queued but not dispatched — still loading.
|
|
36455
36686
|
const status = availability?.status ?? 'loading';
|
|
36687
|
+
// A stop taken off the trip keeps its results in view, to pick one back.
|
|
36688
|
+
const isRemoved = status === 'resolved' && !selected && results.length > 0;
|
|
36689
|
+
const removable = canRemove(item);
|
|
36690
|
+
const shown = selected
|
|
36691
|
+
? [selected, ...configured, ...fill]
|
|
36692
|
+
: configured.length || fill.length
|
|
36693
|
+
? [...configured, ...fill]
|
|
36694
|
+
: others.slice(0, MAX_HOTEL_RESULTS);
|
|
36695
|
+
const remaining = results.length - shown.length;
|
|
36456
36696
|
return (React__default["default"].createElement(React__default["default"].Fragment, { key: line.guid },
|
|
36457
36697
|
showHeader && (React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
36458
36698
|
React__default["default"].createElement("div", { className: "search__results__label__date" },
|
|
@@ -36464,13 +36704,18 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36464
36704
|
React__default["default"].createElement("strong", null, headerTitle || node.name))))),
|
|
36465
36705
|
status === 'loading' && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }),
|
|
36466
36706
|
status === 'unavailable' && React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
36467
|
-
status === 'resolved' && selected && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
36468
|
-
availability.preferredUnavailable && node.preferredAccommodationName && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
36707
|
+
status === 'resolved' && (selected || isRemoved) && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
36708
|
+
selected && availability.preferredUnavailable && node.preferredAccommodationName && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
36469
36709
|
React__default["default"].createElement("span", null,
|
|
36470
36710
|
node.preferredAccommodationName,
|
|
36471
36711
|
" \u2014 ",
|
|
36472
36712
|
translations.SRP.NO_RESULTS))),
|
|
36473
|
-
React__default["default"].createElement(TrajectCardRow, { selectionKey: selected
|
|
36713
|
+
React__default["default"].createElement(TrajectCardRow, { selectionKey: selected?.code ?? '' }, shown.map((result) => {
|
|
36714
|
+
const isSelected = result.code === selected?.code;
|
|
36715
|
+
return (React__default["default"].createElement(TrajectNodeCard, { key: result.code, node: node, result: result, isSelected: isSelected, languageCode: context.languageCode, translations: translations, onSelect: () => (isSelected && removable ? handleRemoveNode(item) : handleSelect(item, result.code)), onEditOptions: node.serviceType === ACCOMMODATION_SERVICE_TYPE ? () => handleEditOptions(item) : undefined, showNights: node.serviceType === ACCOMMODATION_SERVICE_TYPE,
|
|
36716
|
+
// selectedLabel={bookedMode && matchesCode(result, node.code ?? '') ? translations.SHARED.BOOKED : undefined}
|
|
36717
|
+
selectedLabel: removable ? translations.SRP.REMOVE : undefined }));
|
|
36718
|
+
})),
|
|
36474
36719
|
remaining > 0 && !hideShowMoreResults && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
36475
36720
|
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMore(item) },
|
|
36476
36721
|
translations.SRP.SHOW_MORE,
|
|
@@ -36480,7 +36725,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36480
36725
|
};
|
|
36481
36726
|
const handleSelectFlight = (item, flight) => {
|
|
36482
36727
|
dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
|
|
36483
|
-
dispatch(
|
|
36728
|
+
dispatch(upsertEditableEntryLines([buildFlightSelectionLine(item, flight, bookedMode)]));
|
|
36484
36729
|
};
|
|
36485
36730
|
const renderFlightNode = (item) => {
|
|
36486
36731
|
const { node, line } = item;
|
|
@@ -36489,7 +36734,9 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36489
36734
|
const flights = availability?.results ?? [];
|
|
36490
36735
|
const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
|
|
36491
36736
|
const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
|
|
36492
|
-
const
|
|
36737
|
+
const isRemoved = status === 'resolved' && !selected && flights.length > 0;
|
|
36738
|
+
const removable = canRemove(item);
|
|
36739
|
+
const visible = others.slice(0, selected ? 2 : 3);
|
|
36493
36740
|
return (React__default["default"].createElement(React__default["default"].Fragment, { key: line.guid },
|
|
36494
36741
|
React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
36495
36742
|
React__default["default"].createElement("div", { className: "search__results__label__date" },
|
|
@@ -36504,15 +36751,15 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36504
36751
|
node.arrivalAirportCode)))),
|
|
36505
36752
|
status === 'loading' && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }),
|
|
36506
36753
|
status === 'unavailable' && React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
36507
|
-
status === 'resolved' && selected && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
36754
|
+
status === 'resolved' && (selected || isRemoved) && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
36508
36755
|
React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
36509
|
-
React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price, selectedLabel: bookedMode && selected.outwardGuid === line.guid ? translations.SHARED.BOOKED : undefined }),
|
|
36510
|
-
visible.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected
|
|
36511
|
-
others.length >
|
|
36756
|
+
selected && (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price, onSelect: removable ? () => handleRemoveFlight(item) : undefined, selectedLabel: bookedMode && selected.outwardGuid === line.guid ? translations.SHARED.BOOKED : removable ? translations.SRP.REMOVE : undefined })),
|
|
36757
|
+
visible.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected?.price, onSelect: () => handleSelectFlight(item, result) })))),
|
|
36758
|
+
others.length > visible.length && !hideShowMoreResults && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
36512
36759
|
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreFlights(item) },
|
|
36513
36760
|
translations.SRP.SHOW_MORE,
|
|
36514
36761
|
" (",
|
|
36515
|
-
others.length -
|
|
36762
|
+
others.length - visible.length,
|
|
36516
36763
|
")")))))));
|
|
36517
36764
|
};
|
|
36518
36765
|
/** The lines a suggested activity put on the entry, which is also how we know it was added. */
|
|
@@ -36588,13 +36835,21 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36588
36835
|
")")))));
|
|
36589
36836
|
};
|
|
36590
36837
|
/**
|
|
36591
|
-
* An activity the traject books is swapped for another, the way a hotel is
|
|
36592
|
-
* found for themselves is added to the trip or taken back off it.
|
|
36838
|
+
* An activity the traject books is swapped for another, the way a hotel is, or - when it is optional -
|
|
36839
|
+
* taken off the trip; one the traveller found for themselves is added to the trip or taken back off it.
|
|
36593
36840
|
*/
|
|
36594
36841
|
const renderActivityCard = ({ result, item }) => {
|
|
36595
36842
|
const isSelected = isActivitySelected({ result, item });
|
|
36596
36843
|
const isBookedCard = !!item && bookedMode && matchesCode(result, item.node.code ?? '');
|
|
36597
|
-
|
|
36844
|
+
const removable = !!item && canRemove(item);
|
|
36845
|
+
const handleClick = () => {
|
|
36846
|
+
if (!item)
|
|
36847
|
+
return handleToggleActivity(result);
|
|
36848
|
+
if (isSelected && removable)
|
|
36849
|
+
return handleRemoveNode(item);
|
|
36850
|
+
handleSelect(item, result.code);
|
|
36851
|
+
};
|
|
36852
|
+
return (React__default["default"].createElement(TrajectNodeCard, { key: `activity-${result.code}`, node: item?.node, result: result, isSelected: isSelected, languageCode: context.languageCode, translations: translations, onSelect: handleClick, selectLabel: item ? undefined : translations.SRP.ADD, selectedLabel: item && !removable ? (isBookedCard ? translations.SHARED.BOOKED : undefined) : translations.SRP.REMOVE }));
|
|
36598
36853
|
};
|
|
36599
36854
|
if (isLoading)
|
|
36600
36855
|
return React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
|
|
@@ -36629,6 +36884,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36629
36884
|
const useTrajectAvailability = () => {
|
|
36630
36885
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
36631
36886
|
const dispatch = reactRedux.useDispatch();
|
|
36887
|
+
const { editablePackagingEntry, trajectTransferAvailability } = reactRedux.useSelector((state) => state.searchResults);
|
|
36632
36888
|
const trajectEntry = context?.trajectEntry;
|
|
36633
36889
|
const showApiItems = !!context?.showApiItems;
|
|
36634
36890
|
const hideShowMoreResults = !!context?.hideShowMoreResults;
|
|
@@ -36654,12 +36910,14 @@ const useTrajectAvailability = () => {
|
|
|
36654
36910
|
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
36655
36911
|
};
|
|
36656
36912
|
// Flight searches take passengers rather than rooms; the API buckets by age.
|
|
36913
|
+
// The ids must be the entry's own pax ids: the flight lines this search caches carry them as
|
|
36914
|
+
// EntryLinePax.PaxId, and booking maps those onto the entry's travelers (PackagingService.SyncPaxIds).
|
|
36657
36915
|
const pax = trajectEntry.entry.pax ?? [];
|
|
36658
36916
|
const ageOf = (p) => p.age ?? 30;
|
|
36659
|
-
const adults = pax.filter((p) => ageOf(p) >= 12)
|
|
36660
|
-
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12)
|
|
36661
|
-
const babies = pax.filter((p) => ageOf(p) < 2)
|
|
36662
|
-
const flightPax = lodash.concat(
|
|
36917
|
+
const adults = pax.filter((p) => ageOf(p) >= 12);
|
|
36918
|
+
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12);
|
|
36919
|
+
const babies = pax.filter((p) => ageOf(p) < 2);
|
|
36920
|
+
const flightPax = lodash.concat(adults.map((p) => ({ id: p.id, age: 31 })), kids.map((p) => ({ id: p.id, age: 8 })), babies.map((p) => ({ id: p.id, age: 1 })));
|
|
36663
36921
|
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
36664
36922
|
// The other members of a line group are alternatives whether or not a search still finds
|
|
36665
36923
|
// them; the ones it does not are drawn from their lines, as the booked service itself is.
|
|
@@ -36790,9 +37048,30 @@ const useTrajectAvailability = () => {
|
|
|
36790
37048
|
return item.lines.map((x) => x.guid);
|
|
36791
37049
|
}
|
|
36792
37050
|
};
|
|
37051
|
+
const resolveTransferNode = async (node) => {
|
|
37052
|
+
dispatch(setTrajectTransferLoading(node.nodeId));
|
|
37053
|
+
try {
|
|
37054
|
+
const results = (await build.searchPackagingTransfers(config, buildTrajectTransferRequest(node, searchContext), controller.signal)) ?? [];
|
|
37055
|
+
if (controller.signal.aborted)
|
|
37056
|
+
return [];
|
|
37057
|
+
const result = results[0] ?? null;
|
|
37058
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: result && buildTrajectTransferLine(node, result) ? result : null }));
|
|
37059
|
+
}
|
|
37060
|
+
catch (error) {
|
|
37061
|
+
if (controller.signal.aborted)
|
|
37062
|
+
return [];
|
|
37063
|
+
console.error('Failed to resolve traject transfer', node.name, error);
|
|
37064
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: null }));
|
|
37065
|
+
}
|
|
37066
|
+
return [];
|
|
37067
|
+
};
|
|
36793
37068
|
(async () => {
|
|
36794
37069
|
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
36795
|
-
const
|
|
37070
|
+
const transfers = bookedMode ? [] : (trajectEntry.nodes ?? []).filter(isTrajectTransferNode);
|
|
37071
|
+
const unresolved = (await Promise.all([
|
|
37072
|
+
...resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))),
|
|
37073
|
+
...transfers.map(resolveTransferNode)
|
|
37074
|
+
])).flat();
|
|
36796
37075
|
if (controller.signal.aborted)
|
|
36797
37076
|
return;
|
|
36798
37077
|
dispatch(setTrajectResolving(false));
|
|
@@ -36804,6 +37083,28 @@ const useTrajectAvailability = () => {
|
|
|
36804
37083
|
})();
|
|
36805
37084
|
return () => controller.abort();
|
|
36806
37085
|
}, [trajectEntry, showApiItems, hideShowMoreResults]);
|
|
37086
|
+
React.useEffect(() => {
|
|
37087
|
+
if (!trajectEntry || !editablePackagingEntry || isBookedEntryTraject(trajectEntry))
|
|
37088
|
+
return;
|
|
37089
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry);
|
|
37090
|
+
const guids = new Set((editablePackagingEntry.lines ?? []).map((line) => line.guid));
|
|
37091
|
+
const toAdd = [];
|
|
37092
|
+
const toRemove = [];
|
|
37093
|
+
(trajectEntry.nodes ?? []).filter(isTrajectTransferNode).forEach((node) => {
|
|
37094
|
+
const result = trajectTransferAvailability[node.nodeId]?.result;
|
|
37095
|
+
const line = result ? buildTrajectTransferLine(node, result) : null;
|
|
37096
|
+
if (!line)
|
|
37097
|
+
return;
|
|
37098
|
+
if (dropped.has(node.nodeId) && guids.has(line.guid))
|
|
37099
|
+
toRemove.push(line.guid);
|
|
37100
|
+
if (!dropped.has(node.nodeId) && !guids.has(line.guid))
|
|
37101
|
+
toAdd.push(line);
|
|
37102
|
+
});
|
|
37103
|
+
if (toRemove.length)
|
|
37104
|
+
dispatch(removeEditableEntryLines(toRemove));
|
|
37105
|
+
if (toAdd.length)
|
|
37106
|
+
dispatch(addEditableEntryLines(toAdd));
|
|
37107
|
+
}, [trajectEntry, editablePackagingEntry, trajectTransferAvailability]);
|
|
36807
37108
|
};
|
|
36808
37109
|
|
|
36809
37110
|
const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
@@ -37108,10 +37409,9 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37108
37409
|
const item = getTrajectNodesWithLines(context.trajectEntry).find((x) => x.line.guid === trajectEditLineGuid);
|
|
37109
37410
|
const result = packagingAccoSearchDetails.find((x) => x.code === selectedPackagingAccoResultCode);
|
|
37110
37411
|
if (item && result) {
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
applyResultToLines(currentLines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
37412
|
+
// A stop the traveller took off has no lines left on the entry: the traject's own put it back.
|
|
37413
|
+
const currentLines = item.lines.map((line) => editablePackagingEntry?.lines?.find((x) => x.guid === line.guid) ?? line);
|
|
37414
|
+
dispatch(upsertEditableEntryLines(applyResultToLines(currentLines, result)));
|
|
37115
37415
|
dispatch(updateTrajectNodeResult({ lineGuid: trajectEditLineGuid, result }));
|
|
37116
37416
|
}
|
|
37117
37417
|
dispatch(setTrajectEditLineGuid(null));
|