@qite/tide-booking-component 1.4.109 → 1.4.111
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 +3613 -2276
- package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
- package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
- package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +7 -1
- package/build/build-cjs/src/search-results/types.d.ts +3 -0
- package/build/build-cjs/src/shared/booking/booking-panel.d.ts +13 -0
- package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
- package/build/build-cjs/src/shared/booking/shared-sidebar.d.ts +34 -0
- package/build/build-cjs/src/shared/booking/step-indicators.d.ts +7 -0
- package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
- package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +7 -0
- package/build/build-esm/index.js +3572 -2247
- package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
- package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
- package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +7 -1
- package/build/build-esm/src/search-results/types.d.ts +3 -0
- package/build/build-esm/src/shared/booking/booking-panel.d.ts +13 -0
- package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
- package/build/build-esm/src/shared/booking/shared-sidebar.d.ts +34 -0
- package/build/build-esm/src/shared/booking/step-indicators.d.ts +7 -0
- package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
- package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +7 -0
- package/package.json +2 -2
- package/src/booking-wizard/components/step-indicator.tsx +10 -31
- package/src/booking-wizard/components/step-route.tsx +39 -14
- package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
- package/src/booking-wizard/features/sidebar/index.tsx +10 -4
- package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
- package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
- package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
- package/src/booking-wizard/features/summary/summary.tsx +1 -1
- package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
- package/src/search-results/components/book-packaging-entry/index.tsx +229 -0
- package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +162 -0
- package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
- package/src/search-results/components/excursions/excursion-results.tsx +1 -1
- package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
- package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
- package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
- package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
- package/src/search-results/components/itinerary/index.tsx +13 -12
- package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +280 -217
- package/src/search-results/components/spinner/spinner.tsx +12 -4
- package/src/search-results/store/search-results-slice.ts +22 -2
- package/src/search-results/types.ts +4 -0
- package/src/shared/booking/booking-panel.tsx +25 -0
- package/src/shared/booking/shared-confirmation.tsx +105 -0
- package/src/shared/booking/shared-sidebar.tsx +432 -0
- package/src/shared/booking/step-indicators.tsx +30 -0
- package/src/shared/booking/summary.tsx +380 -0
- package/src/shared/booking/travelers-form.tsx +870 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
- package/src/shared/components/flyin/flights-flyin.tsx +1 -1
- package/src/shared/components/flyin/flyin.tsx +16 -9
- package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
- package/src/shared/components/icon.tsx +13 -0
- package/src/shared/translations/ar-SA.json +7 -1
- package/src/shared/translations/da-DK.json +7 -1
- package/src/shared/translations/de-DE.json +7 -1
- package/src/shared/translations/en-GB.json +8 -2
- package/src/shared/translations/es-ES.json +7 -1
- package/src/shared/translations/fr-BE.json +7 -1
- package/src/shared/translations/fr-FR.json +7 -1
- package/src/shared/translations/is-IS.json +7 -1
- package/src/shared/translations/it-IT.json +7 -1
- package/src/shared/translations/ja-JP.json +7 -1
- package/src/shared/translations/nl-BE.json +7 -1
- package/src/shared/translations/nl-NL.json +7 -1
- package/src/shared/translations/no-NO.json +7 -1
- package/src/shared/translations/pl-PL.json +7 -1
- package/src/shared/translations/pt-PT.json +7 -1
- package/src/shared/translations/sv-SE.json +7 -1
- package/src/shared/utils/booking-summary.tsx +46 -0
- package/src/shared/utils/localization-util.ts +8 -0
- package/src/shared/utils/tide-api-utils.ts +2 -2
- package/styles/components/_dropdown.scss +5 -0
- package/styles/components/_flyin.scss +43 -0
- package/styles/components/_loader.scss +82 -0
- package/styles/components/_search.scss +14 -2
- package/styles/content-blocks-variables.scss +14 -14
- /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /package/src/{booking-wizard/components → shared/booking}/product-card.tsx +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
3
|
+
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
4
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
5
|
+
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
6
|
+
import BookingPanel from '../../../shared/booking/booking-panel';
|
|
7
|
+
import StepIndicators from '../../../shared/booking/step-indicators';
|
|
8
|
+
import WLSidebar from './wl-sidebar';
|
|
9
|
+
import { SearchSeed } from '../../types';
|
|
10
|
+
import SharedTravelersForm, {
|
|
11
|
+
applyTravelersFormValuesToEditablePackagingEntry,
|
|
12
|
+
createInitialValuesFromEditablePackagingEntry,
|
|
13
|
+
SharedTravelersSettings
|
|
14
|
+
} from '../../../shared/booking/travelers-form';
|
|
15
|
+
import { useFormik } from 'formik';
|
|
16
|
+
import { TravelersFormValues } from '../../../booking-wizard/types';
|
|
17
|
+
import { setBookingNumber, setCurrentStep, setEditablePackagingEntry } from '../../store/search-results-slice';
|
|
18
|
+
import validateForm from '../../../booking-wizard/features/travelers-form/validate-form';
|
|
19
|
+
import { bookPackagingEntry, CountryItem, getCountries, PackagingEntry, TideClientConfig } from '@qite/tide-client';
|
|
20
|
+
import SharedSummary from '../../../shared/booking/summary';
|
|
21
|
+
import { renderEditablePackagingEntrySummaryOptions } from '../../../shared/utils/booking-summary';
|
|
22
|
+
|
|
23
|
+
// TODO; fix import
|
|
24
|
+
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
25
|
+
import SharedConfirmation from '../../../shared/booking/shared-confirmation';
|
|
26
|
+
|
|
27
|
+
interface BookPackagingEntryProps {
|
|
28
|
+
activeSearchSeed: SearchSeed | null;
|
|
29
|
+
isConfirmationPage?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const travelerFormFields = [{ type: 'gender' }, { type: 'firstName' }, { type: 'lastName' }, { type: 'birthDate' }];
|
|
33
|
+
|
|
34
|
+
const mainBookerFormFields = [
|
|
35
|
+
{ type: 'street' },
|
|
36
|
+
{ type: 'houseNumber' },
|
|
37
|
+
{ type: 'box' },
|
|
38
|
+
{ type: 'zipCode' },
|
|
39
|
+
{ type: 'place' },
|
|
40
|
+
{ type: 'country' },
|
|
41
|
+
{ type: 'phone' },
|
|
42
|
+
{ type: 'email' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const travellersSettings: SharedTravelersSettings = {
|
|
46
|
+
countries: [
|
|
47
|
+
{ iso2: 'BE', name: 'Belgium', phonePrefix: '+32' },
|
|
48
|
+
{ iso2: 'NL', name: 'Netherlands', phonePrefix: '+31' },
|
|
49
|
+
{ iso2: 'FR', name: 'France', phonePrefix: '+33' }
|
|
50
|
+
],
|
|
51
|
+
formFields: travelerFormFields,
|
|
52
|
+
mainBookerFormFields
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isConfirmationPage }) => {
|
|
56
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
57
|
+
const dispatch = useDispatch();
|
|
58
|
+
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
59
|
+
|
|
60
|
+
const [countries, setCountries] = useState<CountryItem[]>([]);
|
|
61
|
+
const [userValidated, setUserValidated] = useState(true);
|
|
62
|
+
const [remarks, setRemarks] = useState('');
|
|
63
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
64
|
+
|
|
65
|
+
if (!context || !editablePackagingEntry || !priceDetails) return null;
|
|
66
|
+
|
|
67
|
+
const config: TideClientConfig = {
|
|
68
|
+
host: context.tideConnection.host,
|
|
69
|
+
apiKey: context.tideConnection.apiKey
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
73
|
+
const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
|
|
74
|
+
|
|
75
|
+
console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
|
|
76
|
+
console.log('priceDetails in WLSidebar:', priceDetails);
|
|
77
|
+
|
|
78
|
+
const initialValues = useMemo(() => createInitialValuesFromEditablePackagingEntry(editablePackagingEntry), [editablePackagingEntry?.transactionId]);
|
|
79
|
+
|
|
80
|
+
const formik = useFormik<TravelersFormValues>({
|
|
81
|
+
initialValues,
|
|
82
|
+
enableReinitialize: true,
|
|
83
|
+
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields),
|
|
84
|
+
onSubmit: (values) => {
|
|
85
|
+
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, values)));
|
|
86
|
+
dispatch(setCurrentStep(1));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!context) return;
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
|
|
94
|
+
(async () => {
|
|
95
|
+
try {
|
|
96
|
+
const result = await getCountries(config, controller.signal);
|
|
97
|
+
setCountries(result.items);
|
|
98
|
+
} catch {}
|
|
99
|
+
})();
|
|
100
|
+
|
|
101
|
+
return () => controller.abort();
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (isConfirmationPage) {
|
|
106
|
+
dispatch(setCurrentStep(2));
|
|
107
|
+
}
|
|
108
|
+
}, [isConfirmationPage, dispatch]);
|
|
109
|
+
|
|
110
|
+
const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
|
|
113
|
+
setIsSubmitting(true);
|
|
114
|
+
|
|
115
|
+
if (typeof window !== 'undefined') {
|
|
116
|
+
window.scrollTo(0, 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let updatedEditablePackagingEntry: PackagingEntry = {
|
|
120
|
+
...editablePackagingEntry,
|
|
121
|
+
remarks
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
125
|
+
const redirectUrl = new URL(window.location.href);
|
|
126
|
+
|
|
127
|
+
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
128
|
+
redirectUrl.searchParams.set('link', '');
|
|
129
|
+
|
|
130
|
+
updatedEditablePackagingEntry = {
|
|
131
|
+
...updatedEditablePackagingEntry,
|
|
132
|
+
redirectUrl: redirectUrl.toString(),
|
|
133
|
+
returnPaymentUrl: true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const request = {
|
|
141
|
+
language: context.languageCode ?? 'en-GB',
|
|
142
|
+
officeId: context.tideConnection.officeId,
|
|
143
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
144
|
+
agentId: context.agentId,
|
|
145
|
+
payload: updatedEditablePackagingEntry
|
|
146
|
+
} as PackagingRequestBase<PackagingEntry>;
|
|
147
|
+
const bookingResponse = await bookPackagingEntry(config, request);
|
|
148
|
+
|
|
149
|
+
dispatch(setBookingNumber(bookingResponse.number));
|
|
150
|
+
|
|
151
|
+
if (bookingResponse.paymentUrl) {
|
|
152
|
+
window.location.href = bookingResponse.paymentUrl;
|
|
153
|
+
} else {
|
|
154
|
+
dispatch(setCurrentStep(2));
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
dispatch(setCurrentStep(3));
|
|
158
|
+
} finally {
|
|
159
|
+
setIsSubmitting(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="booking">
|
|
165
|
+
<div className="booking__content">
|
|
166
|
+
<BookingPanel
|
|
167
|
+
currentStep={currentStep}
|
|
168
|
+
stepLabels={stepLabels}
|
|
169
|
+
StepIndicatorsComponent={StepIndicators}
|
|
170
|
+
renderTitle={(step) => (
|
|
171
|
+
<>
|
|
172
|
+
{step + 1}. {stepLabels[step]}
|
|
173
|
+
</>
|
|
174
|
+
)}>
|
|
175
|
+
{currentStep === 0 && (
|
|
176
|
+
<SharedTravelersForm
|
|
177
|
+
formik={formik}
|
|
178
|
+
translations={translations}
|
|
179
|
+
travellersSettings={travellersSettings}
|
|
180
|
+
countries={countries}
|
|
181
|
+
travelersFirstStep={false}
|
|
182
|
+
isUnavailable={false}
|
|
183
|
+
useCompactForm={false}
|
|
184
|
+
showAgentSelection={false}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{currentStep === 1 && (
|
|
189
|
+
<SharedSummary
|
|
190
|
+
translations={translations}
|
|
191
|
+
travelerFormValues={formik.values}
|
|
192
|
+
isSubmitting={isSubmitting}
|
|
193
|
+
userValidated={userValidated}
|
|
194
|
+
remarks={remarks}
|
|
195
|
+
enableVoucher={false}
|
|
196
|
+
allowOption={false}
|
|
197
|
+
isOffer={false}
|
|
198
|
+
onUserValidatedChange={setUserValidated}
|
|
199
|
+
onRemarksChange={setRemarks}
|
|
200
|
+
onSubmit={handleSummarySubmit}
|
|
201
|
+
renderOptions={() => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations)}
|
|
202
|
+
renderPreviousButton={() => (
|
|
203
|
+
<button type="button" title={translations.STEPS.PREVIOUS} onClick={() => dispatch(setCurrentStep(0))} className="cta cta--secondary">
|
|
204
|
+
{translations.STEPS.PREVIOUS}
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
{currentStep === 2 && (
|
|
210
|
+
<SharedConfirmation
|
|
211
|
+
bookingNumber={bookingNumber ?? editablePackagingEntry?.dossierNumber ?? ''}
|
|
212
|
+
isOption={false}
|
|
213
|
+
isOffer={false}
|
|
214
|
+
translations={translations.CONFIRMATION}
|
|
215
|
+
// companyContactPhone={context?.companyContactPhone || ''}
|
|
216
|
+
// companyContactEmail={context?.companyContactEmail || ''}
|
|
217
|
+
// homeUrl={context?.homeUrl || '/'}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
{currentStep === 3 && <div>{/* error */}</div>}
|
|
221
|
+
</BookingPanel>
|
|
222
|
+
<div className="backdrop" id="backdrop"></div>
|
|
223
|
+
<WLSidebar activeSearchSeed={activeSearchSeed} />
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export default BookPackagingEntry;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { useContext, useMemo } from 'react';
|
|
2
|
+
import { formatDate, getDateOnlyTime, getTranslations } from '../../../shared/utils/localization-util';
|
|
3
|
+
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
4
|
+
import { useSelector } from 'react-redux';
|
|
5
|
+
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
6
|
+
|
|
7
|
+
import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
|
|
8
|
+
import { first, last, sum } from 'lodash';
|
|
9
|
+
import { SearchSeed } from '../../types';
|
|
10
|
+
import { getTravelersText } from '../../../booking-wizard/features/sidebar/sidebar-util';
|
|
11
|
+
import { RoomTraveler } from '../../../booking-wizard/types';
|
|
12
|
+
|
|
13
|
+
import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingEntryLine } from '@qite/tide-client';
|
|
14
|
+
import Spinner from '../spinner/spinner';
|
|
15
|
+
import SharedSidebar from '../../../shared/booking/shared-sidebar';
|
|
16
|
+
|
|
17
|
+
interface WLSidebarProps {
|
|
18
|
+
activeSearchSeed: SearchSeed | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const mapToSidebarFlightMetaData = (entryLine: PackagingEntryLine) => {
|
|
22
|
+
return {
|
|
23
|
+
flightLines: entryLine.flightInformation?.flightLines.map((f) => ({
|
|
24
|
+
number: f.flightNumber,
|
|
25
|
+
departureDate: f.departureDate,
|
|
26
|
+
departureAirport: f.departureAirportCode,
|
|
27
|
+
departureAirportDescription: f.departureAirportDescription,
|
|
28
|
+
departureTime: f.departureTime,
|
|
29
|
+
arrivalDate: f.arrivalDate,
|
|
30
|
+
arrivalAirport: f.arrivalAirportCode,
|
|
31
|
+
arrivalAirportDescription: f.arrivalAirportDescription,
|
|
32
|
+
arrivalTime: f.arrivalTime,
|
|
33
|
+
flightClass: '',
|
|
34
|
+
travelClass: '',
|
|
35
|
+
airline: f.airlineDescription,
|
|
36
|
+
airlineCode: f.airlineCode,
|
|
37
|
+
operatingAirlineCode: f.operatingAirlineCode,
|
|
38
|
+
operatingAirlineDescription: f.operatingAirlineDescription,
|
|
39
|
+
durationInTicks: f.durationInTicks
|
|
40
|
+
})),
|
|
41
|
+
luggageIncluded: false, // Not present in editablePackagingEntry, set default
|
|
42
|
+
bagageAllowed: false, // Not present in editablePackagingEntry, set default
|
|
43
|
+
bagage: '', // Not present in editablePackagingEntry, set default
|
|
44
|
+
mealIncluded: false, // Not present in editablePackagingEntry, set default
|
|
45
|
+
meal: '', // Not present in editablePackagingEntry, set default
|
|
46
|
+
durationInTicks: entryLine.flightInformation?.flightLines.reduce((s, { durationInTicks }) => s + (durationInTicks ?? 0), 0)
|
|
47
|
+
} as BookingPackageFlightMetaData;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const selectSeparatePackagePriceDetails = (priceDetails: BookingPriceDetail[]) => {
|
|
51
|
+
const result: BookingPriceDetail[] = [];
|
|
52
|
+
const filteredPriceDetails = priceDetails.filter((priceDetail) => priceDetail.isSeparate);
|
|
53
|
+
|
|
54
|
+
filteredPriceDetails.forEach((priceDetail) => {
|
|
55
|
+
const priceDetailToMerge = result.find(
|
|
56
|
+
(x) => x.productCode === priceDetail.productCode && x.accommodationCode === priceDetail.accommodationCode && x.productType === priceDetail.productType
|
|
57
|
+
);
|
|
58
|
+
if (priceDetailToMerge) {
|
|
59
|
+
priceDetailToMerge.total += priceDetail.total;
|
|
60
|
+
priceDetailToMerge.price += priceDetail.price;
|
|
61
|
+
} else {
|
|
62
|
+
result.push(Object.assign({}, priceDetail));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
|
|
70
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
71
|
+
if (!context) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
75
|
+
const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
76
|
+
|
|
77
|
+
// Map editablePackagingEntry to sidebar props (example, adjust as needed)
|
|
78
|
+
if (!editablePackagingEntry) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sortedLines = useMemo(() => {
|
|
83
|
+
return [...(editablePackagingEntry?.lines ?? [])].sort((a, b) => {
|
|
84
|
+
const dateA = getDateOnlyTime(a.from);
|
|
85
|
+
const dateB = getDateOnlyTime(b.from);
|
|
86
|
+
|
|
87
|
+
if (dateA !== dateB) {
|
|
88
|
+
return dateA - dateB;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (a.order ?? Infinity) - (b.order ?? Infinity);
|
|
92
|
+
});
|
|
93
|
+
}, [editablePackagingEntry]);
|
|
94
|
+
|
|
95
|
+
const firstEntryLine = first(sortedLines);
|
|
96
|
+
const accommodationLines = editablePackagingEntry.lines.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE);
|
|
97
|
+
const accommodationLine = first(accommodationLines) ?? firstEntryLine;
|
|
98
|
+
|
|
99
|
+
const location =
|
|
100
|
+
accommodationLine?.location?.name ??
|
|
101
|
+
accommodationLine?.oord?.name ??
|
|
102
|
+
accommodationLine?.region?.name ??
|
|
103
|
+
accommodationLine?.country?.name ??
|
|
104
|
+
firstEntryLine?.location?.name;
|
|
105
|
+
|
|
106
|
+
const rooms =
|
|
107
|
+
activeSearchSeed?.rooms.map((room) => {
|
|
108
|
+
const adults = room.pax
|
|
109
|
+
.filter((p) => p.age && p.age >= 18)
|
|
110
|
+
.map((p) => {
|
|
111
|
+
return { id: p.id, age: p.age } as RoomTraveler;
|
|
112
|
+
});
|
|
113
|
+
const children = room.pax
|
|
114
|
+
.filter((p) => p.age && p.age < 18)
|
|
115
|
+
.map((p) => {
|
|
116
|
+
return { id: p.id, age: p.age } as RoomTraveler;
|
|
117
|
+
});
|
|
118
|
+
return { adults, children };
|
|
119
|
+
}) || [];
|
|
120
|
+
|
|
121
|
+
const travelerRooms = getTravelersText(rooms, translations);
|
|
122
|
+
|
|
123
|
+
const flightSegments = sortedLines.filter(
|
|
124
|
+
(line) => line.serviceType === FLIGHT_SERVICE_TYPE && line.flightInformation && Array.isArray(line.flightInformation.flightLines)
|
|
125
|
+
);
|
|
126
|
+
const outboundFlight = first(flightSegments);
|
|
127
|
+
const returnFlight = flightSegments.length > 1 ? last(flightSegments) : undefined;
|
|
128
|
+
const outboundFlightMetaData = outboundFlight ? mapToSidebarFlightMetaData(outboundFlight) : undefined;
|
|
129
|
+
const returnFlightMetaData = returnFlight ? mapToSidebarFlightMetaData(returnFlight) : undefined;
|
|
130
|
+
|
|
131
|
+
const basePrice = sum(priceDetails?.details.filter((pd) => pd.isInPackage).map((pd) => pd.price * pd.amount) ?? []);
|
|
132
|
+
const separateExtraPriceDetails = priceDetails?.details.filter((pd) => !pd.isInPackage && pd.isSeparate) ?? [];
|
|
133
|
+
const totalPrice = sum([basePrice, ...separateExtraPriceDetails.map((priceDetail) => priceDetail.price * priceDetail.amount)]);
|
|
134
|
+
|
|
135
|
+
const includedCosts = selectSeparatePackagePriceDetails(priceDetails?.details.filter((pd) => pd.isInPackage) ?? []);
|
|
136
|
+
return (
|
|
137
|
+
<SharedSidebar
|
|
138
|
+
productName={location ?? ''}
|
|
139
|
+
thumbnailUrl={context.destinationImage?.url}
|
|
140
|
+
translations={translations}
|
|
141
|
+
travelerRooms={travelerRooms}
|
|
142
|
+
startDateText={first(sortedLines)?.from && formatDate(new Date(first(sortedLines)!.from))}
|
|
143
|
+
endDateText={last(sortedLines)?.to && formatDate(new Date(last(sortedLines)!.to))}
|
|
144
|
+
isLoading={!editablePackagingEntry || !priceDetails}
|
|
145
|
+
loaderComponent={<Spinner />}
|
|
146
|
+
departureFlightMetaData={outboundFlightMetaData}
|
|
147
|
+
returnFlightMetaData={returnFlightMetaData}
|
|
148
|
+
includedServiceTypes={editablePackagingEntry.lines.map((line) => line.serviceType)}
|
|
149
|
+
packagingAccommodations={accommodationLines}
|
|
150
|
+
basePrice={basePrice}
|
|
151
|
+
commission={priceDetails?.commission}
|
|
152
|
+
totalPrice={totalPrice}
|
|
153
|
+
includedCosts={includedCosts}
|
|
154
|
+
extraCosts={separateExtraPriceDetails}
|
|
155
|
+
deposit={priceDetails?.deposit}
|
|
156
|
+
isUnavailable={false}
|
|
157
|
+
agent={context.agentId}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export default WLSidebar;
|
|
@@ -93,10 +93,14 @@ const DayByDayExcursions: React.FC<DayByDayExcursionsProps> = () => {
|
|
|
93
93
|
<React.Fragment key={dayKey}>
|
|
94
94
|
<div className="search__results__label search__results__label--secondary">
|
|
95
95
|
<div className="search__results__label__date">
|
|
96
|
-
<
|
|
96
|
+
<p className="search__results__label__date-date">{format(day, 'd', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
|
|
97
|
+
<p>{format(day, 'MMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
|
|
97
98
|
</div>
|
|
98
99
|
<div className="search__results__label__text">
|
|
99
|
-
<
|
|
100
|
+
<Icon name="ui-excursion" height={16} />
|
|
101
|
+
<h3>
|
|
102
|
+
{translations.SRP.SELECT} <strong>{translations.SRP.EXCURSION}</strong>
|
|
103
|
+
</h3>
|
|
100
104
|
</div>
|
|
101
105
|
</div>
|
|
102
106
|
|
|
@@ -122,7 +122,7 @@ const ExcursionResults: React.FC<ExcursionResultsProps> = ({ isFlyIn, activeSear
|
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
return isLoading ? (
|
|
125
|
-
<Spinner />
|
|
125
|
+
<Spinner label={translations.SRP.LOADING_EXCURSIONS} />
|
|
126
126
|
) : (
|
|
127
127
|
<div className="flyin__content flyin__content--columns">
|
|
128
128
|
{/* <Filters
|
package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx
CHANGED
|
@@ -3,12 +3,12 @@ import Icon from '../../../../shared/components/icon';
|
|
|
3
3
|
import { ExtendedFlightSearchResponseItem } from '../../../types';
|
|
4
4
|
import SearchResultsConfigurationContext from '../../../search-results-configuration-context';
|
|
5
5
|
import { getTranslations } from '../../../../shared/utils/localization-util';
|
|
6
|
-
import { FlightSearchResponseFlightSegment } from '@qite/tide-client';
|
|
7
6
|
import IndependentFlightOption from './independent-flight-option';
|
|
8
7
|
import { useDispatch } from 'react-redux';
|
|
9
8
|
import { setFlyInIsOpen, setSelectedFlight } from '../../../store/search-results-slice';
|
|
10
9
|
import { useFlightSearch } from '../flight-search-context';
|
|
11
10
|
import { getFlightKey } from '../../../utils/flight-utils';
|
|
11
|
+
import { format } from 'date-fns';
|
|
12
12
|
|
|
13
13
|
interface IndependentFlightSelectionProps {
|
|
14
14
|
searchResults: ExtendedFlightSearchResponseItem[];
|
|
@@ -93,13 +93,20 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
|
|
|
93
93
|
return searchResults.find((flight) => getFlightKey(flight.return.segments) === selectedReturnKey) || null;
|
|
94
94
|
}, [searchResults, selectedReturnKey]);
|
|
95
95
|
|
|
96
|
+
const firstResultDate = uniqueOutwardFlights.length > 0 ? uniqueOutwardFlights[0].outward.segments[0].departureDateTime : null;
|
|
97
|
+
|
|
98
|
+
const firstResultDay = firstResultDate ? format(firstResultDate, 'd') : null;
|
|
99
|
+
const firstResultMonth = firstResultDate ? format(firstResultDate, 'MMM') : null;
|
|
100
|
+
|
|
96
101
|
return (
|
|
97
102
|
<>
|
|
98
103
|
<div className="search__results__label search__results__label--secondary">
|
|
99
104
|
<div className="search__results__label__date">
|
|
100
|
-
<
|
|
105
|
+
<p className="search__results__label__date-date">{firstResultDay}</p>
|
|
106
|
+
<p>{firstResultMonth}</p>
|
|
101
107
|
</div>
|
|
102
108
|
<div className="search__results__label__text">
|
|
109
|
+
<Icon name="ui-flight" height={16} fill="white" />
|
|
103
110
|
<h3>
|
|
104
111
|
{translations.SRP.SELECT} <strong> {translations.SRP.DEPARTURE}</strong>
|
|
105
112
|
</h3>
|
|
@@ -131,9 +138,11 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
|
|
|
131
138
|
|
|
132
139
|
<div className="search__results__label search__results__label--secondary">
|
|
133
140
|
<div className="search__results__label__date">
|
|
134
|
-
<
|
|
141
|
+
{/* <p className="search__results__label__date-date">{format(day, 'd', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
|
|
142
|
+
<p>{format(day, 'MMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p> */}
|
|
135
143
|
</div>
|
|
136
144
|
<div className="search__results__label__text">
|
|
145
|
+
<Icon name="ui-flight" height={16} fill="white" />
|
|
137
146
|
<h3>
|
|
138
147
|
{translations.SRP.SELECT} <strong> {translations.SRP.RETURN}</strong>
|
|
139
148
|
</h3>
|
|
@@ -19,7 +19,7 @@ const GroupTourCard: React.FC<GroupTourCardProps> = ({ result, languageCode, cms
|
|
|
19
19
|
const context = useContext(SearchResultsConfigurationContext);
|
|
20
20
|
const { selectedSearchResult } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
21
21
|
if (!context) {
|
|
22
|
-
return;
|
|
22
|
+
return null;
|
|
23
23
|
}
|
|
24
24
|
const dispatch = useDispatch();
|
|
25
25
|
const translations = getTranslations(languageCode ?? 'en-GB');
|
|
@@ -196,16 +196,19 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
196
196
|
{!isFlyIn && (
|
|
197
197
|
<div className="search__results__label search__results__label--secondary">
|
|
198
198
|
<div className="search__results__label__date">
|
|
199
|
-
{firstResultDay && firstResultMonth ? (
|
|
199
|
+
{/* /* {firstResultDay && firstResultMonth ? (
|
|
200
200
|
<>
|
|
201
201
|
<p className="search__results__label__date-date">{firstResultDay}</p>
|
|
202
202
|
<p>{firstResultMonth}</p>
|
|
203
203
|
</>
|
|
204
204
|
) : (
|
|
205
205
|
<Icon name="ui-bed" height={16} fill="white" />
|
|
206
|
-
)}
|
|
206
|
+
)} */}
|
|
207
|
+
<p className="search__results__label__date-date">{firstResultDay}</p>
|
|
208
|
+
<p>{firstResultMonth}</p>
|
|
207
209
|
</div>
|
|
208
210
|
<div className="search__results__label__text">
|
|
211
|
+
<Icon name="ui-bed" height={16} />
|
|
209
212
|
<h3>
|
|
210
213
|
{translations.SRP.SELECT} <strong>{translations.SRP.ACCOMMODATION}</strong>
|
|
211
214
|
</h3>
|
|
@@ -213,7 +216,7 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
213
216
|
</div>
|
|
214
217
|
)}
|
|
215
218
|
{isLoading ? (
|
|
216
|
-
<>{context.customSpinner ?? <Spinner />}</>
|
|
219
|
+
<>{context.customSpinner ?? <Spinner label={translations.SRP.LOADING_ACCOMMODATIONS} />}</>
|
|
217
220
|
) : (
|
|
218
221
|
renderHotelResults(visibleResults, context, activeTab, translations, selectedPackagingAccoResult, isFlyIn)
|
|
219
222
|
)}
|
|
@@ -365,7 +365,7 @@ const FullItinerary: React.FC<FullItineraryProps> = ({ isLoading }) => {
|
|
|
365
365
|
|
|
366
366
|
return (
|
|
367
367
|
<>
|
|
368
|
-
{isLoading && <Spinner />}
|
|
368
|
+
{isLoading && <Spinner label={translations.SRP.LOADING_ITINERARY} />}
|
|
369
369
|
<div ref={hostRef} style={{ display: isLoading ? 'none' : 'block' }} />
|
|
370
370
|
</>
|
|
371
371
|
);
|
|
@@ -4,11 +4,12 @@ import { first, groupBy, isEmpty, last } from 'lodash';
|
|
|
4
4
|
import { PackagingEntryLine, PackagingEntryLineFlightLine } from '@qite/tide-client';
|
|
5
5
|
import Icon from '../../../shared/components/icon';
|
|
6
6
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
7
|
-
import { formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
8
|
-
import { useSelector } from 'react-redux';
|
|
7
|
+
import { formatPrice, getDateOnlyTime, getTranslations } from '../../../shared/utils/localization-util';
|
|
8
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
9
9
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
10
10
|
import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
|
|
11
11
|
import Spinner from '../spinner/spinner';
|
|
12
|
+
import { setBookPackagingEntry } from '../../store/search-results-slice';
|
|
12
13
|
|
|
13
14
|
interface ItineraryProps {
|
|
14
15
|
isOpen: boolean;
|
|
@@ -113,19 +114,13 @@ const getServiceTypePriority = (serviceType?: number) => {
|
|
|
113
114
|
return SERVICE_TYPE_PRIORITY[serviceType ?? -1] ?? 2;
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
const getDateOnlyTime = (date?: string | Date | null) => {
|
|
117
|
-
if (!date) return 0;
|
|
118
|
-
|
|
119
|
-
const parsedDate = new Date(date);
|
|
120
|
-
|
|
121
|
-
return new Date(parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate()).getTime();
|
|
122
|
-
};
|
|
123
|
-
|
|
124
117
|
const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
125
118
|
const context = useContext(SearchResultsConfigurationContext);
|
|
126
119
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
127
120
|
const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
128
121
|
|
|
122
|
+
const dispatch = useDispatch();
|
|
123
|
+
|
|
129
124
|
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
130
125
|
|
|
131
126
|
const sortedLines = useMemo(() => {
|
|
@@ -177,6 +172,10 @@ const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoadin
|
|
|
177
172
|
const totalPrice = priceDetails?.total || packagingEntry.price || 0;
|
|
178
173
|
const pricePerPerson = totalPrice / numberOfPax;
|
|
179
174
|
|
|
175
|
+
const handleConfirm = () => {
|
|
176
|
+
dispatch(setBookPackagingEntry(true));
|
|
177
|
+
};
|
|
178
|
+
|
|
180
179
|
return (
|
|
181
180
|
<div className={`search__filters--modal ${isOpen ? 'is-open' : ''}`}>
|
|
182
181
|
<div className="search__filters--background" onClick={handleSetIsOpen}></div>
|
|
@@ -211,7 +210,7 @@ const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoadin
|
|
|
211
210
|
|
|
212
211
|
<div className="search__filter__prices">
|
|
213
212
|
{isLoading ? (
|
|
214
|
-
<Spinner />
|
|
213
|
+
<Spinner label={translations.PRODUCT.LOADING_PRICE} />
|
|
215
214
|
) : (
|
|
216
215
|
<>
|
|
217
216
|
<div className="search__filter__prices__wrapper">
|
|
@@ -223,7 +222,9 @@ const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoadin
|
|
|
223
222
|
</strong>
|
|
224
223
|
</p>
|
|
225
224
|
</div>
|
|
226
|
-
<button className="cta"
|
|
225
|
+
<button className="cta" onClick={handleConfirm}>
|
|
226
|
+
{translations.QSM.CONFIRM}
|
|
227
|
+
</button>
|
|
227
228
|
</>
|
|
228
229
|
)}
|
|
229
230
|
</div>
|
|
@@ -123,7 +123,7 @@ const FlightResultsContainer: React.FC<FlightResultsContainerProps> = ({ isMobil
|
|
|
123
123
|
</div>
|
|
124
124
|
|
|
125
125
|
<div className="search__results__wrapper">
|
|
126
|
-
{flightsLoading && <Spinner />}
|
|
126
|
+
{flightsLoading && <Spinner label={translations.SRP.LOADING_FLIGHTS} />}
|
|
127
127
|
{context?.searchConfiguration.qsmType == PortalQsmType.Flight && context?.showFlightAccommodationResults && results && results.length > 0 && (
|
|
128
128
|
<FlightSelection searchResults={results} flightSelectionType={flightSelectionType} />
|
|
129
129
|
)}
|