@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.
Files changed (106) hide show
  1. package/build/build-cjs/index.js +3613 -2276
  2. package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
  3. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  4. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  5. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  6. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  7. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
  8. package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  9. package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
  10. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +7 -1
  11. package/build/build-cjs/src/search-results/types.d.ts +3 -0
  12. package/build/build-cjs/src/shared/booking/booking-panel.d.ts +13 -0
  13. package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
  14. package/build/build-cjs/src/shared/booking/shared-sidebar.d.ts +34 -0
  15. package/build/build-cjs/src/shared/booking/step-indicators.d.ts +7 -0
  16. package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
  17. package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
  18. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
  19. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  20. package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
  21. package/build/build-cjs/src/shared/utils/localization-util.d.ts +7 -0
  22. package/build/build-esm/index.js +3572 -2247
  23. package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
  24. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  25. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  26. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  27. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  28. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
  29. package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  30. package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
  31. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +7 -1
  32. package/build/build-esm/src/search-results/types.d.ts +3 -0
  33. package/build/build-esm/src/shared/booking/booking-panel.d.ts +13 -0
  34. package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
  35. package/build/build-esm/src/shared/booking/shared-sidebar.d.ts +34 -0
  36. package/build/build-esm/src/shared/booking/step-indicators.d.ts +7 -0
  37. package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
  38. package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
  39. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
  40. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  41. package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
  42. package/build/build-esm/src/shared/utils/localization-util.d.ts +7 -0
  43. package/package.json +2 -2
  44. package/src/booking-wizard/components/step-indicator.tsx +10 -31
  45. package/src/booking-wizard/components/step-route.tsx +39 -14
  46. package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
  47. package/src/booking-wizard/features/sidebar/index.tsx +10 -4
  48. package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
  49. package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
  50. package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
  51. package/src/booking-wizard/features/summary/summary.tsx +1 -1
  52. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
  53. package/src/search-results/components/book-packaging-entry/index.tsx +229 -0
  54. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +162 -0
  55. package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
  56. package/src/search-results/components/excursions/excursion-results.tsx +1 -1
  57. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
  58. package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
  59. package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
  60. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
  61. package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
  62. package/src/search-results/components/itinerary/index.tsx +13 -12
  63. package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
  64. package/src/search-results/components/search-results-container/search-results-container.tsx +280 -217
  65. package/src/search-results/components/spinner/spinner.tsx +12 -4
  66. package/src/search-results/store/search-results-slice.ts +22 -2
  67. package/src/search-results/types.ts +4 -0
  68. package/src/shared/booking/booking-panel.tsx +25 -0
  69. package/src/shared/booking/shared-confirmation.tsx +105 -0
  70. package/src/shared/booking/shared-sidebar.tsx +432 -0
  71. package/src/shared/booking/step-indicators.tsx +30 -0
  72. package/src/shared/booking/summary.tsx +380 -0
  73. package/src/shared/booking/travelers-form.tsx +870 -0
  74. package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
  75. package/src/shared/components/flyin/flights-flyin.tsx +1 -1
  76. package/src/shared/components/flyin/flyin.tsx +16 -9
  77. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
  78. package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
  79. package/src/shared/components/icon.tsx +13 -0
  80. package/src/shared/translations/ar-SA.json +7 -1
  81. package/src/shared/translations/da-DK.json +7 -1
  82. package/src/shared/translations/de-DE.json +7 -1
  83. package/src/shared/translations/en-GB.json +8 -2
  84. package/src/shared/translations/es-ES.json +7 -1
  85. package/src/shared/translations/fr-BE.json +7 -1
  86. package/src/shared/translations/fr-FR.json +7 -1
  87. package/src/shared/translations/is-IS.json +7 -1
  88. package/src/shared/translations/it-IT.json +7 -1
  89. package/src/shared/translations/ja-JP.json +7 -1
  90. package/src/shared/translations/nl-BE.json +7 -1
  91. package/src/shared/translations/nl-NL.json +7 -1
  92. package/src/shared/translations/no-NO.json +7 -1
  93. package/src/shared/translations/pl-PL.json +7 -1
  94. package/src/shared/translations/pt-PT.json +7 -1
  95. package/src/shared/translations/sv-SE.json +7 -1
  96. package/src/shared/utils/booking-summary.tsx +46 -0
  97. package/src/shared/utils/localization-util.ts +8 -0
  98. package/src/shared/utils/tide-api-utils.ts +2 -2
  99. package/styles/components/_dropdown.scss +5 -0
  100. package/styles/components/_flyin.scss +43 -0
  101. package/styles/components/_loader.scss +82 -0
  102. package/styles/components/_search.scss +14 -2
  103. package/styles/content-blocks-variables.scss +14 -14
  104. /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  105. /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  106. /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}.&nbsp;{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
- <Icon name="ui-excursion" height={16} fill="white" />
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
- <h3>{format(day, 'EEEE d MMMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</h3>
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
@@ -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
- <Icon name="ui-flight" height={16} fill="white" />
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
- <Icon name="ui-flight" height={16} fill="white" />
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');
@@ -15,7 +15,7 @@ interface GroupTourResultsProps {
15
15
  const GroupTourResults: React.FC<GroupTourResultsProps> = ({ isLoading }) => {
16
16
  const context = useContext(SearchResultsConfigurationContext);
17
17
  if (!context) {
18
- return;
18
+ return null;
19
19
  }
20
20
 
21
21
  if (isLoading) {
@@ -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">{translations.QSM.CONFIRM}</button>
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
  )}