@qite/tide-booking-component 1.4.113 → 1.4.115
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 +219 -130
- 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/types.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-esm/index.js +219 -130
- 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/types.d.ts +1 -0
- package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
- package/package.json +1 -1
- package/src/search-results/components/book-packaging-entry/index.tsx +27 -7
- 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 +5 -1
- package/src/search-results/types.ts +1 -0
- package/src/shared/booking/summary.tsx +0 -1
- package/src/shared/components/flyin/flights-flyin.tsx +5 -2
- package/src/shared/components/flyin/flyin.tsx +10 -1
- package/src/shared/utils/booking-summary.tsx +11 -0
- package/src/shared/utils/tide-api-utils.ts +2 -2
- package/styles/components/_select-wrapper.scss +5 -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,16 +59,19 @@ 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
|
|
|
56
|
-
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber } = useSelector(
|
|
66
|
+
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = useSelector(
|
|
67
|
+
(state: SearchResultsRootState) => state.searchResults
|
|
68
|
+
);
|
|
57
69
|
|
|
58
70
|
const [countries, setCountries] = useState<CountryItem[]>([]);
|
|
59
71
|
const [userValidated, setUserValidated] = useState(true);
|
|
60
72
|
const [remarks, setRemarks] = useState('');
|
|
61
73
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
74
|
+
const [selectedPackagingAccoResult, setSelectedPackagingAccoResult] = useState<PackagingAccommodationResponse | null>(null);
|
|
62
75
|
|
|
63
76
|
const translations = useMemo(() => getTranslations(context?.languageCode ?? 'en-GB'), [context?.languageCode]);
|
|
64
77
|
|
|
@@ -117,7 +130,14 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
117
130
|
}
|
|
118
131
|
}, [isConfirmationPage, dispatch]);
|
|
119
132
|
|
|
120
|
-
|
|
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} />;
|
|
121
141
|
|
|
122
142
|
const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
|
123
143
|
e.preventDefault();
|
|
@@ -165,11 +185,10 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
165
185
|
window.location.href = bookingResponse.paymentUrl;
|
|
166
186
|
} else {
|
|
167
187
|
dispatch(setCurrentStep(2));
|
|
188
|
+
setIsSubmitting(false);
|
|
168
189
|
}
|
|
169
190
|
} catch (error) {
|
|
170
191
|
dispatch(setCurrentStep(3));
|
|
171
|
-
} finally {
|
|
172
|
-
setIsSubmitting(false);
|
|
173
192
|
}
|
|
174
193
|
};
|
|
175
194
|
|
|
@@ -186,6 +205,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
186
205
|
{step + 1}. {stepLabels[step]}
|
|
187
206
|
</>
|
|
188
207
|
)}>
|
|
208
|
+
{isConfirmationPage && isLoading && <Spinner label={translations.SUMMARY.PROCESS_BOOKING} />}
|
|
189
209
|
{currentStep === 0 && (
|
|
190
210
|
<SharedTravelersForm
|
|
191
211
|
formik={formik}
|
|
@@ -237,7 +257,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
237
257
|
|
|
238
258
|
<div className="backdrop" id="backdrop"></div>
|
|
239
259
|
|
|
240
|
-
<WLSidebar activeSearchSeed={activeSearchSeed} />
|
|
260
|
+
<WLSidebar activeSearchSeed={activeSearchSeed} packagingAccoResult={selectedPackagingAccoResult} />
|
|
241
261
|
</div>
|
|
242
262
|
</div>
|
|
243
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);
|
|
@@ -1489,7 +1489,11 @@ const SearchResultsContainer: React.FC<SearchResultsContainerProps> = ({ onBooki
|
|
|
1489
1489
|
{context && (
|
|
1490
1490
|
<div className="search">
|
|
1491
1491
|
{bookPackagingEntry ? (
|
|
1492
|
-
<BookPackagingEntry
|
|
1492
|
+
<BookPackagingEntry
|
|
1493
|
+
activeSearchSeed={activeSearchSeed}
|
|
1494
|
+
isLoading={itineraryIsLoading || pricesAreLoading}
|
|
1495
|
+
isConfirmationPage={isBookingConfirmation}
|
|
1496
|
+
/>
|
|
1493
1497
|
) : (
|
|
1494
1498
|
<div className="search__container">
|
|
1495
1499
|
{context.searchConfiguration.qsmType === PortalQsmType.Flight && (
|
|
@@ -60,6 +60,7 @@ export interface SearchResultsConfiguration {
|
|
|
60
60
|
|
|
61
61
|
destinationImage?: { url: string; alt: string };
|
|
62
62
|
onBook?: (result: BookingPackage) => void;
|
|
63
|
+
onFlightBook?: (result: ExtendedFlightSearchResponseItem) => void;
|
|
63
64
|
packagingEntry?: PackagingEntry | null;
|
|
64
65
|
|
|
65
66
|
generatePaymentUrl?: boolean;
|
|
@@ -1,7 +1,6 @@
|
|
|
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';
|
|
7
6
|
import Spinner from '../../search-results/components/spinner/spinner';
|
|
@@ -140,10 +140,13 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
// TODO: go to booking page?
|
|
143
|
-
const
|
|
143
|
+
const onHandleConfirm = () => {
|
|
144
144
|
if (isOpen) {
|
|
145
145
|
onCancelSearch();
|
|
146
146
|
setIsOpen(false);
|
|
147
|
+
if (context?.onFlightBook && selectedCombinationFlight) {
|
|
148
|
+
context.onFlightBook(selectedCombinationFlight);
|
|
149
|
+
}
|
|
147
150
|
}
|
|
148
151
|
};
|
|
149
152
|
|
|
@@ -492,7 +495,7 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
|
492
495
|
{translations.SHARED.TOTAL_PRICE}: €{selectedCombinationFlight?.price?.toFixed(2)}
|
|
493
496
|
</div>
|
|
494
497
|
<div className="flyin__button-wrapper">
|
|
495
|
-
<button className="cta cta--select" onClick={
|
|
498
|
+
<button className="cta cta--select" onClick={onHandleConfirm}>
|
|
496
499
|
{translations.PRODUCT.BOOK_NOW}
|
|
497
500
|
</button>
|
|
498
501
|
</div>
|
|
@@ -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') && (
|
|
@@ -44,3 +44,14 @@ export const renderEditablePackagingEntrySummaryOptions = (editablePackagingEntr
|
|
|
44
44
|
);
|
|
45
45
|
});
|
|
46
46
|
};
|
|
47
|
+
|
|
48
|
+
export const getImageSrcFromHtml = (html?: string | null): string | undefined => {
|
|
49
|
+
if (!html || typeof window === 'undefined') {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
54
|
+
const img = doc.querySelector('img');
|
|
55
|
+
|
|
56
|
+
return img?.getAttribute('src') ?? undefined;
|
|
57
|
+
};
|
|
@@ -3,8 +3,8 @@ import { isNil } from 'lodash';
|
|
|
3
3
|
import { ApiSettingsState } from '../types';
|
|
4
4
|
|
|
5
5
|
export const tideConnection = {
|
|
6
|
-
|
|
7
|
-
host: 'https://preview-tide.tidesoftware.be',
|
|
6
|
+
host: 'https://localhost:44341',
|
|
7
|
+
// host: 'https://preview-tide.tidesoftware.be',
|
|
8
8
|
apiKey: 'e9b95d79-de4c-41d6-ab7e-3dd429873058',
|
|
9
9
|
catalogueIds: [1],
|
|
10
10
|
officeId: 1
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
border-left: 2px solid var(--tide-booking-room-options-dropdown-select-icon-color);
|
|
14
14
|
border-bottom: 2px solid var(--tide-booking-room-options-dropdown-select-icon-color);
|
|
15
15
|
transform: rotate(-45deg);
|
|
16
|
+
pointer-events: none;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -30,6 +31,10 @@
|
|
|
30
31
|
border: --tide-booking-room-options-dropdown-select-border-focus;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
&:hover {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
option {
|
|
34
39
|
appearance: none;
|
|
35
40
|
display: flex;
|