@qite/tide-booking-component 1.4.109 → 1.4.110

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 (81) hide show
  1. package/build/build-cjs/index.js +1351 -775
  2. package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
  3. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  4. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  5. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  6. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
  7. package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  8. package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
  9. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +5 -1
  10. package/build/build-cjs/src/shared/booking/BookingPanel.d.ts +13 -0
  11. package/build/build-cjs/src/shared/booking/Sidebar.d.ts +34 -0
  12. package/build/build-cjs/src/shared/booking/StepIndicators.d.ts +7 -0
  13. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
  14. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  15. package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
  16. package/build/build-esm/index.js +1335 -770
  17. package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
  18. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  19. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  20. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  21. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
  22. package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  23. package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
  24. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +5 -1
  25. package/build/build-esm/src/shared/booking/BookingPanel.d.ts +13 -0
  26. package/build/build-esm/src/shared/booking/Sidebar.d.ts +34 -0
  27. package/build/build-esm/src/shared/booking/StepIndicators.d.ts +7 -0
  28. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
  29. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  30. package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
  31. package/package.json +1 -1
  32. package/src/booking-wizard/components/step-indicator.tsx +10 -31
  33. package/src/booking-wizard/components/step-route.tsx +39 -14
  34. package/src/booking-wizard/features/sidebar/index.tsx +10 -4
  35. package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
  36. package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
  37. package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
  38. package/src/search-results/components/book-packaging-entry/index.tsx +48 -0
  39. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +165 -0
  40. package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
  41. package/src/search-results/components/excursions/excursion-results.tsx +1 -1
  42. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
  43. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
  44. package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
  45. package/src/search-results/components/itinerary/index.tsx +13 -12
  46. package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
  47. package/src/search-results/components/search-results-container/search-results-container.tsx +239 -204
  48. package/src/search-results/components/spinner/spinner.tsx +12 -4
  49. package/src/search-results/store/search-results-slice.ts +16 -2
  50. package/src/shared/booking/BookingPanel.tsx +25 -0
  51. package/src/shared/booking/Sidebar.tsx +432 -0
  52. package/src/shared/booking/StepIndicators.tsx +30 -0
  53. package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
  54. package/src/shared/components/flyin/flights-flyin.tsx +1 -1
  55. package/src/shared/components/flyin/flyin.tsx +12 -4
  56. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
  57. package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
  58. package/src/shared/components/icon.tsx +13 -0
  59. package/src/shared/translations/ar-SA.json +7 -1
  60. package/src/shared/translations/da-DK.json +7 -1
  61. package/src/shared/translations/de-DE.json +7 -1
  62. package/src/shared/translations/en-GB.json +8 -2
  63. package/src/shared/translations/es-ES.json +7 -1
  64. package/src/shared/translations/fr-BE.json +7 -1
  65. package/src/shared/translations/fr-FR.json +7 -1
  66. package/src/shared/translations/is-IS.json +7 -1
  67. package/src/shared/translations/it-IT.json +7 -1
  68. package/src/shared/translations/ja-JP.json +7 -1
  69. package/src/shared/translations/nl-BE.json +7 -1
  70. package/src/shared/translations/nl-NL.json +7 -1
  71. package/src/shared/translations/no-NO.json +7 -1
  72. package/src/shared/translations/pl-PL.json +7 -1
  73. package/src/shared/translations/pt-PT.json +7 -1
  74. package/src/shared/translations/sv-SE.json +7 -1
  75. package/src/shared/utils/localization-util.ts +8 -0
  76. package/styles/components/_loader.scss +82 -0
  77. package/styles/components/_search.scss +9 -2
  78. package/styles/content-blocks-variables.scss +14 -14
  79. /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  80. /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  81. /package/src/{booking-wizard/components → shared/booking}/product-card.tsx +0 -0
@@ -0,0 +1,432 @@
1
+ import React, { useState } from 'react';
2
+ import { formatPrice } from '../utils/localization-util';
3
+ import { BookingPackageRoom, PackagingEntryLine, ServiceType } from '@qite/tide-client';
4
+ import { buildClassName } from '../utils/class-util';
5
+ import ProductCard from './product-card';
6
+ import { compact, isEmpty } from 'lodash';
7
+ import SidebarFlight from '../../booking-wizard/features/sidebar/sidebar-flight';
8
+ import { getDatePeriodText, getPaxTypeTranslation } from '../../booking-wizard/features/sidebar/sidebar-util';
9
+ import { PricePerPaxType } from '../../booking-wizard/types';
10
+
11
+ interface SharedSidebarProps {
12
+ productName: string;
13
+ thumbnailUrl?: string;
14
+ isLoading?: boolean;
15
+ travelerRooms?: string[];
16
+ startDateText?: string;
17
+ endDateText?: string;
18
+ departureFlightMetaData?: any;
19
+ returnFlightMetaData?: any;
20
+ basePrice?: number;
21
+ commission?: number;
22
+ totalPrice?: number;
23
+ remainingAmountText?: string;
24
+ includedCosts?: any[];
25
+ extraCosts?: any[];
26
+ deposit?: number;
27
+ accommodations?: BookingPackageRoom[];
28
+ packagingAccommodations?: PackagingEntryLine[];
29
+ includedServiceTypes?: number[];
30
+ isOnRequest?: boolean;
31
+ headerComponent?: React.ReactNode;
32
+ footerComponent?: React.ReactNode;
33
+ loaderComponent?: React.ReactNode;
34
+ isUnavailable?: boolean;
35
+ basePricePerPaxType?: PricePerPaxType[];
36
+ seperateExtraPricePerPaxType?: PricePerPaxType[];
37
+ translations: any;
38
+ agent?: number;
39
+ }
40
+
41
+ const SharedSidebar: React.FC<SharedSidebarProps> = ({
42
+ productName,
43
+ thumbnailUrl,
44
+ isLoading,
45
+ translations,
46
+ headerComponent,
47
+ travelerRooms,
48
+ startDateText,
49
+ endDateText,
50
+ loaderComponent,
51
+ departureFlightMetaData,
52
+ returnFlightMetaData,
53
+ accommodations,
54
+ packagingAccommodations,
55
+ isOnRequest,
56
+ includedServiceTypes,
57
+ basePrice,
58
+ commission,
59
+ totalPrice,
60
+ includedCosts,
61
+ extraCosts,
62
+ deposit,
63
+ basePricePerPaxType,
64
+ seperateExtraPricePerPaxType,
65
+ isUnavailable,
66
+ agent,
67
+ footerComponent
68
+ }) => {
69
+ if (!translations) return null;
70
+
71
+ const [active, setActive] = useState<boolean>(false);
72
+
73
+ const handleToggleClick = () => {
74
+ setActive(!active);
75
+ };
76
+
77
+ const isFlightOnly = includedServiceTypes?.length === 1 && includedServiceTypes.includes(ServiceType.flight);
78
+
79
+ const canShowPriceBreakdownSection = Boolean(basePrice && basePrice > 0) || !isEmpty(includedCosts) || !isEmpty(extraCosts);
80
+ const canShowTotalPriceSection = Boolean(totalPrice && totalPrice > 0);
81
+
82
+ const remainingAmount = Number(((totalPrice ?? 0) - (deposit ?? 0)).toFixed(2));
83
+
84
+ // TODO: Determine currency code based on the data, for now default to EUR
85
+ let currencyCode = 'EUR';
86
+
87
+ return (
88
+ <div className={buildClassName(['booking__sidebar', active && 'booking__sidebar--active'])}>
89
+ {headerComponent}
90
+
91
+ <div className="booking__sidebar-frame">
92
+ <ProductCard productName={productName} thumbnailUrl={thumbnailUrl} handleToggleClick={handleToggleClick} />
93
+ <div className="pricing-summary">
94
+ <div className="pricing-summary__wrapper">
95
+ <div className="pricing-summary__region pricing-summary__region--fade-in">
96
+ <div className="pricing-summary__group">
97
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.TRAVEL_INFO}</h6>
98
+ {!isEmpty(travelerRooms) &&
99
+ travelerRooms?.map((room, rIndex) => (
100
+ <div className="pricing-summary__row" key={rIndex}>
101
+ <div className="pricing-summary__property">
102
+ {travelerRooms.length > 1 && `${translations.SHARED.ROOM} ${rIndex + 1}`}
103
+ {travelerRooms.length === 1 && translations.ROOM_OPTIONS_FORM.TRAVELER_GROUP}
104
+ </div>
105
+
106
+ <div className="pricing-summary__value">{room}</div>
107
+ </div>
108
+ ))}
109
+ {startDateText && (
110
+ <div className="pricing-summary__row">
111
+ <div className="pricing-summary__property">
112
+ {startDateText && endDateText ? translations.SIDEBAR.DEPARTURE : translations.SIDEBAR.DEPARTURE_SINGLE}
113
+ </div>
114
+ <div className="pricing-summary__value">{startDateText}</div>
115
+ </div>
116
+ )}
117
+
118
+ {endDateText && (
119
+ <div className="pricing-summary__row">
120
+ <div className="pricing-summary__property">{translations.SIDEBAR.ARRIVAL}</div>
121
+ <div className="pricing-summary__value">{endDateText}</div>
122
+ </div>
123
+ )}
124
+
125
+ {/* {lines.map((line: any, idx: number) => (
126
+ <div className="pricing-summary__row" key={line.guid || idx}>
127
+ <div className="pricing-summary__property">
128
+ {line.serviceType === 3 && (
129
+ <>
130
+ {line.productName} <br />
131
+ {line.accommodationName && <span>{line.accommodationName}</span>}
132
+ <br />
133
+ {line.from && line.to && (
134
+ <span>
135
+ {new Date(line.from).toLocaleDateString()} - {new Date(line.to).toLocaleDateString()}
136
+ </span>
137
+ )}
138
+ </>
139
+ )}
140
+ {line.serviceType === 7 && line.flightInformation && (
141
+ <>
142
+ <strong>{line.productName}</strong>
143
+ <br />
144
+ {line.flightInformation.flightLines &&
145
+ line.flightInformation.flightLines.map((flight: any, fIdx: number) => (
146
+ <div key={fIdx} style={{ marginBottom: 4 }}>
147
+ {flight.airlineDescription} {flight.flightNumber}
148
+ <br />
149
+ {flight.departureAirportDescription} ({flight.departureAirportCode}) {flight.departureTime} → {flight.arrivalAirportDescription}{' '}
150
+ ({flight.arrivalAirportCode}) {flight.arrivalTime}
151
+ </div>
152
+ ))}
153
+ </>
154
+ )}
155
+ </div>
156
+ </div>
157
+ ))} */}
158
+ </div>
159
+ {isLoading && loaderComponent}
160
+
161
+ {!isLoading && departureFlightMetaData && (
162
+ <SidebarFlight title={translations.SIDEBAR.DEPARTURE_FLIGHT} flightMetaData={departureFlightMetaData} translations={translations} />
163
+ )}
164
+
165
+ {!isLoading && returnFlightMetaData && (
166
+ <SidebarFlight title={translations.SIDEBAR.ARRIVAL_FLIGHT} flightMetaData={returnFlightMetaData} translations={translations} />
167
+ )}
168
+
169
+ {accommodations && (
170
+ <div className="pricing-summary__group">
171
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
172
+ {accommodations.map((accommodation) => {
173
+ let option = accommodation.options.find((x) => x.isSelected);
174
+ return (
175
+ <div key={accommodation.index}>
176
+ <div className="pricing-summary__row">
177
+ <div className="pricing-summary__property">
178
+ {option?.accommodationName}
179
+ {isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
180
+ {option?.isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
181
+ </div>
182
+ {/*<div className="pricing-summary__value">
183
+ {option && option.price > 0 && formatPrice(option?.price)}
184
+ </div>*/}
185
+ </div>
186
+ <div className="pricing-summary__row">
187
+ <div className="price-summarty__property">{option?.regimeName}</div>
188
+ <div className="price-summary__value">{!isFlightOnly && getDatePeriodText(translations, option?.from, option?.to, true)}</div>
189
+ </div>
190
+ </div>
191
+ );
192
+ })}
193
+ </div>
194
+ )}
195
+ {packagingAccommodations && (
196
+ <div className="pricing-summary__group">
197
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
198
+ {packagingAccommodations.map((accommodation) => {
199
+ return (
200
+ <div key={accommodation.guid}>
201
+ <div className="pricing-summary__row">
202
+ <div className="pricing-summary__property">
203
+ {accommodation?.accommodationName}
204
+ {isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
205
+ </div>
206
+ {/*<div className="pricing-summary__value">
207
+ {option && option.price > 0 && formatPrice(option?.price)}
208
+ </div>*/}
209
+ </div>
210
+ <div className="pricing-summary__row">
211
+ <div className="price-summarty__property">{accommodation?.regimeName}</div>
212
+ <div className="price-summary__value">
213
+ {!isFlightOnly && getDatePeriodText(translations, accommodation?.from, accommodation?.to, true)}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ );
218
+ })}
219
+ </div>
220
+ )}
221
+ </div>
222
+
223
+ {!isLoading && canShowPriceBreakdownSection && (
224
+ <div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
225
+ {basePrice !== undefined && basePrice > 0 && (
226
+ <div className="pricing-summary__group">
227
+ <div className="pricing-summary__row">
228
+ <div className="pricing-summary__property">
229
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.BASE_PRICE}</h6>
230
+ </div>
231
+ <div className="pricing-summary__value">{formatPrice(basePrice, currencyCode)}</div>
232
+ </div>
233
+ {basePricePerPaxType &&
234
+ basePricePerPaxType.map((ppt, index) => (
235
+ <React.Fragment key={`${ppt.paxType}-${index}`}>
236
+ <div className="pricing-summary__row">
237
+ <div className="pricing-summary__property">
238
+ {ppt.numberOfPax} {getPaxTypeTranslation(translations, ppt.paxType, ppt.numberOfPax)}
239
+ </div>
240
+ <div className="pricing-summary__value">{formatPrice(ppt.pricePerPaxType, currencyCode)}</div>
241
+ </div>
242
+ {ppt.details.map((detail, dIndex) => (
243
+ <div className="pricing-summary__row pricing-summary__row--sub" key={`${ppt.paxType}-${index}-${dIndex}`}>
244
+ <div className="pricing-summary__property">
245
+ {detail.numberOfPax}x {detail.description}
246
+ </div>
247
+ <div className="pricing-summary__value">{formatPrice(detail.price / detail.numberOfPax, currencyCode)}</div>
248
+ </div>
249
+ ))}
250
+ </React.Fragment>
251
+ ))}
252
+ </div>
253
+ )}
254
+ {!isEmpty(includedCosts) && (
255
+ <div className="pricing-summary__group">
256
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.INCLUDED_COSTS}</h6>
257
+ {includedCosts?.map((priceDetail, index) => (
258
+ <React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
259
+ <div className="pricing-summary__row">
260
+ <div className="pricing-summary__property">{priceDetail.productName}</div>
261
+ {priceDetail.showPrice && (
262
+ <div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
263
+ )}
264
+ </div>
265
+ <div className="pricing-summary__row">
266
+ <div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
267
+ </div>
268
+ </React.Fragment>
269
+ ))}
270
+ </div>
271
+ )}
272
+ {!isEmpty(extraCosts) && (
273
+ <div className="pricing-summary__group">
274
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.EXTRA_COSTS}</h6>
275
+ {extraCosts?.map((priceDetail, index) => (
276
+ <React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
277
+ <div className="pricing-summary__row">
278
+ <div className="pricing-summary__property">{priceDetail.productName}</div>
279
+ {priceDetail.showPrice && (
280
+ <div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
281
+ )}
282
+ </div>
283
+ <div className="pricing-summary__row">
284
+ <div className="pricing-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
285
+ </div>
286
+ {seperateExtraPricePerPaxType &&
287
+ seperateExtraPricePerPaxType.map((ppt, index) => (
288
+ <React.Fragment key={`${ppt.paxType}-${index}`}>
289
+ <div className="pricing-summary__row">
290
+ <div className="pricing-summary__property">
291
+ {ppt.numberOfPax} {getPaxTypeTranslation(translations, ppt.paxType, ppt.numberOfPax)}
292
+ </div>
293
+ <div className="pricing-summary__value">{formatPrice(ppt.pricePerPaxType, currencyCode)}</div>
294
+ </div>
295
+ {ppt.details.map((detail, dIndex) => (
296
+ <div className="pricing-summary__row pricing-summary__row--sub" key={`${ppt.paxType}-${index}-${dIndex}`}>
297
+ <div className="pricing-summary__property">
298
+ {detail.numberOfPax}x {detail.description}
299
+ </div>
300
+ <div className="pricing-summary__value">{formatPrice(detail.price / detail.numberOfPax, currencyCode)}</div>
301
+ </div>
302
+ ))}
303
+ </React.Fragment>
304
+ ))}
305
+ </React.Fragment>
306
+ ))}
307
+ </div>
308
+ )}
309
+ </div>
310
+ )}
311
+
312
+ {!isLoading && canShowTotalPriceSection && !isUnavailable && (
313
+ <div className={`pricing-summary__region pricing-summary__region--pricing ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
314
+ {deposit && remainingAmount > 0 ? (
315
+ <div className="pricing-summary__group">
316
+ {agent && (
317
+ <div className="pricing-summary__row pricing-summary__row--total-price">
318
+ <div className="pricing-summary__property">
319
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.COMMISSION}</h6>
320
+ </div>
321
+ <div className="pricing-summary__value">
322
+ <div className="pricing">
323
+ <div className="pricing__price">{formatPrice(commission ?? 0, currencyCode)}</div>
324
+ </div>
325
+ </div>
326
+ </div>
327
+ )}
328
+ {totalPrice !== undefined && totalPrice > 0 && (
329
+ <div className="pricing-summary__row pricing-summary__row--total-price">
330
+ <div className="pricing-summary__property">
331
+ <h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
332
+ </div>
333
+ <div className="pricing-summary__value">
334
+ <div className="pricing">
335
+ <div className="pricing__price">{formatPrice(totalPrice, currencyCode)}</div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ )}
340
+ <div className="pricing-summary__row">
341
+ <div className="pricing-summary__property">
342
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.DEPOSIT}</h6>
343
+ </div>
344
+
345
+ <div className="pricing-summary__value">
346
+ <div className="pricing">
347
+ <div className="pricing__price">{formatPrice(deposit, currencyCode)}</div>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ <div className="pricing-summary__row">
352
+ <small>
353
+ <em>
354
+ {translations.SIDEBAR.DEPOSIT_TEXT1}
355
+ <strong>{translations.SIDEBAR.DEPOSIT_TEXT2}</strong>
356
+ {translations.SIDEBAR.DEPOSIT_TEXT3}
357
+ {formatPrice(remainingAmount, currencyCode)}
358
+ {translations.SIDEBAR.DEPOSIT_TEXT4}
359
+ <strong>{translations.SIDEBAR.DEPOSIT_TEXT5}</strong>
360
+ {translations.SIDEBAR.DEPOSIT_TEXT6}
361
+ </em>
362
+ </small>
363
+ </div>
364
+ </div>
365
+ ) : (
366
+ <div className="pricing-summary__group">
367
+ {agent && (
368
+ <div className="pricing-summary__row pricing-summary__row--total-price">
369
+ <div className="pricing-summary__property">
370
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.COMMISSION}</h6>
371
+ </div>
372
+ <div className="pricing-summary__value">
373
+ <div className="pricing">
374
+ <div className="pricing__price">{formatPrice(commission ?? 0, currencyCode)}</div>
375
+ </div>
376
+ </div>
377
+ </div>
378
+ )}
379
+ {totalPrice !== undefined && totalPrice > 0 && (
380
+ <div className="pricing-summary__row pricing-summary__row--total-price">
381
+ <div className="pricing-summary__property">
382
+ <h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
383
+ </div>
384
+
385
+ <div className="pricing-summary__value">
386
+ <div className="pricing">
387
+ <div className="pricing__price">{formatPrice(totalPrice, currencyCode)}</div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ )}
392
+ </div>
393
+ )}
394
+ </div>
395
+ )}
396
+
397
+ {footerComponent}
398
+
399
+ {/* <div className="pricing-summary__region pricing-summary__region--pricing pricing-summary__region--fade-in">
400
+ <div className="pricing-summary__group">
401
+ <div className="pricing-summary__row pricing-summary__row--total-price">
402
+ <div className="pricing-summary__property">
403
+ <h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
404
+ </div>
405
+ <div className="pricing-summary__value">
406
+ <div className="pricing">
407
+ <div className="pricing__price">{formatPrice(price || 0, 'EUR')}</div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ {depositAmount > 0 && (
412
+ <div className="pricing-summary__row">
413
+ <div className="pricing-summary__property">
414
+ <h6 className="pricing-summary__title">{translations.SIDEBAR.DEPOSIT}</h6>
415
+ </div>
416
+ <div className="pricing-summary__value">
417
+ <div className="pricing">
418
+ <div className="pricing__price">{formatPrice(depositAmount, 'EUR')}</div>
419
+ </div>
420
+ </div>
421
+ </div>
422
+ )}
423
+ </div>
424
+ </div> */}
425
+ </div>
426
+ </div>
427
+ </div>
428
+ </div>
429
+ );
430
+ };
431
+
432
+ export default SharedSidebar;
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { buildClassName } from '../utils/class-util';
3
+
4
+ interface StepIndicatorsProps {
5
+ currentStep: number;
6
+ stepLabels: string[];
7
+ }
8
+
9
+ const StepIndicators: React.FC<StepIndicatorsProps> = ({ currentStep, stepLabels }) => {
10
+ return (
11
+ <div className="step-indicators">
12
+ <div className="step-indicators__items">
13
+ {stepLabels.map((stepName, index) => (
14
+ <div
15
+ key={`${index + 1}-${stepName}`}
16
+ className={buildClassName([
17
+ 'step-indicators__item',
18
+ currentStep === index && 'step-indicators__item--active',
19
+ currentStep > index && 'step-indicators__item--completed'
20
+ ])}>
21
+ <div className="step-indicators__icon">{index + 1}</div>
22
+ <div className="step-indicators__text">{stepName}</div>
23
+ </div>
24
+ ))}
25
+ </div>
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export default StepIndicators;
@@ -43,14 +43,13 @@ const formatPrice = (price?: number, currencyCode?: string | null) => {
43
43
  const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, handleConfirm }) => {
44
44
  const dispatch = useDispatch();
45
45
  const context = useContext(SearchResultsConfigurationContext);
46
+ const language = context?.languageCode ?? 'en-GB';
47
+ const translations = getTranslations(language);
46
48
 
47
49
  if (isLoading) {
48
- return <>{context?.customSpinner ?? <Spinner />}</>;
50
+ return <>{context?.customSpinner ?? <Spinner label={translations.SRP.LOADING_ACCOMMODATIONS} />}</>;
49
51
  }
50
52
 
51
- const language = context?.languageCode ?? 'en-GB';
52
- const translations = getTranslations(language);
53
-
54
53
  const { packagingAccoSearchDetails, selectedPackagingAccoResultCode } = useSelector((state: SearchResultsRootState) => state.searchResults);
55
54
 
56
55
  const selectedPackagingAccoSearchDetails = useMemo<PackagingAccommodationResponse | undefined>(() => {
@@ -151,7 +151,7 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
151
151
  <>
152
152
  <div className="flyin__content">
153
153
  {flightSearchDetailsLoading || isEmpty(flights) ? (
154
- <Spinner />
154
+ <Spinner label={translations.SRP.LOADING_FLIGHTS} />
155
155
  ) : (
156
156
  flight && (
157
157
  <div className="flyin__content-text-row">
@@ -38,6 +38,8 @@ type FlyInProps = {
38
38
  isPackageEditFlow?: boolean;
39
39
  sortByTypes?: SortByType[];
40
40
  activeSearchSeed?: SearchSeed | null;
41
+ toggleFilters?: () => void;
42
+ filtersOpen: boolean;
41
43
  };
42
44
 
43
45
  const FlyIn: React.FC<FlyInProps> = ({
@@ -51,7 +53,9 @@ const FlyIn: React.FC<FlyInProps> = ({
51
53
  isPackageEditFlow,
52
54
  handleConfirm,
53
55
  sortByTypes,
54
- activeSearchSeed
56
+ activeSearchSeed,
57
+ toggleFilters,
58
+ filtersOpen
55
59
  }) => {
56
60
  const dispatch = useDispatch();
57
61
  const context = useContext(SearchResultsConfigurationContext);
@@ -166,8 +170,8 @@ const FlyIn: React.FC<FlyInProps> = ({
166
170
  <Filters
167
171
  initialFilters={initialFilters}
168
172
  filters={filters}
169
- isOpen={false}
170
- handleSetIsOpen={() => {}}
173
+ isOpen={filtersOpen}
174
+ handleSetIsOpen={() => toggleFilters && toggleFilters()}
171
175
  // handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
172
176
  isLoading={isLoading}
173
177
  setFilters={(filters) => dispatch(setFilters(filters))}
@@ -183,6 +187,10 @@ const FlyIn: React.FC<FlyInProps> = ({
183
187
  </>
184
188
  )}
185
189
  </span>
190
+ <div className="cta cta--filter" onClick={() => toggleFilters && toggleFilters()}>
191
+ <Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
192
+ {translations.SRP.FILTERS}
193
+ </div>
186
194
  {sortByTypes && sortByTypes.length > 0 && (
187
195
  <div className="search__result-row-filter">
188
196
  <ItemPicker
@@ -208,7 +216,7 @@ const FlyIn: React.FC<FlyInProps> = ({
208
216
  )}
209
217
 
210
218
  {srpType === PortalQsmType.AccommodationAndFlight && (flyInType === 'flight-outward-results' || flyInType === 'flight-return-results') && (
211
- <PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />
219
+ <PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} toggleFilters={toggleFilters} filtersOpen={filtersOpen} />
212
220
  )}
213
221
 
214
222
  {srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-results' && (
@@ -36,14 +36,13 @@ const formatPrice = (price?: number, currencyCode = 'EUR') => {
36
36
  const GroupTourFlyIn: React.FC<GroupTourFlyInProps> = ({ isLoading, isOpen, setIsOpen }) => {
37
37
  const dispatch = useDispatch();
38
38
  const context = useContext(SearchResultsConfigurationContext);
39
+ const language = context?.languageCode ?? 'en-GB';
40
+ const translations = getTranslations(language);
39
41
 
40
42
  if (isLoading) {
41
- return <>{context?.customSpinner ?? <Spinner />}</>;
43
+ return <>{context?.customSpinner ?? <Spinner label={translations.SRP.LOADING_OPTIONS} />}</>;
42
44
  }
43
45
 
44
- const language = context?.languageCode ?? 'en-GB';
45
- const translations = getTranslations(language);
46
-
47
46
  const { bookingPackageDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
48
47
 
49
48
  const selectedBookingPackageDetails = useMemo<BookingPackageOption | undefined>(() => {
@@ -24,13 +24,16 @@ import IndependentFlightOption from '../../../search-results/components/flight/f
24
24
  import Filters from '../../../search-results/components/filters/filters';
25
25
  import { SortByType } from '../../../search-results/types';
26
26
  import ItemPicker from '../../../search-results/components/item-picker';
27
+ import Icon from '../icon';
27
28
 
28
29
  type FlightsFlyInProps = {
29
30
  isOpen: boolean;
30
31
  setIsOpen: (open: boolean) => void;
32
+ toggleFilters?: () => void;
33
+ filtersOpen: boolean;
31
34
  };
32
35
 
33
- const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
36
+ const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen, toggleFilters, filtersOpen }) => {
34
37
  const context = useContext(SearchResultsConfigurationContext);
35
38
  const language = context?.languageCode ?? 'en-GB';
36
39
  const translations = getTranslations(language);
@@ -80,14 +83,14 @@ const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen
80
83
  <>
81
84
  <div className="flyin__content flyin__content--columns">
82
85
  {flightsLoading ? (
83
- <Spinner />
86
+ <Spinner label={translations.SRP.LOADING_FLIGHTS} />
84
87
  ) : (
85
88
  <>
86
89
  <Filters
87
90
  initialFilters={initialFlightFilters}
88
91
  filters={flightFilters}
89
- isOpen={false}
90
- handleSetIsOpen={() => {}}
92
+ isOpen={filtersOpen}
93
+ handleSetIsOpen={() => toggleFilters && toggleFilters()}
91
94
  // handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
92
95
  isLoading={flightsLoading}
93
96
  setFilters={(filters) => dispatch(setFlightFilters(filters))}
@@ -99,6 +102,10 @@ const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen
99
102
  {uniqueOutwardFlights?.length && uniqueOutwardFlights.length}
100
103
  &nbsp;{translations.FLIGHTS_FORM.FLIGHTS_FOUND_2}&nbsp;{translations.FLIGHTS_FORM.FLIGHTS_FOUND_3}
101
104
  </span>
105
+ <div className="cta cta--filter" onClick={() => toggleFilters && toggleFilters()}>
106
+ <Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
107
+ {translations.SRP.FILTERS}
108
+ </div>
102
109
  {sortByTypes && sortByTypes.length > 0 && (
103
110
  <div className="search__result-row-filter">
104
111
  <ItemPicker
@@ -1093,6 +1093,19 @@ const Icon: React.FC<IconProps> = ({ name, className, title, width, height, fill
1093
1093
  </svg>
1094
1094
  );
1095
1095
 
1096
+ case 'ui-shopping-cart':
1097
+ return (
1098
+ <svg
1099
+ className={['icon', `icon--${name}`, className].filter((className) => !isEmpty(className)).join(' ')}
1100
+ width={width}
1101
+ height={height}
1102
+ viewBox="0 0 640 512"
1103
+ fill={fill ?? 'currentColor'}>
1104
+ <HTMLComment text="!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." />
1105
+ {title && <title>{title}</title>}
1106
+ <path d="M24-16C10.7-16 0-5.3 0 8S10.7 32 24 32l45.3 0c3.9 0 7.2 2.8 7.9 6.6l52.1 286.3c6.2 34.2 36 59.1 70.8 59.1L456 384c13.3 0 24-10.7 24-24s-10.7-24-24-24l-255.9 0c-11.6 0-21.5-8.3-23.6-19.7l-5.1-28.3 303.6 0c30.8 0 57.2-21.9 62.9-52.2L568.9 69.9C572.6 50.2 557.5 32 537.4 32l-412.7 0-.4-2c-4.8-26.6-28-46-55.1-46L24-16zM208 512a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm224 0a48 48 0 1 0 0-96 48 48 0 1 0 0 96z" />
1107
+ </svg>
1108
+ );
1096
1109
  default:
1097
1110
  return null;
1098
1111
  }
@@ -348,6 +348,7 @@
348
348
  "DEPARTURE_ASC": "تاريخ المغادرة (الأقرب أولاً)",
349
349
  "FILTERS": "عوامل التصفية",
350
350
  "SHOW_ITINERARY": "عرض خط سير الرحلة",
351
+ "VIEW_BOOKING": "عرض الحجز",
351
352
  "ITINERARY_TITLE": "ملخص رحلتك",
352
353
  "DOSSIER_NUMBER": "رقم الملف",
353
354
  "PACKAGE_PRICE_PER_PERSON": "سعر الباقة للشخص الواحد",
@@ -381,7 +382,12 @@
381
382
  "DURATION_ASC": "المدة تصاعدياً",
382
383
  "DURATION_DESC": "المدة تنازلياً",
383
384
  "TRAVEL_GROUP": "مجموعة المسافرين",
384
- "EXCURSION": "رحلة"
385
+ "EXCURSION": "رحلة",
386
+ "LOADING_EXCURSIONS": "جارٍ تحميل الرحلات...",
387
+ "LOADING_FLIGHTS": "جارٍ تحميل الرحلات الجوية...",
388
+ "LOADING_ACCOMMODATIONS": "جارٍ تحميل الإقامات...",
389
+ "LOADING_ITINERARY": "جارٍ تحميل خط سير الرحلة...",
390
+ "LOADING_OPTIONS": "جارٍ تحميل الخيارات..."
385
391
  },
386
392
  "ITINERARY": {
387
393
  "DAY": "اليوم",