@qite/tide-booking-component 1.4.33 → 1.4.35

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 (64) hide show
  1. package/build/build-cjs/index.js +1674 -416
  2. package/build/build-cjs/qsm/types.d.ts +0 -1
  3. package/build/build-cjs/search-results/components/filters/filters.d.ts +2 -2
  4. package/build/build-cjs/search-results/components/flight/flight-results.d.ts +2 -0
  5. package/build/build-cjs/search-results/components/hotel/hotel-card.d.ts +1 -0
  6. package/build/build-cjs/search-results/components/itinerary/index.d.ts +2 -2
  7. package/build/build-cjs/search-results/store/search-results-slice.d.ts +11 -2
  8. package/build/build-cjs/search-results/types.d.ts +0 -14
  9. package/build/build-esm/index.js +1674 -416
  10. package/build/build-esm/qsm/types.d.ts +0 -1
  11. package/build/build-esm/search-results/components/filters/filters.d.ts +2 -2
  12. package/build/build-esm/search-results/components/flight/flight-results.d.ts +2 -0
  13. package/build/build-esm/search-results/components/hotel/hotel-card.d.ts +1 -0
  14. package/build/build-esm/search-results/components/itinerary/index.d.ts +2 -2
  15. package/build/build-esm/search-results/store/search-results-slice.d.ts +11 -2
  16. package/build/build-esm/search-results/types.d.ts +0 -14
  17. package/package.json +77 -77
  18. package/src/booking-product/components/product.tsx +26 -22
  19. package/src/booking-wizard/features/booking/booking-self-contained.tsx +303 -304
  20. package/src/booking-wizard/features/travelers-form/controls/gender-control.tsx +5 -5
  21. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +10 -10
  22. package/src/content/components/icon.tsx +1 -1
  23. package/src/content/features/content-page/content-page-self-contained.tsx +0 -1
  24. package/src/qsm/components/QSMContainer/qsm-container.tsx +217 -218
  25. package/src/qsm/components/mobile-filter-modal/index.tsx +12 -10
  26. package/src/qsm/components/travel-class-picker/index.tsx +5 -3
  27. package/src/qsm/components/travel-input/index.tsx +15 -12
  28. package/src/qsm/components/travel-input-group/index.tsx +14 -3
  29. package/src/qsm/components/travel-nationality-picker/index.tsx +5 -3
  30. package/src/qsm/components/travel-type-picker/index.tsx +5 -3
  31. package/src/qsm/qsm-configuration-context.ts +0 -1
  32. package/src/qsm/store/qsm-slice.ts +261 -261
  33. package/src/qsm/types.ts +144 -145
  34. package/src/search-results/components/filters/filters.tsx +15 -16
  35. package/src/search-results/components/flight/flight-results.tsx +168 -1099
  36. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +21 -24
  37. package/src/search-results/components/hotel/hotel-card.tsx +4 -3
  38. package/src/search-results/components/icon.tsx +1 -1
  39. package/src/search-results/components/itinerary/index.tsx +229 -129
  40. package/src/search-results/components/round-trip/round-trip-results.tsx +1 -1
  41. package/src/search-results/components/search-results-container/search-results-container.tsx +353 -337
  42. package/src/search-results/components/spinner/spinner.tsx +3 -1
  43. package/src/search-results/components/tab-views/index.tsx +13 -7
  44. package/src/search-results/features/flights/flight-search-results-self-contained.tsx +1 -14
  45. package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +1 -14
  46. package/src/search-results/store/search-results-slice.ts +37 -3
  47. package/src/search-results/types.ts +0 -15
  48. package/src/shared/translations/ar-SA.json +70 -0
  49. package/src/shared/translations/da-DK.json +70 -0
  50. package/src/shared/translations/de-DE.json +70 -0
  51. package/src/shared/translations/en-GB.json +71 -1
  52. package/src/shared/translations/es-ES.json +70 -0
  53. package/src/shared/translations/fr-BE.json +71 -1
  54. package/src/shared/translations/fr-FR.json +70 -0
  55. package/src/shared/translations/is-IS.json +72 -2
  56. package/src/shared/translations/it-IT.json +70 -0
  57. package/src/shared/translations/ja-JP.json +72 -2
  58. package/src/shared/translations/nl-BE.json +70 -0
  59. package/src/shared/translations/nl-NL.json +70 -0
  60. package/src/shared/translations/no-NO.json +72 -2
  61. package/src/shared/translations/pl-PL.json +70 -0
  62. package/src/shared/translations/pt-PT.json +70 -0
  63. package/src/shared/translations/sv-SE.json +72 -2
  64. package/styles/components/_search.scss +7 -1
@@ -1,337 +1,353 @@
1
- import React, { useContext, useEffect, useState } from 'react';
2
- import { useDispatch, useSelector } from 'react-redux';
3
- import { SearchResultsRootState } from '../../store/search-results-store';
4
- import SearchResultsConfigurationContext from '../../search-results-configuration-context';
5
-
6
- import { resetFilters, setSortKey, setResults, setIsLoading, setSelectedHotel } from '../../store/search-results-slice';
7
- import { Filter, SortingOption } from '../../types';
8
- import useMediaQuery from '../../../shared/utils/use-media-query-util';
9
- import Filters from '../filters/filters';
10
- import ItemPicker from '../item-picker';
11
-
12
- import { TideClientConfig, details, search } from '@qite/tide-client';
13
- import {
14
- BookingPackageDestination,
15
- BookingPackageDetailsRequest,
16
- BookingPackagePax,
17
- BookingPackageRequest,
18
- BookingPackageRequestRoom,
19
- BookingPackageSearchRequest
20
- } from '@qite/tide-client/build/types';
21
- import { getDateFromParams, getNumberFromParams, getRoomsFromParams } from '../../../shared/utils/query-string-util';
22
- import { range } from 'lodash';
23
- import { Room } from '../../../booking-wizard/types';
24
- import Icon from '../icon';
25
- import Itinerary from '../itinerary';
26
- import TabViews from '../tab-views';
27
- import HotelAccommodationResults from '../hotel/hotel-accommodation-results';
28
- import RoundTripResults from '../round-trip/round-trip-results';
29
- import { enrichFiltersWithResults } from '../filters/utility';
30
-
31
- const SearchResultsContainer: React.FC = () => {
32
- const isMobile = useMediaQuery('(max-width: 1200px)');
33
- const dispatch = useDispatch();
34
- const context = useContext(SearchResultsConfigurationContext);
35
- const { results, isLoading, filters, sortKey, selectedHotelId } = useSelector((state: SearchResultsRootState) => state.searchResults);
36
-
37
- const [searchTrigger, setSearchTrigger] = useState(0);
38
- const [initialFiltersSet, setInitialFiltersSet] = useState(false);
39
- const [initialFilters, setInitialFilters] = useState<Filter[]>([]);
40
-
41
- useEffect(() => {
42
- const runSearch = async () => {
43
- dispatch(setIsLoading(true));
44
- try {
45
- if (!context) {
46
- return;
47
- }
48
-
49
- const params = new URLSearchParams(location.search);
50
- let from = getDateFromParams(params, 'fromDate');
51
- let to = getDateFromParams(params, 'toDate');
52
- const rooms = getRoomsFromParams(params, 'rooms');
53
- let country = getNumberFromParams(params, 'country');
54
- let region = getNumberFromParams(params, 'region');
55
- let oord = getNumberFromParams(params, 'oord');
56
- let city = getNumberFromParams(params, 'location');
57
-
58
- // temp hardcoded params
59
- if (!from || !to) {
60
- from = '2026-04-07';
61
- to = '2026-04-13';
62
- }
63
- if (!country && !region && !oord && !city) {
64
- region = 1;
65
- }
66
-
67
- if (typeof window !== 'undefined') {
68
- window.scrollTo(0, 0);
69
- }
70
-
71
- let destinationId: number | null = null;
72
- let destinationIsCountry = false;
73
- let destinationIsRegion = false;
74
- let destinationIsOord = false;
75
- let destinationIsLocation = false;
76
-
77
- if (country) {
78
- destinationId = country;
79
- destinationIsCountry = true;
80
- } else if (region) {
81
- destinationId = region;
82
- destinationIsRegion = true;
83
- } else if (oord) {
84
- destinationId = oord;
85
- destinationIsOord = true;
86
- } else if (city) {
87
- destinationId = city;
88
- destinationIsLocation = true;
89
- }
90
-
91
- const searchRequest: BookingPackageRequest<BookingPackageSearchRequest> = {
92
- officeId: 1,
93
- payload: {
94
- catalogueIds: context.tideConnection.catalogueIds ?? [],
95
- serviceType:
96
- context?.type === 'hotel' || context?.type === 'hotel-flight' ? 3 : context?.type === 'flight' ? 7 : context?.type === 'roundTrip' ? 1 : 0,
97
- searchType: 0,
98
- destination: {
99
- id: Number(destinationId),
100
- isCountry: destinationIsCountry,
101
- isRegion: destinationIsRegion,
102
- isOord: destinationIsOord,
103
- isLocation: destinationIsLocation
104
- } as BookingPackageDestination,
105
- rooms: getRequestRooms(rooms),
106
- fromDate: from,
107
- toDate: to,
108
- earliestFromOffset: 0,
109
- latestToOffset: 0,
110
- includeFlights: true,
111
- regimeCodes:
112
- filters
113
- .find((f) => f.property === 'regime')
114
- ?.options?.filter((o) => o.isChecked)
115
- .flatMap((o) => o.value.toString()) || [],
116
- minPrice: filters.find((f) => f.property === 'price')?.selectedMin,
117
- maxPrice: filters.find((f) => f.property === 'price')?.selectedMax,
118
- useExactDates: true,
119
- onlyCachedResults: false,
120
- includeAllAllotments: true
121
- }
122
- };
123
-
124
- const config: TideClientConfig = {
125
- host: context.tideConnection.host,
126
- apiKey: context.tideConnection.apiKey
127
- };
128
-
129
- const packageSearchResults = await search(config, searchRequest);
130
-
131
- console.log('Search results', packageSearchResults);
132
-
133
- const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters);
134
- if (!initialFiltersSet) {
135
- dispatch(resetFilters(enrichedFilters));
136
- setInitialFilters(enrichedFilters);
137
- setInitialFiltersSet(true);
138
- }
139
-
140
- dispatch(setResults({ results: packageSearchResults }));
141
- if (packageSearchResults?.length > 0) {
142
- dispatch(setSelectedHotel(packageSearchResults[0].productId));
143
- }
144
- dispatch(setIsLoading(false));
145
- } catch (err) {
146
- console.error('Search failed', err);
147
- dispatch(setIsLoading(false));
148
- }
149
- };
150
-
151
- runSearch();
152
- }, [location.search, searchTrigger]);
153
-
154
- useEffect(() => {
155
- const fetchPackageDetails = async () => {
156
- if (!selectedHotelId || !context) return;
157
-
158
- try {
159
- const config: TideClientConfig = {
160
- host: context.tideConnection.host,
161
- apiKey: context.tideConnection.apiKey
162
- };
163
-
164
- const selectedItem = results.find((r) => r.productId === selectedHotelId);
165
- if (!selectedItem) return;
166
-
167
- const params = new URLSearchParams(location.search);
168
- const rooms = getRoomsFromParams(params, 'rooms');
169
- const requestRooms = getRequestRooms(rooms);
170
-
171
- const detailsRequest: BookingPackageRequest<BookingPackageDetailsRequest> = {
172
- officeId: 1,
173
- payload: {
174
- catalogueId: selectedItem.catalogueId,
175
- rooms: requestRooms,
176
- searchType: 0, // same as search
177
- productCode: selectedItem.code,
178
- fromDate: selectedItem.fromDate,
179
- toDate: selectedItem.toDate,
180
- includeFlights: true,
181
- includeHotels: true,
182
- includePaxTypes: true,
183
- checkExternalAvailability: true,
184
- expectedPrice: selectedItem.price,
185
- duration: null,
186
- preNights: null,
187
- postNights: null
188
- }
189
- };
190
-
191
- const detailsResponse = await details(config, detailsRequest);
192
-
193
- console.log('Package details:', detailsResponse);
194
-
195
- // TODO: store flights / details in redux
196
- // dispatch(setSelectedHotelDetails(details));
197
- } catch (err) {
198
- console.error('Failed to fetch package details', err);
199
- }
200
- };
201
-
202
- fetchPackageDetails();
203
- }, [selectedHotelId]);
204
-
205
- const getRequestRooms = (rooms: Room[] | null) => {
206
- if (!rooms) {
207
- // Fall back to 2 adults
208
- var room = { index: 0, pax: [] } as BookingPackageRequestRoom;
209
- range(0, 2).forEach(() => {
210
- room.pax.push({
211
- age: 30
212
- } as BookingPackagePax);
213
- });
214
- return [room];
215
- }
216
-
217
- const requestRooms = rooms?.map((x, i) => {
218
- var room = { index: i, pax: [] } as BookingPackageRequestRoom;
219
- range(0, x.adults).forEach(() => {
220
- room.pax.push({
221
- age: 30
222
- } as BookingPackagePax);
223
- });
224
- x.childAges.forEach((x) => {
225
- room.pax.push({
226
- age: x
227
- } as BookingPackagePax);
228
- });
229
- return room;
230
- });
231
-
232
- return requestRooms;
233
- };
234
-
235
- const [isMobileFiltersOpen, setIsMobileFiltersOpen] = useState(false);
236
-
237
- const handleSortChange = (newSortKey: string) => {
238
- dispatch(setSortKey(newSortKey));
239
- };
240
-
241
- const handleSetIsMobileFiltersOpen = () => {
242
- setIsMobileFiltersOpen(!isMobileFiltersOpen);
243
- };
244
-
245
- useEffect(() => {
246
- if (typeof document !== 'undefined') {
247
- document.body.classList.toggle('has-overlay', isMobileFiltersOpen);
248
- }
249
- }, [isMobileFiltersOpen]);
250
-
251
- const sortingOptions: SortingOption[] = [
252
- { key: 'price-asc', label: 'Price: Low to High' },
253
- { key: 'price-desc', label: 'Price: High to Low' },
254
- { key: 'departure-date', label: 'Departure Date' }
255
- ];
256
-
257
- return (
258
- <div id="tide-booking" className="search__bg">
259
- {context && (
260
- <div className="search">
261
- <div className="search__container">
262
- {context.showFilters && (
263
- <Filters
264
- filters={filters}
265
- isMobileFiltersOpen={isMobileFiltersOpen}
266
- handleSetIsMobileFiltersOpen={handleSetIsMobileFiltersOpen}
267
- handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
268
- isLoading={isLoading}
269
- />
270
- )}
271
- {context.type === 'hotel-flight' && (
272
- <Itinerary isMobileFiltersOpen={isMobileFiltersOpen} handleSetIsMobileFiltersOpen={handleSetIsMobileFiltersOpen} isLoading={isLoading} />
273
- )}
274
- {/* ---------------- Results ---------------- */}
275
- <div className="search__results">
276
- {isMobile && (
277
- <div className="search__result-row">
278
- <div className="cta cta--filter" onClick={() => setIsMobileFiltersOpen(true)}>
279
- <Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
280
- {context.translations?.filters}
281
- </div>
282
- {sortingOptions && sortingOptions.length > 0 && (
283
- <ItemPicker
284
- items={sortingOptions}
285
- selection={sortKey || undefined}
286
- label="Sort by"
287
- placeholder="Sort by"
288
- classModifier="travel-class-picker__items"
289
- onPick={handleSortChange}
290
- />
291
- )}
292
- </div>
293
- )}
294
-
295
- <div className="search__result-row">
296
- <span className="search__result-row-text">
297
- {!isLoading && (
298
- <>
299
- {results?.length ?? 4} {context.translations?.totalResultsLabel}
300
- </>
301
- )}
302
- </span>
303
- {!isMobile && sortingOptions && sortingOptions.length > 0 && (
304
- <div className="search__result-row-filter">
305
- <ItemPicker
306
- items={sortingOptions}
307
- selection={sortKey || undefined}
308
- label="Sort by"
309
- placeholder="Sort by"
310
- classModifier="travel-class-picker__items"
311
- onPick={handleSortChange}
312
- />
313
- </div>
314
- )}
315
- </div>
316
-
317
- <div className="search__results__wrapper">
318
- {context.showTabViews && <TabViews />}
319
-
320
- {context.showRoundTripResults && <RoundTripResults />}
321
-
322
- {/* {context.showFlightResults && <FlightResults isDeparture={true} />} */}
323
-
324
- {context.showHotelAccommodationResults && <HotelAccommodationResults isLoading={isLoading} context={context} />}
325
- {/* {context.showFlightAccommodationResults && <FlightAccommodationResults />} */}
326
-
327
- {/* {context.showFlightResults && <FlightResults isDeparture={false} />} */}
328
- </div>
329
- </div>
330
- </div>
331
- </div>
332
- )}
333
- </div>
334
- );
335
- };
336
-
337
- export default SearchResultsContainer;
1
+ import React, { useContext, useEffect, useState } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { SearchResultsRootState } from '../../store/search-results-store';
4
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
5
+
6
+ import { resetFilters, setSortKey, setResults, setIsLoading, setSelectedHotel, setBookingPackageDetails, setEntry } from '../../store/search-results-slice';
7
+ import { Filter, SortingOption } from '../../types';
8
+ import useMediaQuery from '../../../shared/utils/use-media-query-util';
9
+ import Filters from '../filters/filters';
10
+ import ItemPicker from '../item-picker';
11
+
12
+ import { TideClientConfig, detailsWL, search } from '@qite/tide-client';
13
+ import {
14
+ BookingPackageDestination,
15
+ BookingPackageDetailsRequest,
16
+ BookingPackagePax,
17
+ BookingPackageRequest,
18
+ BookingPackageRequestRoom,
19
+ BookingPackageSearchRequest
20
+ } from '@qite/tide-client/build/types';
21
+ import { getDateFromParams, getNumberFromParams, getRoomsFromParams } from '../../../shared/utils/query-string-util';
22
+ import { range } from 'lodash';
23
+ import { Room } from '../../../booking-wizard/types';
24
+ import Icon from '../icon';
25
+ import Itinerary from '../itinerary';
26
+ import TabViews from '../tab-views';
27
+ import HotelAccommodationResults from '../hotel/hotel-accommodation-results';
28
+ import RoundTripResults from '../round-trip/round-trip-results';
29
+ import { enrichFiltersWithResults } from '../filters/utility';
30
+ import FlightResults from '../flight/flight-results';
31
+ import { getTranslations } from '../../../shared/utils/localization-util';
32
+
33
+ const SearchResultsContainer: React.FC = () => {
34
+ const isMobile = useMediaQuery('(max-width: 1200px)');
35
+ const dispatch = useDispatch();
36
+ const context = useContext(SearchResultsConfigurationContext);
37
+ const translations = getTranslations(context?.languageCode ?? 'en-GB');
38
+ const { results, bookingPackageDetails, isLoading, filters, sortKey, selectedHotelId } = useSelector((state: SearchResultsRootState) => state.searchResults);
39
+
40
+ const [searchTrigger, setSearchTrigger] = useState(0);
41
+ const [initialFiltersSet, setInitialFiltersSet] = useState(false);
42
+ const [initialFilters, setInitialFilters] = useState<Filter[]>([]);
43
+
44
+ // seperate Search
45
+ useEffect(() => {
46
+ const runSearch = async () => {
47
+ dispatch(setIsLoading(true));
48
+ try {
49
+ if (!context) {
50
+ return;
51
+ }
52
+
53
+ const params = new URLSearchParams(location.search);
54
+ let from = getDateFromParams(params, 'fromDate');
55
+ let to = getDateFromParams(params, 'toDate');
56
+ const rooms = getRoomsFromParams(params, 'rooms');
57
+ let country = getNumberFromParams(params, 'country');
58
+ let region = getNumberFromParams(params, 'region');
59
+ let oord = getNumberFromParams(params, 'oord');
60
+ let city = getNumberFromParams(params, 'location');
61
+ let hotel = getNumberFromParams(params, 'hotel');
62
+ let tagId = getNumberFromParams(params, 'tagId');
63
+
64
+ // temp hardcoded params
65
+ if (!from || !to) {
66
+ from = '2026-04-07';
67
+ to = '2026-04-13';
68
+ }
69
+ if (!country && !region && !oord && !city) {
70
+ region = 1;
71
+ }
72
+
73
+ if (typeof window !== 'undefined') {
74
+ window.scrollTo(0, 0);
75
+ }
76
+
77
+ let destinationId: number | null = null;
78
+ let destinationIsCountry = false;
79
+ let destinationIsRegion = false;
80
+ let destinationIsOord = false;
81
+ let destinationIsLocation = false;
82
+
83
+ if (country) {
84
+ destinationId = country;
85
+ destinationIsCountry = true;
86
+ } else if (region) {
87
+ destinationId = region;
88
+ destinationIsRegion = true;
89
+ } else if (oord) {
90
+ destinationId = oord;
91
+ destinationIsOord = true;
92
+ } else if (city) {
93
+ destinationId = city;
94
+ destinationIsLocation = true;
95
+ }
96
+
97
+ const searchRequest: BookingPackageRequest<BookingPackageSearchRequest> = {
98
+ officeId: 1,
99
+ payload: {
100
+ catalogueIds: context.tideConnection.catalogueIds ?? [],
101
+ serviceType:
102
+ context?.type === 'hotel' || context?.type === 'hotel-flight' ? 3 : context?.type === 'flight' ? 7 : context?.type === 'roundTrip' ? 1 : 0,
103
+ searchType: 0,
104
+ destination: {
105
+ id: Number(destinationId),
106
+ isCountry: destinationIsCountry,
107
+ isRegion: destinationIsRegion,
108
+ isOord: destinationIsOord,
109
+ isLocation: destinationIsLocation
110
+ } as BookingPackageDestination,
111
+ rooms: getRequestRooms(rooms),
112
+ fromDate: from,
113
+ toDate: to,
114
+ earliestFromOffset: 0,
115
+ latestToOffset: 0,
116
+ includeFlights: true,
117
+ regimeCodes:
118
+ filters
119
+ .find((f) => f.property === 'regime')
120
+ ?.options?.filter((o) => o.isChecked)
121
+ .flatMap((o) => o.value.toString()) || [],
122
+ minPrice: filters.find((f) => f.property === 'price')?.selectedMin,
123
+ maxPrice: filters.find((f) => f.property === 'price')?.selectedMax,
124
+ useExactDates: true,
125
+ onlyCachedResults: false,
126
+ includeAllAllotments: true,
127
+ productIds: hotel ? [hotel] : [],
128
+ productTagIds: tagId ? [tagId] : []
129
+ }
130
+ };
131
+
132
+ const config: TideClientConfig = {
133
+ host: context.tideConnection.host,
134
+ apiKey: context.tideConnection.apiKey
135
+ };
136
+
137
+ const packageSearchResults = await search(config, searchRequest);
138
+
139
+ console.log('Search results', packageSearchResults);
140
+
141
+ const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters);
142
+ if (!initialFiltersSet) {
143
+ dispatch(resetFilters(enrichedFilters));
144
+ setInitialFilters(enrichedFilters);
145
+ setInitialFiltersSet(true);
146
+ }
147
+
148
+ dispatch(setResults({ results: packageSearchResults }));
149
+ if (packageSearchResults?.length > 0) {
150
+ dispatch(setSelectedHotel(packageSearchResults[0].productId));
151
+ }
152
+ dispatch(setIsLoading(false));
153
+ } catch (err) {
154
+ console.error('Search failed', err);
155
+ dispatch(setIsLoading(false));
156
+ }
157
+ };
158
+
159
+ runSearch();
160
+ }, [location.search, searchTrigger]);
161
+
162
+ // Seperate detailsCall
163
+ useEffect(() => {
164
+ const fetchPackageDetails = async () => {
165
+ if (!selectedHotelId || !context) return;
166
+
167
+ try {
168
+ const config: TideClientConfig = {
169
+ host: context.tideConnection.host,
170
+ apiKey: context.tideConnection.apiKey
171
+ };
172
+
173
+ const selectedItem = results.find((r) => r.productId === selectedHotelId);
174
+ if (!selectedItem) return;
175
+
176
+ const params = new URLSearchParams(location.search);
177
+ const rooms = getRoomsFromParams(params, 'rooms');
178
+ const requestRooms = getRequestRooms(rooms);
179
+
180
+ const detailsRequest: BookingPackageRequest<BookingPackageDetailsRequest> = {
181
+ officeId: 1,
182
+ payload: {
183
+ catalogueId: selectedItem.catalogueId,
184
+ rooms: requestRooms,
185
+ searchType: 0, // same as search
186
+ productCode: selectedItem.code,
187
+ fromDate: selectedItem.fromDate,
188
+ toDate: selectedItem.toDate,
189
+ includeFlights: true,
190
+ includeHotels: true,
191
+ includePaxTypes: true,
192
+ checkExternalAvailability: true,
193
+ expectedPrice: selectedItem.price,
194
+ duration: null,
195
+ preNights: null,
196
+ postNights: null
197
+ }
198
+ };
199
+
200
+ const detailsWLResponse = await detailsWL(config, detailsRequest);
201
+ console.log('Details with entryLight:', detailsWLResponse);
202
+ dispatch(setBookingPackageDetails({ details: detailsWLResponse?.payload?.bookingPackage }));
203
+ dispatch(setEntry({ entry: detailsWLResponse?.payload?.entry }));
204
+ } catch (err) {
205
+ console.error('Failed to fetch package details', err);
206
+ }
207
+ };
208
+
209
+ fetchPackageDetails();
210
+ }, [selectedHotelId]);
211
+
212
+ const getRequestRooms = (rooms: Room[] | null) => {
213
+ if (!rooms) {
214
+ // Fall back to 2 adults
215
+ var room = { index: 0, pax: [] } as BookingPackageRequestRoom;
216
+ range(0, 2).forEach(() => {
217
+ room.pax.push({
218
+ age: 30
219
+ } as BookingPackagePax);
220
+ });
221
+ return [room];
222
+ }
223
+
224
+ const requestRooms = rooms?.map((x, i) => {
225
+ var room = { index: i, pax: [] } as BookingPackageRequestRoom;
226
+ range(0, x.adults).forEach(() => {
227
+ room.pax.push({
228
+ age: 30
229
+ } as BookingPackagePax);
230
+ });
231
+ x.childAges.forEach((x) => {
232
+ room.pax.push({
233
+ age: x
234
+ } as BookingPackagePax);
235
+ });
236
+ return room;
237
+ });
238
+
239
+ return requestRooms;
240
+ };
241
+
242
+ const [filtersOpen, setFiltersOpen] = useState(false);
243
+ const [itineraryOpen, setItineraryOpen] = useState(false);
244
+
245
+ const handleSortChange = (newSortKey: string) => {
246
+ dispatch(setSortKey(newSortKey));
247
+ };
248
+
249
+ useEffect(() => {
250
+ if (typeof document !== 'undefined') {
251
+ document.body.classList.toggle('has-overlay', filtersOpen);
252
+ }
253
+ }, [filtersOpen]);
254
+
255
+ const sortingOptions: SortingOption[] = [
256
+ { key: 'price-asc', label: translations.SRP.PRICE_ASC },
257
+ { key: 'price-desc', label: translations.SRP.PRICE_DESC },
258
+ { key: 'departure-date', label: translations.SRP.DEPARTURE_ASC }
259
+ ];
260
+
261
+ return (
262
+ <div id="tide-booking" className="search__bg">
263
+ {context && (
264
+ <div className="search">
265
+ <div className="search__container">
266
+ {context.showFilters && (
267
+ <Filters
268
+ filters={filters}
269
+ isOpen={filtersOpen}
270
+ handleSetIsOpen={() => setFiltersOpen(!filtersOpen)}
271
+ handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
272
+ isLoading={isLoading}
273
+ />
274
+ )}
275
+ {context.type === 'hotel-flight' && (
276
+ <Itinerary isOpen={itineraryOpen} handleSetIsOpen={() => setItineraryOpen(!itineraryOpen)} isLoading={isLoading} />
277
+ )}
278
+ {/* ---------------- Results ---------------- */}
279
+ <div className="search__results">
280
+ {isMobile && (
281
+ <div className="search__result-row">
282
+ <div className="search__results__actions">
283
+ {context.showFilters && (
284
+ <div className="cta cta--filter" onClick={() => setFiltersOpen(true)}>
285
+ <Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
286
+ {translations.SRP.FILTERS}
287
+ </div>
288
+ )}
289
+ <div className="cta cta--filter" onClick={() => setItineraryOpen(true)}>
290
+ <Icon name="ui-calendar" className="mobile-filters-button__icon" height={16} />
291
+ {translations.SRP.SHOW_ITINERARY}
292
+ </div>
293
+ </div>
294
+ {sortingOptions && sortingOptions.length > 0 && (
295
+ <ItemPicker
296
+ items={sortingOptions}
297
+ selection={sortKey || undefined}
298
+ label={translations.SRP.SORTBY}
299
+ placeholder={translations.SRP.SORTBY}
300
+ classModifier="travel-class-picker__items"
301
+ onPick={handleSortChange}
302
+ />
303
+ )}
304
+ </div>
305
+ )}
306
+
307
+ <div className="search__result-row">
308
+ <span className="search__result-row-text">
309
+ {!isLoading && (
310
+ <>
311
+ {results?.length ?? 4} {translations.SRP.TOTAL_RESULTS_LABEL}
312
+ </>
313
+ )}
314
+ </span>
315
+ {!isMobile && sortingOptions && sortingOptions.length > 0 && (
316
+ <div className="search__result-row-filter">
317
+ <ItemPicker
318
+ items={sortingOptions}
319
+ selection={sortKey || undefined}
320
+ label={translations.SRP.SORTBY}
321
+ placeholder={translations.SRP.SORTBY}
322
+ classModifier="travel-class-picker__items"
323
+ onPick={handleSortChange}
324
+ />
325
+ </div>
326
+ )}
327
+ </div>
328
+
329
+ <div className="search__results__wrapper">
330
+ {context.showTabViews && <TabViews />}
331
+
332
+ {/* {context.showRoundTripResults && <RoundTripResults />} */}
333
+
334
+ {context.showFlightResults && bookingPackageDetails?.outwardFlights && (
335
+ <FlightResults flights={bookingPackageDetails.outwardFlights} isDeparture={true} />
336
+ )}
337
+
338
+ {context.showHotelAccommodationResults && <HotelAccommodationResults isLoading={isLoading} context={context} />}
339
+ {/* {context.showFlightAccommodationResults && <FlightAccommodationResults />} */}
340
+
341
+ {context.showFlightResults && bookingPackageDetails?.returnFlights && (
342
+ <FlightResults flights={bookingPackageDetails.returnFlights} isDeparture={false} />
343
+ )}
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ )}
349
+ </div>
350
+ );
351
+ };
352
+
353
+ export default SearchResultsContainer;