@qite/tide-booking-component 1.4.112 → 1.4.114
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 +425 -263
- package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
- package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +2 -0
- package/build/build-cjs/src/search-results/components/search-results-container/search-results-container.d.ts +4 -1
- package/build/build-cjs/src/search-results/index.d.ts +1 -0
- package/build/build-cjs/src/shared/booking/summary.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-esm/index.js +424 -263
- package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
- package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +2 -0
- package/build/build-esm/src/search-results/components/search-results-container/search-results-container.d.ts +4 -1
- package/build/build-esm/src/search-results/index.d.ts +1 -0
- package/build/build-esm/src/shared/booking/summary.d.ts +1 -0
- package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
- package/package.json +2 -2
- package/src/search-results/components/book-packaging-entry/index.tsx +60 -22
- package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +27 -16
- package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +5 -2
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +11 -2
- package/src/search-results/components/search-results-container/search-results-container.tsx +22 -8
- package/src/search-results/index.tsx +3 -2
- package/src/shared/booking/summary.tsx +4 -2
- package/src/shared/booking/travelers-form.tsx +12 -6
- package/src/shared/components/flyin/flyin.tsx +10 -1
- package/src/shared/translations/ar-SA.json +3 -1
- package/src/shared/translations/da-DK.json +3 -1
- package/src/shared/translations/de-DE.json +3 -1
- package/src/shared/translations/en-GB.json +3 -1
- package/src/shared/translations/es-ES.json +3 -1
- package/src/shared/translations/fr-BE.json +3 -1
- package/src/shared/translations/fr-FR.json +3 -1
- package/src/shared/translations/is-IS.json +3 -1
- package/src/shared/translations/it-IT.json +3 -1
- package/src/shared/translations/ja-JP.json +3 -1
- package/src/shared/translations/nl-BE.json +3 -1
- package/src/shared/translations/nl-NL.json +3 -1
- package/src/shared/translations/no-NO.json +3 -1
- package/src/shared/translations/pl-PL.json +3 -1
- package/src/shared/translations/pt-PT.json +3 -1
- package/src/shared/translations/sv-SE.json +3 -1
- package/src/shared/utils/booking-summary.tsx +11 -0
- package/styles/components/_booking.scss +10 -0
- package/styles/components/_select-wrapper.scss +5 -0
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { SearchSeed } from '../../types';
|
|
3
3
|
interface BookPackagingEntryProps {
|
|
4
4
|
activeSearchSeed: SearchSeed | null;
|
|
5
|
+
isLoading: boolean;
|
|
5
6
|
isConfirmationPage?: boolean;
|
|
6
7
|
}
|
|
7
8
|
declare const BookPackagingEntry: React.FC<BookPackagingEntryProps>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SearchSeed } from '../../types';
|
|
3
|
+
import { PackagingAccommodationResponse } from '@qite/tide-client';
|
|
3
4
|
interface WLSidebarProps {
|
|
4
5
|
activeSearchSeed: SearchSeed | null;
|
|
6
|
+
packagingAccoResult: PackagingAccommodationResponse | null;
|
|
5
7
|
}
|
|
6
8
|
declare const WLSidebar: React.FC<WLSidebarProps>;
|
|
7
9
|
export default WLSidebar;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
2
|
+
interface SearchResultsContainerProps {
|
|
3
|
+
onBookingStarted?: () => void;
|
|
4
|
+
}
|
|
5
|
+
declare const SearchResultsContainer: React.FC<SearchResultsContainerProps>;
|
|
3
6
|
export default SearchResultsContainer;
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { SearchResultsConfiguration } from './types';
|
|
3
3
|
interface SearchResultsProps {
|
|
4
4
|
configuration: SearchResultsConfiguration;
|
|
5
|
+
onBookingStarted?: () => void;
|
|
5
6
|
}
|
|
6
7
|
declare const SearchResults: React.FC<SearchResultsProps>;
|
|
7
8
|
export default SearchResults;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qite/tide-booking-component",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.114",
|
|
4
4
|
"description": "React Booking wizard & Booking product component for Tide",
|
|
5
5
|
"main": "build/build-cjs/index.js",
|
|
6
6
|
"types": "build/build-cjs/src/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@jsonurl/jsonurl": "^1.1.4",
|
|
31
31
|
"@popperjs/core": "^2.10.2",
|
|
32
|
-
"@qite/tide-client": "^1.1.
|
|
32
|
+
"@qite/tide-client": "^1.1.173",
|
|
33
33
|
"@reduxjs/toolkit": "^2.8.2",
|
|
34
34
|
"@rollup/plugin-commonjs": "^19.0.1",
|
|
35
35
|
"@rollup/plugin-json": "^4.1.0",
|
|
@@ -16,13 +16,23 @@ import { useFormik } from 'formik';
|
|
|
16
16
|
import { TravelersFormValues } from '../../../booking-wizard/types';
|
|
17
17
|
import { setBookingNumber, setCurrentStep, setEditablePackagingEntry } from '../../store/search-results-slice';
|
|
18
18
|
import validateForm from '../../../booking-wizard/features/travelers-form/validate-form';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
bookPackagingEntry,
|
|
21
|
+
CountryItem,
|
|
22
|
+
getCountries,
|
|
23
|
+
PackagingAccommodationResponse,
|
|
24
|
+
PackagingEntry,
|
|
25
|
+
PackagingRequestBase,
|
|
26
|
+
TideClientConfig
|
|
27
|
+
} from '@qite/tide-client';
|
|
20
28
|
import SharedSummary from '../../../shared/booking/summary';
|
|
21
29
|
import { renderEditablePackagingEntrySummaryOptions } from '../../../shared/utils/booking-summary';
|
|
22
30
|
import SharedConfirmation from '../../../shared/booking/shared-confirmation';
|
|
31
|
+
import Spinner from '../spinner/spinner';
|
|
23
32
|
|
|
24
33
|
interface BookPackagingEntryProps {
|
|
25
34
|
activeSearchSeed: SearchSeed | null;
|
|
35
|
+
isLoading: boolean;
|
|
26
36
|
isConfirmationPage?: boolean;
|
|
27
37
|
}
|
|
28
38
|
|
|
@@ -49,54 +59,70 @@ const travellersSettings: SharedTravelersSettings = {
|
|
|
49
59
|
mainBookerFormFields
|
|
50
60
|
};
|
|
51
61
|
|
|
52
|
-
const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isConfirmationPage }) => {
|
|
62
|
+
const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isLoading, isConfirmationPage }) => {
|
|
53
63
|
const context = useContext(SearchResultsConfigurationContext);
|
|
54
64
|
const dispatch = useDispatch();
|
|
55
|
-
|
|
65
|
+
|
|
66
|
+
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = useSelector(
|
|
67
|
+
(state: SearchResultsRootState) => state.searchResults
|
|
68
|
+
);
|
|
56
69
|
|
|
57
70
|
const [countries, setCountries] = useState<CountryItem[]>([]);
|
|
58
71
|
const [userValidated, setUserValidated] = useState(true);
|
|
59
72
|
const [remarks, setRemarks] = useState('');
|
|
60
73
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
74
|
+
const [selectedPackagingAccoResult, setSelectedPackagingAccoResult] = useState<PackagingAccommodationResponse | null>(null);
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const config: TideClientConfig = {
|
|
65
|
-
host: context.tideConnection.host,
|
|
66
|
-
apiKey: context.tideConnection.apiKey
|
|
67
|
-
};
|
|
76
|
+
const translations = useMemo(() => getTranslations(context?.languageCode ?? 'en-GB'), [context?.languageCode]);
|
|
68
77
|
|
|
69
|
-
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
70
78
|
const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
const config: TideClientConfig | null = useMemo(() => {
|
|
81
|
+
if (!context) return null;
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
return {
|
|
84
|
+
host: context.tideConnection.host,
|
|
85
|
+
apiKey: context.tideConnection.apiKey
|
|
86
|
+
};
|
|
87
|
+
}, [context]);
|
|
88
|
+
|
|
89
|
+
const initialValues = useMemo(() => {
|
|
90
|
+
if (!editablePackagingEntry) {
|
|
91
|
+
return {} as TravelersFormValues;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return createInitialValuesFromEditablePackagingEntry(editablePackagingEntry);
|
|
95
|
+
}, [editablePackagingEntry?.transactionId]);
|
|
76
96
|
|
|
77
97
|
const formik = useFormik<TravelersFormValues>({
|
|
78
98
|
initialValues,
|
|
79
99
|
enableReinitialize: true,
|
|
80
100
|
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields),
|
|
81
101
|
onSubmit: (values) => {
|
|
102
|
+
if (!editablePackagingEntry) return;
|
|
103
|
+
|
|
82
104
|
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, values)));
|
|
105
|
+
|
|
83
106
|
dispatch(setCurrentStep(1));
|
|
84
107
|
}
|
|
85
108
|
});
|
|
86
109
|
|
|
87
110
|
useEffect(() => {
|
|
88
|
-
if (!context) return;
|
|
111
|
+
if (!context || !config) return;
|
|
112
|
+
|
|
89
113
|
const controller = new AbortController();
|
|
90
114
|
|
|
91
115
|
(async () => {
|
|
92
116
|
try {
|
|
93
117
|
const result = await getCountries(config, controller.signal);
|
|
94
118
|
setCountries(result.items);
|
|
95
|
-
} catch {
|
|
119
|
+
} catch {
|
|
120
|
+
// optionally handle error
|
|
121
|
+
}
|
|
96
122
|
})();
|
|
97
123
|
|
|
98
124
|
return () => controller.abort();
|
|
99
|
-
}, []);
|
|
125
|
+
}, [context, config]);
|
|
100
126
|
|
|
101
127
|
useEffect(() => {
|
|
102
128
|
if (isConfirmationPage) {
|
|
@@ -104,6 +130,15 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
104
130
|
}
|
|
105
131
|
}, [isConfirmationPage, dispatch]);
|
|
106
132
|
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const selectedPackagingAccoResult = packagingAccoResults?.find((result) => result.code === selectedPackagingAccoResultCode);
|
|
135
|
+
if (selectedPackagingAccoResult) {
|
|
136
|
+
setSelectedPackagingAccoResult(selectedPackagingAccoResult);
|
|
137
|
+
}
|
|
138
|
+
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
139
|
+
|
|
140
|
+
if (!context || !editablePackagingEntry || !priceDetails || !config) return <Spinner label={translations.SUMMARY.PROCESS_BOOKING} />;
|
|
141
|
+
|
|
107
142
|
const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
|
108
143
|
e.preventDefault();
|
|
109
144
|
|
|
@@ -141,6 +176,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
141
176
|
agentId: context.agentId,
|
|
142
177
|
payload: updatedEditablePackagingEntry
|
|
143
178
|
} as PackagingRequestBase<PackagingEntry>;
|
|
179
|
+
|
|
144
180
|
const bookingResponse = await bookPackagingEntry(config, request);
|
|
145
181
|
|
|
146
182
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
@@ -149,11 +185,10 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
149
185
|
window.location.href = bookingResponse.paymentUrl;
|
|
150
186
|
} else {
|
|
151
187
|
dispatch(setCurrentStep(2));
|
|
188
|
+
setIsSubmitting(false);
|
|
152
189
|
}
|
|
153
190
|
} catch (error) {
|
|
154
191
|
dispatch(setCurrentStep(3));
|
|
155
|
-
} finally {
|
|
156
|
-
setIsSubmitting(false);
|
|
157
192
|
}
|
|
158
193
|
};
|
|
159
194
|
|
|
@@ -170,6 +205,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
170
205
|
{step + 1}. {stepLabels[step]}
|
|
171
206
|
</>
|
|
172
207
|
)}>
|
|
208
|
+
{isConfirmationPage && isLoading && <Spinner label={translations.SUMMARY.PROCESS_BOOKING} />}
|
|
173
209
|
{currentStep === 0 && (
|
|
174
210
|
<SharedTravelersForm
|
|
175
211
|
formik={formik}
|
|
@@ -188,6 +224,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
188
224
|
translations={translations}
|
|
189
225
|
travelerFormValues={formik.values}
|
|
190
226
|
isSubmitting={isSubmitting}
|
|
227
|
+
skipPayment={!context.generatePaymentUrl}
|
|
191
228
|
userValidated={userValidated}
|
|
192
229
|
remarks={remarks}
|
|
193
230
|
enableVoucher={false}
|
|
@@ -204,22 +241,23 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
204
241
|
)}
|
|
205
242
|
/>
|
|
206
243
|
)}
|
|
244
|
+
|
|
207
245
|
{currentStep === 2 && (
|
|
208
246
|
<SharedConfirmation
|
|
209
247
|
bookingNumber={bookingNumber ?? editablePackagingEntry?.dossierNumber ?? ''}
|
|
210
248
|
isOption={false}
|
|
211
249
|
isOffer={false}
|
|
212
250
|
translations={translations.CONFIRMATION}
|
|
213
|
-
// companyContactPhone={context?.companyContactPhone || ''}
|
|
214
|
-
// companyContactEmail={context?.companyContactEmail || ''}
|
|
215
|
-
// homeUrl={context?.homeUrl || '/'}
|
|
216
251
|
/>
|
|
217
252
|
)}
|
|
253
|
+
|
|
218
254
|
{currentStep === 3 && <div>{/* error */}</div>}
|
|
219
255
|
</BookingPanel>
|
|
220
256
|
</div>
|
|
257
|
+
|
|
221
258
|
<div className="backdrop" id="backdrop"></div>
|
|
222
|
-
|
|
259
|
+
|
|
260
|
+
<WLSidebar activeSearchSeed={activeSearchSeed} packagingAccoResult={selectedPackagingAccoResult} />
|
|
223
261
|
</div>
|
|
224
262
|
</div>
|
|
225
263
|
);
|
|
@@ -10,12 +10,14 @@ import { SearchSeed } from '../../types';
|
|
|
10
10
|
import { getTravelersText } from '../../../booking-wizard/features/sidebar/sidebar-util';
|
|
11
11
|
import { RoomTraveler } from '../../../booking-wizard/types';
|
|
12
12
|
|
|
13
|
-
import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingEntryLine } from '@qite/tide-client';
|
|
13
|
+
import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingAccommodationResponse, PackagingEntryLine, PortalQsmType } from '@qite/tide-client';
|
|
14
14
|
import Spinner from '../spinner/spinner';
|
|
15
15
|
import SharedSidebar from '../../../shared/booking/shared-sidebar';
|
|
16
|
+
import { getImageSrcFromHtml } from '../../../shared/utils/booking-summary';
|
|
16
17
|
|
|
17
18
|
interface WLSidebarProps {
|
|
18
19
|
activeSearchSeed: SearchSeed | null;
|
|
20
|
+
packagingAccoResult: PackagingAccommodationResponse | null;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const mapToSidebarFlightMetaData = (entryLine: PackagingEntryLine) => {
|
|
@@ -66,18 +68,12 @@ const selectSeparatePackagePriceDetails = (priceDetails: BookingPriceDetail[]) =
|
|
|
66
68
|
return result;
|
|
67
69
|
};
|
|
68
70
|
|
|
69
|
-
const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
|
|
71
|
+
const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed, packagingAccoResult }) => {
|
|
70
72
|
const context = useContext(SearchResultsConfigurationContext);
|
|
71
|
-
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
73
|
+
|
|
75
74
|
const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
if (!editablePackagingEntry) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
76
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
81
77
|
|
|
82
78
|
const sortedLines = useMemo(() => {
|
|
83
79
|
return [...(editablePackagingEntry?.lines ?? [])].sort((a, b) => {
|
|
@@ -92,16 +88,31 @@ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
|
|
|
92
88
|
});
|
|
93
89
|
}, [editablePackagingEntry]);
|
|
94
90
|
|
|
91
|
+
const accoImage = useMemo(() => {
|
|
92
|
+
return getImageSrcFromHtml(packagingAccoResult?.contents);
|
|
93
|
+
}, [packagingAccoResult?.contents]);
|
|
94
|
+
|
|
95
|
+
if (!context || !editablePackagingEntry) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
const firstEntryLine = first(sortedLines);
|
|
96
100
|
const accommodationLines = editablePackagingEntry.lines.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE);
|
|
97
101
|
const accommodationLine = first(accommodationLines) ?? firstEntryLine;
|
|
98
102
|
|
|
99
103
|
const location =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
context.searchConfiguration.qsmType === PortalQsmType.Accommodation
|
|
105
|
+
? packagingAccoResult?.name ??
|
|
106
|
+
accommodationLine?.location?.name ??
|
|
107
|
+
accommodationLine?.oord?.name ??
|
|
108
|
+
accommodationLine?.region?.name ??
|
|
109
|
+
accommodationLine?.country?.name ??
|
|
110
|
+
firstEntryLine?.location?.name
|
|
111
|
+
: accommodationLine?.location?.name ??
|
|
112
|
+
accommodationLine?.oord?.name ??
|
|
113
|
+
accommodationLine?.region?.name ??
|
|
114
|
+
accommodationLine?.country?.name ??
|
|
115
|
+
firstEntryLine?.location?.name;
|
|
105
116
|
|
|
106
117
|
const rooms =
|
|
107
118
|
activeSearchSeed?.rooms.map((room) => {
|
|
@@ -136,7 +147,7 @@ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
|
|
|
136
147
|
return (
|
|
137
148
|
<SharedSidebar
|
|
138
149
|
productName={location ?? ''}
|
|
139
|
-
thumbnailUrl={context.destinationImage?.url}
|
|
150
|
+
thumbnailUrl={accoImage ?? context.destinationImage?.url}
|
|
140
151
|
translations={translations}
|
|
141
152
|
travelerRooms={travelerRooms}
|
|
142
153
|
startDateText={first(sortedLines)?.from && formatDate(new Date(first(sortedLines)!.from))}
|
package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx
CHANGED
|
@@ -94,9 +94,12 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
|
|
|
94
94
|
}, [searchResults, selectedReturnKey]);
|
|
95
95
|
|
|
96
96
|
const firstResultDate = uniqueOutwardFlights.length > 0 ? uniqueOutwardFlights[0].outward.segments[0].departureDateTime : null;
|
|
97
|
+
const firstResultReturnDate = uniqueReturnFlights.length > 0 ? uniqueReturnFlights[0].return.segments[0].departureDateTime : null;
|
|
97
98
|
|
|
98
99
|
const firstResultDay = firstResultDate ? format(firstResultDate, 'd') : null;
|
|
99
100
|
const firstResultMonth = firstResultDate ? format(firstResultDate, 'MMM') : null;
|
|
101
|
+
const firstResultReturnDay = firstResultReturnDate ? format(firstResultReturnDate, 'd') : null;
|
|
102
|
+
const firstResultReturnMonth = firstResultReturnDate ? format(firstResultReturnDate, 'MMM') : null;
|
|
100
103
|
|
|
101
104
|
return (
|
|
102
105
|
<>
|
|
@@ -138,8 +141,8 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
|
|
|
138
141
|
|
|
139
142
|
<div className="search__results__label search__results__label--secondary">
|
|
140
143
|
<div className="search__results__label__date">
|
|
141
|
-
|
|
142
|
-
<p>{
|
|
144
|
+
<p className="search__results__label__date-date">{firstResultReturnDay}</p>
|
|
145
|
+
<p>{firstResultReturnMonth}</p>
|
|
143
146
|
</div>
|
|
144
147
|
<div className="search__results__label__text">
|
|
145
148
|
<Icon name="ui-flight" height={16} fill="white" />
|
|
@@ -175,12 +175,21 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
175
175
|
const visibleResults = React.useMemo(() => {
|
|
176
176
|
const shouldShowAll = context?.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight || isFlyIn;
|
|
177
177
|
|
|
178
|
+
let filteredMapperResults = mappedResults;
|
|
179
|
+
|
|
180
|
+
if (selectedPackagingAccoResult) {
|
|
181
|
+
filteredMapperResults = mappedResults.filter((result) => result.code !== selectedPackagingAccoResult.code);
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
if (shouldShowAll) {
|
|
179
|
-
|
|
185
|
+
if (isFlyIn) {
|
|
186
|
+
return mappedResults;
|
|
187
|
+
}
|
|
188
|
+
return filteredMapperResults;
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
if (selectedPackagingAccoResult) {
|
|
183
|
-
return
|
|
192
|
+
return filteredMapperResults.filter((result) => result.code !== selectedPackagingAccoResult.code).slice(0, 2);
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
return mappedResults.slice(0, 3);
|
|
@@ -128,7 +128,11 @@ type BuildPackagingEntryPartialArgs = {
|
|
|
128
128
|
language: string;
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
interface SearchResultsContainerProps {
|
|
132
|
+
onBookingStarted?: () => void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const SearchResultsContainer: React.FC<SearchResultsContainerProps> = ({ onBookingStarted }) => {
|
|
132
136
|
const currentSearch = typeof window !== 'undefined' ? window.location.search : '';
|
|
133
137
|
|
|
134
138
|
const dispatch = useDispatch();
|
|
@@ -361,8 +365,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
361
365
|
if (typeof window !== 'undefined') {
|
|
362
366
|
window.scrollTo(0, 0);
|
|
363
367
|
}
|
|
364
|
-
var adults = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >=
|
|
365
|
-
var kids = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 2 && x.age! <
|
|
368
|
+
var adults = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 12).length;
|
|
369
|
+
var kids = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 2 && x.age! < 12).length;
|
|
366
370
|
var babies = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! < 2).length;
|
|
367
371
|
|
|
368
372
|
return {
|
|
@@ -812,6 +816,12 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
812
816
|
}
|
|
813
817
|
}, [context?.packagingEntry]);
|
|
814
818
|
|
|
819
|
+
useEffect(() => {
|
|
820
|
+
if (bookPackagingEntry && onBookingStarted) {
|
|
821
|
+
onBookingStarted();
|
|
822
|
+
}
|
|
823
|
+
}, [bookPackagingEntry, onBookingStarted]);
|
|
824
|
+
|
|
815
825
|
// separate detailsCall
|
|
816
826
|
useEffect(() => {
|
|
817
827
|
const fetchDetails = async () => {
|
|
@@ -1435,12 +1445,12 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1435
1445
|
seed.rooms?.forEach((room, roomIndex) => {
|
|
1436
1446
|
const paxIds = room.pax.map((_, paxIndex) => {
|
|
1437
1447
|
const id = paxId++;
|
|
1438
|
-
|
|
1439
1448
|
pax.push({
|
|
1440
1449
|
id,
|
|
1441
|
-
firstName: '',
|
|
1442
|
-
lastName: '',
|
|
1443
|
-
dateOfBirth: null,
|
|
1450
|
+
firstName: _.firstName || '',
|
|
1451
|
+
lastName: _.lastName || '',
|
|
1452
|
+
dateOfBirth: _.dateOfBirth || null,
|
|
1453
|
+
age: _.age || null,
|
|
1444
1454
|
isMainBooker: roomIndex === 0 && paxIndex === 0
|
|
1445
1455
|
});
|
|
1446
1456
|
|
|
@@ -1479,7 +1489,11 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1479
1489
|
{context && (
|
|
1480
1490
|
<div className="search">
|
|
1481
1491
|
{bookPackagingEntry ? (
|
|
1482
|
-
<BookPackagingEntry
|
|
1492
|
+
<BookPackagingEntry
|
|
1493
|
+
activeSearchSeed={activeSearchSeed}
|
|
1494
|
+
isLoading={itineraryIsLoading || pricesAreLoading}
|
|
1495
|
+
isConfirmationPage={isBookingConfirmation}
|
|
1496
|
+
/>
|
|
1483
1497
|
) : (
|
|
1484
1498
|
<div className="search__container">
|
|
1485
1499
|
{context.searchConfiguration.qsmType === PortalQsmType.Flight && (
|
|
@@ -7,15 +7,16 @@ import { createSearchResultsStore } from './store/search-results-store';
|
|
|
7
7
|
|
|
8
8
|
interface SearchResultsProps {
|
|
9
9
|
configuration: SearchResultsConfiguration;
|
|
10
|
+
onBookingStarted?: () => void;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const SearchResults: React.FC<SearchResultsProps> = ({ configuration }) => {
|
|
13
|
+
const SearchResults: React.FC<SearchResultsProps> = ({ configuration, onBookingStarted }) => {
|
|
13
14
|
const store = React.useMemo(() => createSearchResultsStore(), []);
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
17
|
<Provider store={store}>
|
|
17
18
|
<SearchResultsConfigurationContext.Provider value={configuration}>
|
|
18
|
-
<SearchResultsContainer />
|
|
19
|
+
<SearchResultsContainer onBookingStarted={onBookingStarted} />
|
|
19
20
|
</SearchResultsConfigurationContext.Provider>
|
|
20
21
|
</Provider>
|
|
21
22
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { compact, findIndex, isEmpty, isNil, uniqBy } from 'lodash';
|
|
2
2
|
import React, { ReactNode, useEffect, useState } from 'react';
|
|
3
3
|
import { SummaryCheckbox, TravelersFormValues } from '../../booking-wizard/types';
|
|
4
|
-
import Loader from '../components/loader';
|
|
5
4
|
import { buildClassName } from '../utils/class-util';
|
|
6
5
|
import Icon from '../components/icon';
|
|
6
|
+
import Spinner from '../../search-results/components/spinner/spinner';
|
|
7
7
|
|
|
8
8
|
export interface SummaryNotification {
|
|
9
9
|
id: number;
|
|
@@ -32,6 +32,7 @@ export interface SharedSummaryProps {
|
|
|
32
32
|
isOffer?: boolean;
|
|
33
33
|
customValidateText?: string;
|
|
34
34
|
isSubmitting?: boolean;
|
|
35
|
+
skipPayment?: boolean;
|
|
35
36
|
userValidated?: boolean;
|
|
36
37
|
renderOptions: () => ReactNode;
|
|
37
38
|
renderPreviousButton: () => ReactNode;
|
|
@@ -66,6 +67,7 @@ const SharedSummary: React.FC<SharedSummaryProps> = ({
|
|
|
66
67
|
isOffer = false,
|
|
67
68
|
customValidateText,
|
|
68
69
|
isSubmitting = false,
|
|
70
|
+
skipPayment = false,
|
|
69
71
|
userValidated = true,
|
|
70
72
|
renderOptions,
|
|
71
73
|
renderPreviousButton,
|
|
@@ -122,7 +124,7 @@ const SharedSummary: React.FC<SharedSummaryProps> = ({
|
|
|
122
124
|
|
|
123
125
|
return (
|
|
124
126
|
<>
|
|
125
|
-
{isSubmitting &&
|
|
127
|
+
{isSubmitting && <Spinner label={skipPayment ? translations.SUMMARY.PROCESS_BOOKING : translations.SUMMARY.REDIRECT} />}
|
|
126
128
|
{!isSubmitting && (
|
|
127
129
|
<form className="form" name="booking--summary" id="booking--summary" onSubmit={onSubmit}>
|
|
128
130
|
<div className="form__booking--summary">
|
|
@@ -98,13 +98,13 @@ export function createInitialValuesFromRooms(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry: PackagingEntry, agentAdressId?: number): TravelersFormValues {
|
|
101
|
-
console.log('editablePackagingEntry?.pax:', editablePackagingEntry?.pax);
|
|
102
101
|
const pax = editablePackagingEntry?.pax ?? [];
|
|
103
102
|
const rooms = editablePackagingEntry.rooms.map((room) => {
|
|
104
103
|
const roomPax = pax.filter((x: PackagingEntryPax) => room.paxIds.includes(x.id));
|
|
105
|
-
|
|
104
|
+
const adults = roomPax.filter((x: PackagingEntryPax) => x.age! >= 18);
|
|
105
|
+
const children = roomPax.filter((x: PackagingEntryPax) => x.age! < 18);
|
|
106
106
|
return {
|
|
107
|
-
adults:
|
|
107
|
+
adults: adults.map((roomTraveler: PackagingEntryPax) => {
|
|
108
108
|
return {
|
|
109
109
|
id: roomTraveler.id,
|
|
110
110
|
firstName: roomTraveler.firstName ?? '',
|
|
@@ -113,7 +113,15 @@ export function createInitialValuesFromEditablePackagingEntry(editablePackagingE
|
|
|
113
113
|
gender: ''
|
|
114
114
|
} as Traveler;
|
|
115
115
|
}),
|
|
116
|
-
children:
|
|
116
|
+
children: children.map((roomTraveler: PackagingEntryPax) => {
|
|
117
|
+
return {
|
|
118
|
+
id: roomTraveler.id,
|
|
119
|
+
firstName: roomTraveler.firstName ?? '',
|
|
120
|
+
lastName: roomTraveler.lastName ?? '',
|
|
121
|
+
birthDate: roomTraveler.dateOfBirth ? format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '',
|
|
122
|
+
gender: ''
|
|
123
|
+
} as Traveler;
|
|
124
|
+
})
|
|
117
125
|
};
|
|
118
126
|
});
|
|
119
127
|
|
|
@@ -144,8 +152,6 @@ export function createInitialValuesFromEditablePackagingEntry(editablePackagingE
|
|
|
144
152
|
|
|
145
153
|
export function applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry: PackagingEntry, values: TravelersFormValues) {
|
|
146
154
|
const travelers = values.rooms.flatMap((room) => [...room.adults, ...room.children]);
|
|
147
|
-
console.log('Applying form values:', values);
|
|
148
|
-
console.log('editablePackagingEntry:', editablePackagingEntry);
|
|
149
155
|
|
|
150
156
|
return {
|
|
151
157
|
...editablePackagingEntry,
|
|
@@ -4,6 +4,7 @@ import { useFlightSearch } from '../../../search-results/components/flight/fligh
|
|
|
4
4
|
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import {
|
|
6
6
|
resetFilters,
|
|
7
|
+
setBookPackagingEntry,
|
|
7
8
|
setFilters,
|
|
8
9
|
setFlyInType,
|
|
9
10
|
setSelectedFlight,
|
|
@@ -125,6 +126,14 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
125
126
|
}
|
|
126
127
|
};
|
|
127
128
|
|
|
129
|
+
const onHandleConfirm = () => {
|
|
130
|
+
if (context?.packagingEntry) {
|
|
131
|
+
handleConfirm?.();
|
|
132
|
+
} else {
|
|
133
|
+
dispatch(setBookPackagingEntry(true));
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
128
137
|
return (
|
|
129
138
|
<div
|
|
130
139
|
className={`flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${
|
|
@@ -211,7 +220,7 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
211
220
|
)}
|
|
212
221
|
|
|
213
222
|
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && flyInType === 'acco-details' && (
|
|
214
|
-
<AccommodationFlyIn isLoading={detailsLoading} handleConfirm={
|
|
223
|
+
<AccommodationFlyIn isLoading={detailsLoading} handleConfirm={onHandleConfirm} />
|
|
215
224
|
)}
|
|
216
225
|
|
|
217
226
|
{srpType === PortalQsmType.AccommodationAndFlight && (flyInType === 'flight-outward-results' || flyInType === 'flight-return-results') && (
|
|
@@ -225,7 +225,9 @@
|
|
|
225
225
|
"VOUCHER_VALIDATE": "تحقق من القسيمة",
|
|
226
226
|
"ADD_VOUCHER": "إضافة قسيمة",
|
|
227
227
|
"VOUCHER_VALID": "القسيمة صالحة",
|
|
228
|
-
"VOUCHER_INVALID": "القسيمة غير صالحة"
|
|
228
|
+
"VOUCHER_INVALID": "القسيمة غير صالحة",
|
|
229
|
+
"REDIRECT": "إعادة التوجيه إلى مزود الدفع...",
|
|
230
|
+
"PROCESS_BOOKING": "يرجى الانتظار، جاري معالجة حجزك"
|
|
229
231
|
},
|
|
230
232
|
"CONFIRMATION": {
|
|
231
233
|
"TITLE_TEXT_OFFER": "تم طلب عرضك رقم {0}",
|
|
@@ -225,7 +225,9 @@
|
|
|
225
225
|
"VOUCHER_VALIDATE": "Valider voucher",
|
|
226
226
|
"ADD_VOUCHER": "Tilføj voucher",
|
|
227
227
|
"VOUCHER_VALID": "Voucher er gyldig",
|
|
228
|
-
"VOUCHER_INVALID": "Voucher er ikke gyldig"
|
|
228
|
+
"VOUCHER_INVALID": "Voucher er ikke gyldig",
|
|
229
|
+
"REDIRECT": "Omdirigerer til betalingsudbyder...",
|
|
230
|
+
"PROCESS_BOOKING": "Vent venligst, din booking behandles"
|
|
229
231
|
},
|
|
230
232
|
"CONFIRMATION": {
|
|
231
233
|
"TITLE_TEXT_OFFER": "Dit tilbud med nummer {0} er blevet anmodet",
|
|
@@ -225,7 +225,9 @@
|
|
|
225
225
|
"VOUCHER_VALIDATE": "Gutschein prüfen",
|
|
226
226
|
"ADD_VOUCHER": "Gutschein hinzufügen",
|
|
227
227
|
"VOUCHER_VALID": "Gutschein ist gültig",
|
|
228
|
-
"VOUCHER_INVALID": "Gutschein ist ungültig"
|
|
228
|
+
"VOUCHER_INVALID": "Gutschein ist ungültig",
|
|
229
|
+
"REDIRECT": "Weiterleitung zum Zahlungsanbieter...",
|
|
230
|
+
"PROCESS_BOOKING": "Bitte warten Sie, Ihre Buchung wird bearbeitet"
|
|
229
231
|
},
|
|
230
232
|
"CONFIRMATION": {
|
|
231
233
|
"TITLE_TEXT_OFFER": "Ihr Angebot mit der Nummer {0} wurde angefordert",
|
|
@@ -229,7 +229,9 @@
|
|
|
229
229
|
"VOUCHER_VALIDATE": "Validate voucher",
|
|
230
230
|
"ADD_VOUCHER": "Add voucher",
|
|
231
231
|
"VOUCHER_VALID": "Voucher is valid",
|
|
232
|
-
"VOUCHER_INVALID": "Voucher is not valid"
|
|
232
|
+
"VOUCHER_INVALID": "Voucher is not valid",
|
|
233
|
+
"REDIRECT": "Redirecting to payment provider...",
|
|
234
|
+
"PROCESS_BOOKING": "Please wait, your booking is being processed"
|
|
233
235
|
},
|
|
234
236
|
"CONFIRMATION": {
|
|
235
237
|
"TITLE_TEXT_OFFER": "Your quote with number {0} has been requested",
|
|
@@ -225,7 +225,9 @@
|
|
|
225
225
|
"VOUCHER_VALIDATE": "Validar vale",
|
|
226
226
|
"ADD_VOUCHER": "Agregar vale",
|
|
227
227
|
"VOUCHER_VALID": "Vale válido",
|
|
228
|
-
"VOUCHER_INVALID": "Vale no válido"
|
|
228
|
+
"VOUCHER_INVALID": "Vale no válido",
|
|
229
|
+
"REDIRECT": "Redirigiendo al proveedor de pagos...",
|
|
230
|
+
"PROCESS_BOOKING": "Por favor, espere, su reserva se está procesando"
|
|
229
231
|
},
|
|
230
232
|
"CONFIRMATION": {
|
|
231
233
|
"TITLE_TEXT_OFFER": "Su presupuesto con número {0} ha sido solicitado",
|