@qite/tide-booking-component 1.4.38 → 1.4.40

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 (99) hide show
  1. package/build/build-cjs/booking-wizard/types.d.ts +1 -0
  2. package/build/build-cjs/content/components/login.d.ts +3 -0
  3. package/build/build-cjs/index.js +7618 -1952
  4. package/build/build-cjs/qsm/components/double-search-input-group/index.d.ts +2 -1
  5. package/build/build-cjs/qsm/components/search-input/index.d.ts +1 -0
  6. package/build/build-cjs/qsm/components/search-input-group/index.d.ts +3 -1
  7. package/build/build-cjs/qsm/store/qsm-slice.d.ts +6 -2
  8. package/build/build-cjs/qsm/types.d.ts +17 -32
  9. package/build/build-cjs/search-results/components/filters/flight-filters.d.ts +8 -0
  10. package/build/build-cjs/search-results/components/flight/flight-accommodation-results.d.ts +4 -1
  11. package/build/build-cjs/search-results/components/flight/flight-option.d.ts +7 -0
  12. package/build/build-cjs/search-results/components/flight/flight-search-context/index.d.ts +36 -0
  13. package/build/build-cjs/search-results/components/icon.d.ts +1 -0
  14. package/build/build-cjs/search-results/components/item-picker/index.d.ts +5 -3
  15. package/build/build-cjs/search-results/components/search-results-container/flight-search-results.d.ts +6 -0
  16. package/build/build-cjs/search-results/store/search-results-slice.d.ts +2 -0
  17. package/build/build-cjs/search-results/types.d.ts +31 -1
  18. package/build/build-cjs/search-results/utils/flight-utils.d.ts +16 -0
  19. package/build/build-cjs/shared/components/flyin.d.ts +9 -0
  20. package/build/build-cjs/shared/types.d.ts +6 -0
  21. package/build/build-cjs/shared/utils/localization-util.d.ts +91 -0
  22. package/build/build-esm/booking-wizard/types.d.ts +1 -0
  23. package/build/build-esm/content/components/login.d.ts +3 -0
  24. package/build/build-esm/index.js +8053 -2356
  25. package/build/build-esm/qsm/components/double-search-input-group/index.d.ts +2 -1
  26. package/build/build-esm/qsm/components/search-input/index.d.ts +1 -0
  27. package/build/build-esm/qsm/components/search-input-group/index.d.ts +3 -1
  28. package/build/build-esm/qsm/store/qsm-slice.d.ts +6 -2
  29. package/build/build-esm/qsm/types.d.ts +17 -32
  30. package/build/build-esm/search-results/components/filters/flight-filters.d.ts +8 -0
  31. package/build/build-esm/search-results/components/flight/flight-accommodation-results.d.ts +4 -1
  32. package/build/build-esm/search-results/components/flight/flight-option.d.ts +7 -0
  33. package/build/build-esm/search-results/components/flight/flight-search-context/index.d.ts +36 -0
  34. package/build/build-esm/search-results/components/icon.d.ts +1 -0
  35. package/build/build-esm/search-results/components/item-picker/index.d.ts +5 -3
  36. package/build/build-esm/search-results/components/search-results-container/flight-search-results.d.ts +6 -0
  37. package/build/build-esm/search-results/store/search-results-slice.d.ts +2 -0
  38. package/build/build-esm/search-results/types.d.ts +31 -1
  39. package/build/build-esm/search-results/utils/flight-utils.d.ts +16 -0
  40. package/build/build-esm/shared/components/flyin.d.ts +9 -0
  41. package/build/build-esm/shared/types.d.ts +6 -0
  42. package/build/build-esm/shared/utils/localization-util.d.ts +91 -0
  43. package/package.json +4 -3
  44. package/rollup.config.js +2 -2
  45. package/src/booking-product/components/dates.tsx +1 -1
  46. package/src/booking-wizard/features/booking/booking-slice.ts +4 -2
  47. package/src/booking-wizard/types.ts +1 -0
  48. package/src/content/components/login.tsx +162 -0
  49. package/src/content/components/slider.tsx +1 -1
  50. package/src/content/features/content-page/content-page-self-contained.tsx +56 -75
  51. package/src/qsm/components/QSMContainer/qsm-container.tsx +197 -75
  52. package/src/qsm/components/double-search-input-group/index.tsx +14 -75
  53. package/src/qsm/components/mobile-filter-modal/index.tsx +18 -11
  54. package/src/qsm/components/search-input/index.tsx +9 -2
  55. package/src/qsm/components/search-input-group/index.tsx +19 -31
  56. package/src/qsm/components/travel-class-picker/index.tsx +1 -0
  57. package/src/qsm/components/travel-input/index.tsx +4 -4
  58. package/src/qsm/components/travel-input-group/index.tsx +4 -3
  59. package/src/qsm/components/travel-nationality-picker/index.tsx +1 -0
  60. package/src/qsm/components/travel-type-picker/index.tsx +1 -0
  61. package/src/qsm/qsm-configuration-context.ts +6 -17
  62. package/src/qsm/store/qsm-slice.ts +13 -1
  63. package/src/qsm/types.ts +19 -39
  64. package/src/search-results/components/filters/flight-filters.tsx +671 -0
  65. package/src/search-results/components/flight/flight-accommodation-results.tsx +20 -562
  66. package/src/search-results/components/flight/flight-banner.tsx +1 -1
  67. package/src/search-results/components/flight/flight-option.tsx +243 -0
  68. package/src/search-results/components/flight/flight-search-context/index.tsx +508 -0
  69. package/src/search-results/components/hotel/hotel-card.tsx +0 -1
  70. package/src/search-results/components/icon.tsx +84 -44
  71. package/src/search-results/components/item-picker/index.tsx +16 -11
  72. package/src/search-results/components/search-results-container/flight-search-results.tsx +120 -0
  73. package/src/search-results/components/search-results-container/search-results-container.tsx +85 -70
  74. package/src/search-results/store/search-results-slice.ts +6 -0
  75. package/src/search-results/types.ts +37 -1
  76. package/src/search-results/utils/flight-utils.ts +106 -0
  77. package/src/shared/components/flyin.tsx +334 -0
  78. package/src/shared/translations/ar-SA.json +13 -1
  79. package/src/shared/translations/da-DK.json +13 -1
  80. package/src/shared/translations/de-DE.json +13 -1
  81. package/src/shared/translations/en-GB.json +13 -1
  82. package/src/shared/translations/es-ES.json +13 -1
  83. package/src/shared/translations/fr-BE.json +13 -1
  84. package/src/shared/translations/fr-FR.json +13 -1
  85. package/src/shared/translations/is-IS.json +13 -1
  86. package/src/shared/translations/it-IT.json +13 -1
  87. package/src/shared/translations/ja-JP.json +13 -1
  88. package/src/shared/translations/nl-BE.json +13 -1
  89. package/src/shared/translations/nl-NL.json +13 -1
  90. package/src/shared/translations/no-NO.json +13 -1
  91. package/src/shared/translations/pl-PL.json +13 -1
  92. package/src/shared/translations/pt-PT.json +13 -1
  93. package/src/shared/translations/sv-SE.json +13 -1
  94. package/src/shared/types.ts +7 -0
  95. package/src/shared/utils/localization-util.ts +71 -0
  96. package/styles/booking-search-results.scss +1 -0
  97. package/styles/components/_flyin.scss +550 -0
  98. package/styles/components/_login.scss +133 -0
  99. package/styles/content-blocks.scss +1 -0
@@ -0,0 +1,508 @@
1
+ import React, { useContext, useEffect, useRef, useState } from 'react';
2
+ import { concat, first, groupBy, isEmpty, minBy, orderBy } from 'lodash';
3
+ import { ExtendedFlightSearchResponseItem, Filters, Sequence, SortByType } from '../../../types';
4
+ import { getArrivalSegment, getDepartureSegment, getNumberOfStops } from '../../../utils/flight-utils';
5
+ import { dateToDateStruct, durationInTicksInMinutes, rangeFromDateTimeInMinutes } from '../../../../shared/utils/localization-util';
6
+ import { FlightSearchFinishResponse, FlightSearchRequest, FlightSearchResponse } from '@qite/tide-client';
7
+ import { v4 as uuidv4 } from 'uuid';
8
+ import { getDateAsDateFromParams, getNumberFromParams, getStringFromParams } from '../../../../shared/utils/query-string-util';
9
+
10
+ interface FlightFilterContextProps {
11
+ children: React.ReactNode;
12
+ tideConnection: any;
13
+ }
14
+
15
+ interface FlightSearchContext {
16
+ // Search States
17
+ isHubReady: boolean;
18
+ currentRequestId: number;
19
+ valuesManuallyChanged: boolean;
20
+ flightsLoading: boolean;
21
+
22
+ // Search Results
23
+ flightSearchResults: ExtendedFlightSearchResponseItem[];
24
+ filteredResults: ExtendedFlightSearchResponseItem[];
25
+
26
+ // Filter
27
+ filters: Filters;
28
+ sortByTypes: SortByType[];
29
+ selectedSortByType: SortByType;
30
+
31
+ airlineResults: ExtendedFlightSearchResponseItem[];
32
+ numberOfStopsResults: ExtendedFlightSearchResponseItem[];
33
+ departureRangeResults: ExtendedFlightSearchResponseItem[];
34
+ departureAirportsResults: ExtendedFlightSearchResponseItem[];
35
+ arrivalAirportsResults: ExtendedFlightSearchResponseItem[];
36
+ travelTimeResults: ExtendedFlightSearchResponseItem[];
37
+ priceResults: ExtendedFlightSearchResponseItem[];
38
+
39
+ onFlightSearch: () => void;
40
+ setFlightSearchResults: (results: ExtendedFlightSearchResponseItem[]) => void;
41
+ // Filter Setters
42
+ setFilters: (filters: Filters) => void;
43
+ setFilteredResults: (results: ExtendedFlightSearchResponseItem[]) => void;
44
+ setSelectedSortByType: (val: SortByType) => void;
45
+ resetFilters: () => void;
46
+ setValuesManuallyChanged: (val: boolean) => void;
47
+ onFilter: () => void;
48
+ }
49
+
50
+ const initialState: FlightSearchContext = {
51
+ isHubReady: false,
52
+ currentRequestId: 0,
53
+ valuesManuallyChanged: false,
54
+ flightsLoading: false,
55
+ flightSearchResults: [],
56
+ filters: { airlines: [], numberOfStops: [], departureRanges: [], departureAirports: [], arrivalAirports: [], travelTimes: [], prices: [] } as Filters,
57
+ filteredResults: [],
58
+
59
+ sortByTypes: [
60
+ { direction: 'asc', label: 'price' } as SortByType,
61
+ { direction: 'desc', label: 'price' } as SortByType,
62
+ { direction: 'asc', label: 'departureTime' } as SortByType,
63
+ { direction: 'desc', label: 'departureTime' } as SortByType,
64
+ { direction: 'asc', label: 'durationInTicks' } as SortByType,
65
+ { direction: 'desc', label: 'durationInTicks' } as SortByType
66
+ ],
67
+ selectedSortByType: { direction: 'asc', label: 'price' } as SortByType,
68
+
69
+ airlineResults: [],
70
+ numberOfStopsResults: [],
71
+ departureRangeResults: [],
72
+ departureAirportsResults: [],
73
+ arrivalAirportsResults: [],
74
+ travelTimeResults: [],
75
+ priceResults: [],
76
+
77
+ onFlightSearch: () => {},
78
+ setFlightSearchResults: () => {},
79
+ setFilters: () => {},
80
+ setFilteredResults: () => {},
81
+ onFilter: () => {},
82
+ setSelectedSortByType: () => {},
83
+ setValuesManuallyChanged: () => {},
84
+ resetFilters: () => {}
85
+ };
86
+
87
+ const FlightSearchContext = React.createContext<FlightSearchContext>(initialState);
88
+
89
+ export const FlightSearchProvider: React.FC<FlightFilterContextProps> = ({ children, tideConnection }) => {
90
+ const [flightSearchResults, setFlightSearchResults] = useState<ExtendedFlightSearchResponseItem[]>(initialState.flightSearchResults);
91
+ const hubProxyRef = useRef<any>(null);
92
+ const [flightsLoading, setFlightsLoading] = useState<boolean>(false);
93
+ const [isHubReady, setIsHubReady] = useState<boolean>(false);
94
+ const [currentRequestId, setCurrentRequestId] = useState<number>(0);
95
+ const [sequenceIds, setSequenceIds] = useState<Sequence[]>([]);
96
+ const [lastSequenceId, setLastSequenceId] = useState<number | null>(null);
97
+
98
+ // FILTER States
99
+ const [filters, setFilters] = useState<Filters>(initialState.filters);
100
+ const [groupedResults, setGroupedResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
101
+ const [filteredResults, setFilteredResults] = useState<ExtendedFlightSearchResponseItem[]>(initialState.filteredResults);
102
+
103
+ const [valuesManuallyChanged, setValuesManuallyChanged] = useState<boolean>(initialState.valuesManuallyChanged);
104
+
105
+ const [airlineResults, setAirlineResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
106
+ const [numberOfStopsResults, setNumberOfStopsResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
107
+ const [departureRangeResults, setDepartureRangeResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
108
+ const [departureAirportsResults, setDepartureAirportsResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
109
+ const [arrivalAirportsResults, setArrivalAirportsResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
110
+ const [travelTimeResults, setTravelTimeResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
111
+ const [priceResults, setPriceResults] = useState<ExtendedFlightSearchResponseItem[]>([]);
112
+
113
+ const [sortByTypes] = useState<SortByType[]>(initialState.sortByTypes);
114
+ const [selectedSortByType, setSelectedSortByType] = useState<SortByType>(initialState.selectedSortByType);
115
+
116
+ // Connect to SignalR of Tide and set up listeners for results
117
+ useEffect(() => {
118
+ if (typeof window === 'undefined') return;
119
+ console.log('Setting up SignalR connection for flight search results');
120
+ // @ts-ignore: SignalR augments jQuery
121
+ import('signalr-no-jquery').then(({ hubConnection }) => {
122
+ const connection = hubConnection(tideConnection.host, { useDefaultPath: true });
123
+ const hubProxy = connection.createHubProxy('Search');
124
+ let currentId = 0;
125
+
126
+ hubProxy.on('FlightResponse', (response: FlightSearchResponse) => {
127
+ console.log('FlightResponse received:', response);
128
+ if (currentId === response.requestId) {
129
+ const itemsWithGuid = response.items.map((item) => ({ ...item, guid: uuidv4(), requestId: response.requestId } as ExtendedFlightSearchResponseItem));
130
+ setFlightSearchResults((prevResults) => {
131
+ const oldResults = prevResults.filter((s) => s.requestId === response.requestId);
132
+ return concat(oldResults, itemsWithGuid);
133
+ });
134
+ setSequenceIds((prevIds) => {
135
+ const oldIds = prevIds.filter((seq) => seq.requestId === response.requestId);
136
+ return concat(oldIds, { requestId: response.requestId, sequenceId: response.sequenceId } as Sequence);
137
+ });
138
+ }
139
+ });
140
+
141
+ hubProxy.on('FinishResponse', (response: FlightSearchFinishResponse) => {
142
+ console.log('FinishResponse received');
143
+ if (currentId == response.requestId) {
144
+ setLastSequenceId(response.sequenceId);
145
+ // Build filters based on the received results
146
+ }
147
+ });
148
+
149
+ hubProxy.on('SetRequestId', (requestId: number) => {
150
+ setCurrentRequestId(requestId);
151
+ currentId = requestId;
152
+ });
153
+
154
+ connection.start().then(() => {
155
+ hubProxyRef.current = hubProxy;
156
+ setTimeout(() => {
157
+ setIsHubReady(true);
158
+ }, 1000); // Delay to ensure hub is ready
159
+ });
160
+
161
+ return () => {
162
+ connection.stop();
163
+ };
164
+ });
165
+ }, []);
166
+
167
+ // check if all sequences are received
168
+ useEffect(() => {
169
+ if (lastSequenceId != null) {
170
+ let hasAll = true;
171
+ for (var i = 0; i < lastSequenceId + 1; i++) {
172
+ if (!sequenceIds.map((seq) => seq.sequenceId).includes(i)) {
173
+ hasAll = false;
174
+ break;
175
+ }
176
+ }
177
+
178
+ if (hasAll) {
179
+ if (flightsLoading) {
180
+ setFlightsLoading(false);
181
+ }
182
+ }
183
+ }
184
+ }, [lastSequenceId, sequenceIds]);
185
+
186
+ const onFlightSearch = () => {
187
+ const hubProxy = hubProxyRef.current;
188
+ if (!hubProxy) {
189
+ console.error('Hub proxy not ready yet');
190
+ return;
191
+ }
192
+
193
+ const params = new URLSearchParams(location.search);
194
+ let fromDate = getDateAsDateFromParams(params, 'fromDate');
195
+ let toDate = getDateAsDateFromParams(params, 'toDate');
196
+
197
+ let departureAirport = getStringFromParams(params, 'departureAirport');
198
+ let destinationAirport = getStringFromParams(params, 'destinationAirport');
199
+ let returnAirport = getStringFromParams(params, 'returnAirport');
200
+ let onlyDirect = getStringFromParams(params, 'onlyDirect') === 'true' ? true : null;
201
+ let luggageIncluded = getStringFromParams(params, 'luggageIncluded') === 'true' ? true : null;
202
+ let travelClass = getStringFromParams(params, 'travelClass');
203
+ let adults = getNumberFromParams(params, 'adults');
204
+ let kids = getNumberFromParams(params, 'kids');
205
+ let babies = getNumberFromParams(params, 'babies');
206
+
207
+ let request = {
208
+ officeId: tideConnection?.officeId ?? 0,
209
+ catalogueId: first(tideConnection?.catalogueIds) ?? 0,
210
+ departureAirportCode: departureAirport,
211
+ arrivalAirportCode: destinationAirport,
212
+ // luggageIncluded: true,
213
+ maxStops: onlyDirect ? 0 : null,
214
+ travelClass: travelClass,
215
+ pax: concat(
216
+ Array.from({ length: adults ?? 0 }, (_, index) => ({
217
+ id: index,
218
+ age: 31
219
+ })),
220
+ Array.from({ length: kids ?? 0 }, (_, index) => ({
221
+ id: index + (adults ?? 0),
222
+ age: 8
223
+ })),
224
+ Array.from({ length: babies ?? 0 }, (_, index) => ({
225
+ id: index + (adults ?? 0) + (kids ?? 0),
226
+ age: 1
227
+ }))
228
+ )
229
+ } as FlightSearchRequest;
230
+
231
+ if (fromDate) {
232
+ request.outward = {
233
+ date: dateToDateStruct(fromDate)
234
+ };
235
+ }
236
+
237
+ if (toDate) {
238
+ request.return = {
239
+ date: dateToDateStruct(toDate)
240
+ };
241
+ }
242
+
243
+ try {
244
+ onCancelSearch();
245
+ setFlightSearchResults([]);
246
+ // setValuesManuallyChanged(false);
247
+ setSequenceIds([]);
248
+ setFlightsLoading(true);
249
+ hubProxy.invoke('SearchFlights', request);
250
+ } catch (err) {
251
+ console.error('Error invoking search:', err);
252
+ }
253
+ };
254
+
255
+ const onCancelSearch = () => {
256
+ const hubProxy = hubProxyRef.current;
257
+ if (!hubProxy) {
258
+ console.error('Hub proxy not ready yet');
259
+ return;
260
+ }
261
+
262
+ try {
263
+ if (currentRequestId) {
264
+ hubProxy.invoke('CancelSearch', currentRequestId);
265
+ }
266
+ console.log('CancelSearch invoked');
267
+ } catch (err) {
268
+ console.error('Error invoking search:', err);
269
+ }
270
+ };
271
+
272
+ useEffect(() => {
273
+ if (!isEmpty(flightSearchResults)) {
274
+ onFilter();
275
+ } else {
276
+ setFilteredResults([]);
277
+ }
278
+ }, [flightSearchResults]);
279
+
280
+ // Sort the grouped results
281
+ useEffect(() => {
282
+ if (groupedResults) {
283
+ setFilteredResults(sortResults(groupedResults));
284
+ }
285
+ }, [selectedSortByType, groupedResults]);
286
+
287
+ const sortResults = (results: ExtendedFlightSearchResponseItem[]) => {
288
+ switch (selectedSortByType.label) {
289
+ case 'departureTime':
290
+ return orderBy(
291
+ results,
292
+ [(result) => getDepartureSegment(result.outward)?.departureDateTime],
293
+ [selectedSortByType.direction] // or "desc"
294
+ );
295
+ case 'durationInTicks':
296
+ return orderBy(
297
+ results,
298
+ [(result) => durationInTicksInMinutes(result.outward?.durationInTicks ?? 0)],
299
+ [selectedSortByType.direction] // or "desc"
300
+ );
301
+ default:
302
+ return orderBy(results, [selectedSortByType.label], [selectedSortByType.direction]);
303
+ }
304
+ };
305
+
306
+ useEffect(() => {
307
+ if (filters && !isEmpty(filters)) {
308
+ onFilter();
309
+ }
310
+ }, [filters]);
311
+
312
+ const onFilter = () => {
313
+ if (!isEmpty(flightSearchResults)) {
314
+ const filterByAirline = (result: ExtendedFlightSearchResponseItem) => {
315
+ if (isEmpty(filters.airlines)) {
316
+ return true;
317
+ }
318
+ return filters.airlines.some((airline) => airline.id === result.airlineCode);
319
+ };
320
+
321
+ const filterByNumberOfStops = (result: ExtendedFlightSearchResponseItem) => {
322
+ if (isEmpty(filters.numberOfStops)) {
323
+ return true;
324
+ }
325
+ const outwardStops = getNumberOfStops(result.outward);
326
+ const returnStops = getNumberOfStops(result.return);
327
+ return filters.numberOfStops.some((stop) => stop.id === outwardStops && stop.id === returnStops);
328
+ };
329
+
330
+ const filterByDepartureRange = (result: ExtendedFlightSearchResponseItem) => {
331
+ if (isEmpty(filters.departureRanges)) {
332
+ return true;
333
+ }
334
+ const departureRange = rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime);
335
+ return filters.departureRanges.some((range) => range.id === departureRange);
336
+ };
337
+
338
+ const filterByDepartureAirport = (result: ExtendedFlightSearchResponseItem) => {
339
+ if (isEmpty(filters.departureAirports)) {
340
+ return true;
341
+ }
342
+ return filters.departureAirports.some((airport) => airport.id === getDepartureSegment(result.outward)?.departureAirportCode);
343
+ };
344
+
345
+ const filterByArrivalAirport = (result: ExtendedFlightSearchResponseItem) => {
346
+ if (isEmpty(filters.arrivalAirports)) {
347
+ return true;
348
+ }
349
+ return filters.arrivalAirports.some((airport) => airport.id === getArrivalSegment(result.outward)?.arrivalAirportCode);
350
+ };
351
+
352
+ const filterByTravelTime = (result: ExtendedFlightSearchResponseItem) => {
353
+ if (isEmpty(filters.travelTimes) || filters.travelTimes.length !== 2 || result.outward?.durationInTicks === undefined) {
354
+ return true;
355
+ }
356
+
357
+ return (
358
+ durationInTicksInMinutes(result.outward?.durationInTicks) >= filters.travelTimes[0].lowestPrice &&
359
+ durationInTicksInMinutes(result.outward?.durationInTicks) <= filters.travelTimes[1].lowestPrice
360
+ );
361
+ };
362
+
363
+ const filterByPrice = (result: ExtendedFlightSearchResponseItem) => {
364
+ if (isEmpty(filters.prices) || filters.prices.length !== 2 || result.price === undefined) {
365
+ return true;
366
+ }
367
+
368
+ return result.price >= filters.prices[0].lowestPrice && result.price <= filters.prices[1].lowestPrice;
369
+ };
370
+
371
+ const filtered = flightSearchResults.filter(
372
+ (result) =>
373
+ filterByAirline(result) &&
374
+ filterByNumberOfStops(result) &&
375
+ filterByDepartureRange(result) &&
376
+ filterByDepartureAirport(result) &&
377
+ filterByArrivalAirport(result) &&
378
+ filterByTravelTime(result) &&
379
+ filterByPrice(result)
380
+ );
381
+ const grouped = groupBy(filtered, (x) => x.flightRouteId);
382
+ const lowestPerRoute = Object.values(grouped).map((group) => minBy(group, (item) => item.price));
383
+ setGroupedResults(lowestPerRoute.filter(Boolean) as ExtendedFlightSearchResponseItem[]);
384
+
385
+ // Set filter option
386
+ setAirlineResults(
387
+ flightSearchResults.filter(
388
+ (result) =>
389
+ filterByNumberOfStops(result) &&
390
+ filterByDepartureRange(result) &&
391
+ filterByDepartureAirport(result) &&
392
+ filterByArrivalAirport(result) &&
393
+ filterByTravelTime(result) &&
394
+ filterByPrice(result)
395
+ )
396
+ );
397
+ setNumberOfStopsResults(
398
+ flightSearchResults.filter(
399
+ (result) =>
400
+ filterByAirline(result) &&
401
+ filterByDepartureRange(result) &&
402
+ filterByDepartureAirport(result) &&
403
+ filterByArrivalAirport(result) &&
404
+ filterByTravelTime(result) &&
405
+ filterByPrice(result)
406
+ )
407
+ );
408
+ setDepartureRangeResults(
409
+ flightSearchResults.filter(
410
+ (result) =>
411
+ filterByAirline(result) &&
412
+ filterByNumberOfStops(result) &&
413
+ filterByDepartureAirport(result) &&
414
+ filterByArrivalAirport(result) &&
415
+ filterByTravelTime(result) &&
416
+ filterByPrice(result)
417
+ )
418
+ );
419
+ setDepartureAirportsResults(
420
+ flightSearchResults.filter(
421
+ (result) =>
422
+ filterByAirline(result) &&
423
+ filterByNumberOfStops(result) &&
424
+ filterByDepartureRange(result) &&
425
+ filterByArrivalAirport(result) &&
426
+ filterByTravelTime(result) &&
427
+ filterByPrice(result)
428
+ )
429
+ );
430
+ setArrivalAirportsResults(
431
+ flightSearchResults.filter(
432
+ (result) =>
433
+ filterByAirline(result) &&
434
+ filterByNumberOfStops(result) &&
435
+ filterByDepartureRange(result) &&
436
+ filterByDepartureAirport(result) &&
437
+ filterByTravelTime(result) &&
438
+ filterByPrice(result)
439
+ )
440
+ );
441
+ setTravelTimeResults(
442
+ flightSearchResults.filter(
443
+ (result) =>
444
+ filterByAirline(result) &&
445
+ filterByNumberOfStops(result) &&
446
+ filterByDepartureRange(result) &&
447
+ filterByDepartureAirport(result) &&
448
+ filterByArrivalAirport(result)
449
+ )
450
+ );
451
+ setPriceResults(
452
+ flightSearchResults.filter(
453
+ (result) =>
454
+ filterByAirline(result) &&
455
+ filterByNumberOfStops(result) &&
456
+ filterByDepartureRange(result) &&
457
+ filterByDepartureAirport(result) &&
458
+ filterByArrivalAirport(result) &&
459
+ filterByTravelTime(result)
460
+ )
461
+ );
462
+ }
463
+ };
464
+
465
+ const resetFilters = () => {
466
+ setFilters({ airlines: [], numberOfStops: [], departureRanges: [], departureAirports: [], arrivalAirports: [], travelTimes: [], prices: [] });
467
+ };
468
+
469
+ return (
470
+ <FlightSearchContext.Provider
471
+ value={{
472
+ flightsLoading,
473
+ isHubReady,
474
+ valuesManuallyChanged,
475
+ setValuesManuallyChanged,
476
+ currentRequestId,
477
+ onFlightSearch,
478
+ flightSearchResults,
479
+ setFlightSearchResults,
480
+ filters,
481
+ filteredResults,
482
+ sortByTypes,
483
+ selectedSortByType,
484
+ setFilters,
485
+ setFilteredResults,
486
+ onFilter,
487
+ setSelectedSortByType,
488
+ resetFilters,
489
+ airlineResults,
490
+ numberOfStopsResults,
491
+ departureRangeResults,
492
+ departureAirportsResults,
493
+ arrivalAirportsResults,
494
+ travelTimeResults,
495
+ priceResults
496
+ }}>
497
+ {children}
498
+ </FlightSearchContext.Provider>
499
+ );
500
+ };
501
+
502
+ export const useFlightSearch = (): FlightSearchContext => {
503
+ const context = useContext(FlightSearchContext);
504
+ if (!context) {
505
+ throw new Error('useFlightSearch must be used within a FlightSearchProvider');
506
+ }
507
+ return context;
508
+ };
@@ -15,7 +15,6 @@ const HotelCard: React.FC<HotelCardProps> = ({ result, translations }) => {
15
15
  const { selectedHotelId } = useSelector((state: SearchResultsRootState) => state.searchResults);
16
16
 
17
17
  const handleChange = (hotelProductId: number) => {
18
- console.log('selected hotel changed to:', hotelProductId);
19
18
  dispatch(setSelectedHotel(hotelProductId));
20
19
  };
21
20