@qite/tide-booking-component 1.4.102 → 1.4.104
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 +1773 -877
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-cjs/src/search-results/types.d.ts +14 -2
- package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +2 -0
- package/build/build-esm/index.js +1747 -861
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-esm/src/search-results/types.d.ts +14 -2
- package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +2 -0
- package/package.json +2 -2
- package/src/booking-wizard/features/flight-options/index.tsx +6 -2
- package/src/search-results/components/filters/filters.tsx +8 -9
- package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +31 -4
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -25
- package/src/search-results/components/icon.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +194 -130
- package/src/search-results/store/search-results-selectors.ts +73 -0
- package/src/search-results/store/search-results-slice.ts +94 -14
- package/src/search-results/types.ts +14 -2
- package/src/search-results/utils/search-results-utils.ts +310 -58
- package/src/shared/components/flyin/flyin.tsx +102 -19
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
- package/src/shared/translations/ar-SA.json +2 -0
- package/src/shared/translations/da-DK.json +2 -0
- package/src/shared/translations/de-DE.json +2 -0
- package/src/shared/translations/en-GB.json +2 -0
- package/src/shared/translations/es-ES.json +2 -0
- package/src/shared/translations/fr-BE.json +2 -0
- package/src/shared/translations/fr-FR.json +2 -0
- package/src/shared/translations/is-IS.json +2 -0
- package/src/shared/translations/it-IT.json +2 -0
- package/src/shared/translations/ja-JP.json +2 -0
- package/src/shared/translations/nl-BE.json +2 -0
- package/src/shared/translations/nl-NL.json +2 -0
- package/src/shared/translations/no-NO.json +2 -0
- package/src/shared/translations/pl-PL.json +2 -0
- package/src/shared/translations/pt-PT.json +2 -0
- package/src/shared/translations/sv-SE.json +2 -0
- package/src/shared/utils/localization-util.ts +5 -2
- package/styles/components/_flight-option.scss +14 -1
- package/styles/components/_flyin.scss +16 -0
- package/styles/components/_search.scss +9 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import React, { useContext,
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
|
2
2
|
import { Filter, FilterOption } from '../../types';
|
|
3
3
|
import MultiRangeFilter from '../multi-range-filter';
|
|
4
4
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
5
|
-
import { resetFilters, setFilters } from '../../store/search-results-slice';
|
|
6
|
-
import { useDispatch } from 'react-redux';
|
|
7
5
|
import Spinner from '../spinner/spinner';
|
|
8
6
|
import Icon from '../icon';
|
|
9
7
|
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
@@ -14,10 +12,13 @@ interface FiltersProps {
|
|
|
14
12
|
isOpen: boolean;
|
|
15
13
|
handleSetIsOpen: () => void;
|
|
16
14
|
isLoading?: boolean;
|
|
15
|
+
setFilters: (filters: Filter[]) => void;
|
|
16
|
+
resetFilters: (filters: Filter[]) => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, handleSetIsOpen, isLoading }) => {
|
|
19
|
+
const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, handleSetIsOpen, isLoading, setFilters, resetFilters }) => {
|
|
20
20
|
const context = useContext(SearchResultsConfigurationContext);
|
|
21
|
+
|
|
21
22
|
if (!context || !context.showFilters) {
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
@@ -25,8 +26,6 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
25
26
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
26
27
|
const [visibleFilters, setVisibleFilters] = useState<Record<string, boolean>>({});
|
|
27
28
|
|
|
28
|
-
const dispatch = useDispatch();
|
|
29
|
-
|
|
30
29
|
const toggleFilterVisibility = (filterId: string) => {
|
|
31
30
|
setVisibleFilters((prev) => ({
|
|
32
31
|
...prev,
|
|
@@ -44,7 +43,7 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
44
43
|
};
|
|
45
44
|
});
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
setFilters(updated);
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
const handleSliderChange = (filter: Filter, newMin: number, newMax: number) => {
|
|
@@ -58,12 +57,12 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
58
57
|
};
|
|
59
58
|
});
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
setFilters(updated);
|
|
62
61
|
};
|
|
63
62
|
|
|
64
63
|
const handleFullReset = () => {
|
|
65
64
|
if (!isLoading) {
|
|
66
|
-
|
|
65
|
+
resetFilters(initialFilters);
|
|
67
66
|
}
|
|
68
67
|
};
|
|
69
68
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext, useState } from 'react';
|
|
2
|
-
import { durationTicksInHoursString, getTranslations, longFormatDate, timeFromDateTime } from '../../../../shared/utils/localization-util';
|
|
2
|
+
import { durationTicksInHoursString, formatPrice, getTranslations, longFormatDate, timeFromDateTime } from '../../../../shared/utils/localization-util';
|
|
3
3
|
import Icon from '../../icon';
|
|
4
4
|
import SearchResultsConfigurationContext from '../../../search-results-configuration-context';
|
|
5
5
|
import { getArrivalSegment, getDepartureSegment, getFlightSegments, getNumberOfStopsLabel } from '../../../utils/flight-utils';
|
|
@@ -11,18 +11,32 @@ interface IndependentFlightOptionProps {
|
|
|
11
11
|
guid: string | null;
|
|
12
12
|
selectedGuid?: string | null;
|
|
13
13
|
isOutward: boolean;
|
|
14
|
+
showSelectedState?: boolean;
|
|
15
|
+
currentSelectedPrice?: number;
|
|
16
|
+
price?: number;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
const IndependentFlightOption: React.FC<IndependentFlightOptionProps> = ({
|
|
19
|
+
const IndependentFlightOption: React.FC<IndependentFlightOptionProps> = ({
|
|
20
|
+
item,
|
|
21
|
+
onSelect,
|
|
22
|
+
guid,
|
|
23
|
+
selectedGuid,
|
|
24
|
+
isOutward,
|
|
25
|
+
showSelectedState,
|
|
26
|
+
currentSelectedPrice,
|
|
27
|
+
price
|
|
28
|
+
}) => {
|
|
17
29
|
const context = useContext(SearchResultsConfigurationContext);
|
|
18
30
|
const language = context?.languageCode ?? 'en-GB';
|
|
19
31
|
const translations = getTranslations(language);
|
|
20
32
|
|
|
21
33
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
22
34
|
|
|
35
|
+
const priceDifference = price && currentSelectedPrice ? price - currentSelectedPrice : null;
|
|
36
|
+
|
|
23
37
|
return (
|
|
24
38
|
<div className="search__result-card" key={`flight-${item.code}`}>
|
|
25
|
-
<div className=
|
|
39
|
+
<div className={`flight ${showSelectedState && selectedGuid === guid ? 'flight--selected' : ''}`}>
|
|
26
40
|
<div className="flight__option">
|
|
27
41
|
<div className="flight__content">
|
|
28
42
|
<div className="flight__flights">
|
|
@@ -30,7 +44,20 @@ const IndependentFlightOption: React.FC<IndependentFlightOptionProps> = ({ item,
|
|
|
30
44
|
<div className="flight__flight__header">
|
|
31
45
|
<div className="flight__status__container"></div>
|
|
32
46
|
<div className="flight__price">
|
|
33
|
-
{
|
|
47
|
+
{price != null &&
|
|
48
|
+
price > 0 &&
|
|
49
|
+
(isOutward ? (
|
|
50
|
+
<span className="price">
|
|
51
|
+
{translations.QSM.ROUNDTRIP + ' ' + translations.SRP.PRICE} {formatPrice(price, 'EUR', context?.languageCode ?? 'en-GB')}
|
|
52
|
+
</span>
|
|
53
|
+
) : (
|
|
54
|
+
priceDifference != null &&
|
|
55
|
+
Math.abs(priceDifference) > 0 && (
|
|
56
|
+
<span className="price">
|
|
57
|
+
{priceDifference > 0 ? '+' : '-'} {formatPrice(Math.abs(priceDifference), 'EUR', context?.languageCode ?? 'en-GB')}
|
|
58
|
+
</span>
|
|
59
|
+
)
|
|
60
|
+
))}
|
|
34
61
|
<button type="button" className={`cta ${selectedGuid === guid ? 'cta--selected' : 'cta--select'}`} onClick={() => onSelect?.()}>
|
|
35
62
|
{selectedGuid === guid ? 'Selected' : 'Select'}
|
|
36
63
|
</button>
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import { HotelResult, SearchResultsConfiguration } from '../../types';
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
|
2
|
+
import { FlyInType, HotelResult, SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import Spinner from '../spinner/spinner';
|
|
4
4
|
import HotelCard from './hotel-card';
|
|
5
|
-
import { useSelector } from 'react-redux';
|
|
5
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
6
6
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
7
7
|
import { BookingPackageItem, PackagingAccommodationResponse, PortalQsmType } from '@qite/tide-client';
|
|
8
8
|
import { format, parseISO } from 'date-fns';
|
|
9
9
|
import { calculateNights, formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
10
10
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
11
11
|
import { first } from 'lodash';
|
|
12
|
+
import Icon from '../icon';
|
|
13
|
+
import { setFlyInIsOpen, setFlyInType } from '../../store/search-results-slice';
|
|
12
14
|
|
|
13
15
|
interface HotelAccommodationResultsProps {
|
|
14
16
|
isLoading: boolean;
|
|
17
|
+
isFlyIn?: boolean;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const getLocation = (locationName?: string, regionName?: string, countryName?: string, fallback?: string): string => {
|
|
@@ -70,7 +73,14 @@ const mapPackagingAccoResult = (searchResult: PackagingAccommodationResponse, cm
|
|
|
70
73
|
};
|
|
71
74
|
};
|
|
72
75
|
|
|
73
|
-
const renderHotelResults = (
|
|
76
|
+
const renderHotelResults = (
|
|
77
|
+
results: HotelResult[],
|
|
78
|
+
context: SearchResultsConfiguration,
|
|
79
|
+
activeTab: string | null,
|
|
80
|
+
translations: any,
|
|
81
|
+
selectedPackagingAccoResult?: HotelResult,
|
|
82
|
+
isFlyIn?: boolean
|
|
83
|
+
) => {
|
|
74
84
|
const renderedResults = results.map((result, index) => {
|
|
75
85
|
const key = `${result.id ?? result.code}-${index}`;
|
|
76
86
|
|
|
@@ -85,19 +95,28 @@ const renderHotelResults = (results: HotelResult[], context: SearchResultsConfig
|
|
|
85
95
|
return <HotelCard key={key} result={result} translations={translations} />;
|
|
86
96
|
});
|
|
87
97
|
|
|
88
|
-
return
|
|
98
|
+
return (
|
|
99
|
+
<div className={`search__results__cards ${activeTab ? `search__results__cards--${activeTab}` : ''}`}>
|
|
100
|
+
{selectedPackagingAccoResult && !isFlyIn && (
|
|
101
|
+
<HotelCard key={selectedPackagingAccoResult.code} result={selectedPackagingAccoResult} translations={translations} />
|
|
102
|
+
)}
|
|
103
|
+
{renderedResults}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
89
106
|
};
|
|
90
107
|
|
|
91
|
-
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading }) => {
|
|
108
|
+
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading, isFlyIn }) => {
|
|
92
109
|
const context = useContext(SearchResultsConfigurationContext);
|
|
93
|
-
|
|
110
|
+
const dispatch = useDispatch();
|
|
94
111
|
if (!context) {
|
|
95
112
|
return null;
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
99
116
|
|
|
100
|
-
const { filteredResults, filteredPackagingAccoResults, activeTab } = useSelector(
|
|
117
|
+
const { filteredResults, filteredPackagingAccoResults, packagingAccoResults, activeTab, flyInIsOpen, selectedPackagingAccoResultCode } = useSelector(
|
|
118
|
+
(state: SearchResultsRootState) => state.searchResults
|
|
119
|
+
);
|
|
101
120
|
|
|
102
121
|
const cmsMap = React.useMemo(() => {
|
|
103
122
|
const map = new Map();
|
|
@@ -146,29 +165,66 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
146
165
|
const firstResultDay = firstResultDate ? format(parseISO(firstResultDate), 'd') : null;
|
|
147
166
|
const firstResultMonth = firstResultDate ? format(parseISO(firstResultDate), 'MMM') : null;
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
const selectedPackagingAccoResult = React.useMemo(() => {
|
|
169
|
+
const selectedResult = packagingAccoResults.find((result) => result.code === selectedPackagingAccoResultCode);
|
|
170
|
+
if (selectedResult) {
|
|
171
|
+
return mapPackagingAccoResult(selectedResult, cmsMap.get(selectedResult.code), context.languageCode, translations);
|
|
172
|
+
}
|
|
173
|
+
}, [packagingAccoResults, selectedPackagingAccoResultCode, cmsMap, context.languageCode, translations]);
|
|
152
174
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
const visibleResults = React.useMemo(() => {
|
|
176
|
+
const shouldShowAll = context?.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight || isFlyIn;
|
|
177
|
+
|
|
178
|
+
if (shouldShowAll) {
|
|
179
|
+
return mappedResults;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (selectedPackagingAccoResult) {
|
|
183
|
+
return mappedResults.filter((result) => result.code !== selectedPackagingAccoResult.code).slice(0, 2);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return mappedResults.slice(0, 3);
|
|
187
|
+
}, [context?.searchConfiguration.qsmType, mappedResults, isFlyIn, selectedPackagingAccoResult]);
|
|
188
|
+
|
|
189
|
+
const handleShowMoreHotels = (flyInType: FlyInType) => {
|
|
190
|
+
dispatch(setFlyInType(flyInType));
|
|
191
|
+
dispatch(setFlyInIsOpen(true));
|
|
192
|
+
};
|
|
156
193
|
|
|
157
194
|
return (
|
|
158
195
|
<>
|
|
159
|
-
|
|
160
|
-
<div className="
|
|
161
|
-
<
|
|
162
|
-
|
|
196
|
+
{!isFlyIn && (
|
|
197
|
+
<div className="search__results__label search__results__label--secondary">
|
|
198
|
+
<div className="search__results__label__date">
|
|
199
|
+
{firstResultDay && firstResultMonth ? (
|
|
200
|
+
<>
|
|
201
|
+
<p className="search__results__label__date-date">{firstResultDay}</p>
|
|
202
|
+
<p>{firstResultMonth}</p>
|
|
203
|
+
</>
|
|
204
|
+
) : (
|
|
205
|
+
<Icon name="ui-bed" height={16} fill="white" />
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
<div className="search__results__label__text">
|
|
209
|
+
<h3>
|
|
210
|
+
{translations.SRP.SELECT} <strong>{translations.SRP.ACCOMMODATION}</strong>
|
|
211
|
+
</h3>
|
|
212
|
+
</div>
|
|
163
213
|
</div>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
214
|
+
)}
|
|
215
|
+
{isLoading ? (
|
|
216
|
+
<>{context.customSpinner ?? <Spinner />}</>
|
|
217
|
+
) : (
|
|
218
|
+
renderHotelResults(visibleResults, context, activeTab, translations, selectedPackagingAccoResult, isFlyIn)
|
|
219
|
+
)}
|
|
220
|
+
{packagingAccoResults.length > 3 && !isFlyIn && (
|
|
221
|
+
<div className="search__results__cards__actions">
|
|
222
|
+
<button className="cta cta--secondary" onClick={() => handleShowMoreHotels('acco-results')}>
|
|
223
|
+
{translations.SRP.SHOW_MORE}
|
|
224
|
+
</button>
|
|
168
225
|
</div>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
{renderHotelResults(mappedResults, context, activeTab, translations)}
|
|
226
|
+
)}
|
|
227
|
+
{mappedResults.length === 0 && !isLoading && <div className="no-results">{translations.SRP.NO_RESULTS}</div>}
|
|
172
228
|
</>
|
|
173
229
|
);
|
|
174
230
|
};
|
|
@@ -134,7 +134,7 @@ const Icon: React.FC<IconProps> = ({ name, className, title, width, height, fill
|
|
|
134
134
|
{title && <title>{title}</title>}
|
|
135
135
|
<path
|
|
136
136
|
d="M32 32c17.7 0 32 14.3 32 32l0 256 224 0 0-160c0-17.7 14.3-32 32-32l224 0c53 0 96 43 96 96l0 224c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-32-224 0-32 0L64 416l0 32c0 17.7-14.3 32-32 32s-32-14.3-32-32L0 64C0 46.3 14.3 32 32 32zm144 96a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"
|
|
137
|
-
fill=
|
|
137
|
+
fill={fill ?? 'currentColor'}
|
|
138
138
|
/>
|
|
139
139
|
</svg>
|
|
140
140
|
);
|