@qite/tide-booking-component 1.4.108 → 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 (86) hide show
  1. package/build/build-cjs/index.js +1607 -886
  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 +1591 -881
  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/content/components/image-with-text.tsx +0 -1
  39. package/src/qsm/components/QSMContainer/qsm-container.tsx +189 -83
  40. package/src/qsm/components/item-picker/index.tsx +2 -6
  41. package/src/qsm/components/mobile-filter-modal/index.tsx +15 -1
  42. package/src/search-results/components/book-packaging-entry/index.tsx +48 -0
  43. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +165 -0
  44. package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
  45. package/src/search-results/components/excursions/excursion-results.tsx +1 -1
  46. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
  47. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
  48. package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
  49. package/src/search-results/components/itinerary/index.tsx +13 -12
  50. package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
  51. package/src/search-results/components/search-results-container/search-results-container.tsx +239 -204
  52. package/src/search-results/components/spinner/spinner.tsx +12 -4
  53. package/src/search-results/store/search-results-slice.ts +16 -2
  54. package/src/shared/booking/BookingPanel.tsx +25 -0
  55. package/src/shared/booking/Sidebar.tsx +432 -0
  56. package/src/shared/booking/StepIndicators.tsx +30 -0
  57. package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
  58. package/src/shared/components/flyin/flights-flyin.tsx +1 -1
  59. package/src/shared/components/flyin/flyin.tsx +12 -4
  60. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
  61. package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
  62. package/src/shared/components/icon.tsx +13 -0
  63. package/src/shared/translations/ar-SA.json +7 -1
  64. package/src/shared/translations/da-DK.json +7 -1
  65. package/src/shared/translations/de-DE.json +7 -1
  66. package/src/shared/translations/en-GB.json +8 -2
  67. package/src/shared/translations/es-ES.json +7 -1
  68. package/src/shared/translations/fr-BE.json +7 -1
  69. package/src/shared/translations/fr-FR.json +7 -1
  70. package/src/shared/translations/is-IS.json +7 -1
  71. package/src/shared/translations/it-IT.json +7 -1
  72. package/src/shared/translations/ja-JP.json +7 -1
  73. package/src/shared/translations/nl-BE.json +7 -1
  74. package/src/shared/translations/nl-NL.json +7 -1
  75. package/src/shared/translations/no-NO.json +7 -1
  76. package/src/shared/translations/pl-PL.json +7 -1
  77. package/src/shared/translations/pt-PT.json +7 -1
  78. package/src/shared/translations/sv-SE.json +7 -1
  79. package/src/shared/utils/localization-util.ts +8 -0
  80. package/styles/components/_footer.scss +2 -8
  81. package/styles/components/_loader.scss +82 -0
  82. package/styles/components/_search.scss +14 -7
  83. package/styles/content-blocks-variables.scss +14 -14
  84. /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  85. /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  86. /package/src/{booking-wizard/components → shared/booking}/product-card.tsx +0 -0
@@ -65,7 +65,6 @@ interface ImageWithTextSectionProps {
65
65
 
66
66
  export const ImageWithTextSection: React.FC<ImageWithTextSectionProps> = ({ variant, sectionTitle, hasBackground = true, cards }) => {
67
67
  const className = ['image-with-text', hasBackground && 'image-with-text--background'].filter(Boolean).join(' ');
68
- console.log('variant', variant);
69
68
  return (
70
69
  <div className={className}>
71
70
  <div className="image-with-text__container">
@@ -434,107 +434,109 @@ const QSMContainer: React.FC = () => {
434
434
  {translations.QSM.CRUISES}
435
435
  </button> */}
436
436
  </div>
437
- <div className="qsm__filter">
438
- {(qsmType === PortalQsmType.Accommodation || qsmType === PortalQsmType.AccommodationAndFlight || qsmType === PortalQsmType.GroupTour) && (
439
- <div className="radiobutton-group qsm__filter__inputgroup">
440
- <div className="radiobutton">
441
- <label className="radiobutton__label">
442
- <input
443
- type="radio"
444
- name="numberOfAccommodations"
445
- // onChange={handleMainBookerChange}
446
- // onBlur={formik.handleBlur}
447
- value=""
448
- checked={true}
449
- readOnly
450
- className="radiobutton__input"
451
- />
452
- <span>{translations.QSM.ONE_ACCOMMODATION}</span>
453
- </label>
454
- </div>
455
- <div className="radiobutton">
456
- <label className="radiobutton__label">
457
- <input
458
- type="radio"
459
- name="numberOfAccommodations"
460
- // onChange={handleMainBookerChange}
461
- // onBlur={formik.handleBlur}
462
- value=""
463
- className="radiobutton__input"
464
- disabled={true}
465
- />
466
- <span>{translations.QSM.MULTIPLE_ACCOMMODATIONS}</span>
467
- </label>
468
- </div>
469
- </div>
470
- )}
471
- {qsmType === PortalQsmType.Flight && (
472
- <div className="radiobutton-group qsm__filter__inputgroup">
473
- {allowOneWay && (
437
+ {!isMobile && (
438
+ <div className="qsm__filter">
439
+ {(qsmType === PortalQsmType.Accommodation || qsmType === PortalQsmType.AccommodationAndFlight || qsmType === PortalQsmType.GroupTour) && (
440
+ <div className="radiobutton-group qsm__filter__inputgroup">
474
441
  <div className="radiobutton">
475
442
  <label className="radiobutton__label">
476
443
  <input
477
444
  type="radio"
478
- name="tripType"
479
- value="oneway"
480
- checked={tripType === 'oneway'}
481
- onChange={() => handleTripTypeChange('oneway')}
445
+ name="numberOfAccommodations"
446
+ // onChange={handleMainBookerChange}
447
+ // onBlur={formik.handleBlur}
448
+ value=""
449
+ checked={true}
450
+ readOnly
482
451
  className="radiobutton__input"
483
452
  />
484
- <span>{translations.QSM.ONEWAY}</span>
453
+ <span>{translations.QSM.ONE_ACCOMMODATION}</span>
485
454
  </label>
486
455
  </div>
487
- )}
488
-
489
- {allowRoundtrip && (
490
456
  <div className="radiobutton">
491
457
  <label className="radiobutton__label">
492
458
  <input
493
459
  type="radio"
494
- name="tripType"
495
- value="roundtrip"
496
- checked={tripType === 'roundtrip'}
497
- onChange={() => handleTripTypeChange('roundtrip')}
460
+ name="numberOfAccommodations"
461
+ // onChange={handleMainBookerChange}
462
+ // onBlur={formik.handleBlur}
463
+ value=""
498
464
  className="radiobutton__input"
465
+ disabled={true}
499
466
  />
500
- <span>{translations.QSM.ROUNDTRIP}</span>
467
+ <span>{translations.QSM.MULTIPLE_ACCOMMODATIONS}</span>
501
468
  </label>
502
469
  </div>
503
- )}
504
-
505
- {allowOpenJaw && (
506
- <div className="radiobutton">
507
- <label className="radiobutton__label">
508
- <input
509
- type="radio"
510
- name="tripType"
511
- value="openjaw"
512
- checked={tripType === 'openjaw'}
513
- onChange={() => handleTripTypeChange('openjaw')}
514
- className="radiobutton__input"
515
- />
516
- <span>{translations.QSM.OPENJAW}</span>
517
- </label>
518
- </div>
519
- )}
470
+ </div>
471
+ )}
472
+ {qsmType === PortalQsmType.Flight && (
473
+ <div className="radiobutton-group qsm__filter__inputgroup">
474
+ {allowOneWay && (
475
+ <div className="radiobutton">
476
+ <label className="radiobutton__label">
477
+ <input
478
+ type="radio"
479
+ name="tripType"
480
+ value="oneway"
481
+ checked={tripType === 'oneway'}
482
+ onChange={() => handleTripTypeChange('oneway')}
483
+ className="radiobutton__input"
484
+ />
485
+ <span>{translations.QSM.ONEWAY}</span>
486
+ </label>
487
+ </div>
488
+ )}
489
+
490
+ {allowRoundtrip && (
491
+ <div className="radiobutton">
492
+ <label className="radiobutton__label">
493
+ <input
494
+ type="radio"
495
+ name="tripType"
496
+ value="roundtrip"
497
+ checked={tripType === 'roundtrip'}
498
+ onChange={() => handleTripTypeChange('roundtrip')}
499
+ className="radiobutton__input"
500
+ />
501
+ <span>{translations.QSM.ROUNDTRIP}</span>
502
+ </label>
503
+ </div>
504
+ )}
505
+
506
+ {allowOpenJaw && (
507
+ <div className="radiobutton">
508
+ <label className="radiobutton__label">
509
+ <input
510
+ type="radio"
511
+ name="tripType"
512
+ value="openjaw"
513
+ checked={tripType === 'openjaw'}
514
+ onChange={() => handleTripTypeChange('openjaw')}
515
+ className="radiobutton__input"
516
+ />
517
+ <span>{translations.QSM.OPENJAW}</span>
518
+ </label>
519
+ </div>
520
+ )}
521
+ </div>
522
+ )}
523
+ <div className="qsm__filter__classgroup">
524
+ {qsmType !== PortalQsmType.Accommodation &&
525
+ qsmType !== PortalQsmType.Car &&
526
+ qsmType !== PortalQsmType.Ticket &&
527
+ qsmType !== PortalQsmType.Cruise &&
528
+ qsmType !== PortalQsmType.Transfer &&
529
+ qsmType !== PortalQsmType.GroupTour &&
530
+ askTravelClass && <TravelClassPicker />}
531
+ {qsmType !== PortalQsmType.Multidestination &&
532
+ qsmType !== PortalQsmType.Car &&
533
+ qsmType !== PortalQsmType.Flight &&
534
+ qsmType !== PortalQsmType.Transfer &&
535
+ askTravelType && <TravelTypePicker />}
536
+ {askNationality && <TravelNationalityPicker />}
520
537
  </div>
521
- )}
522
- <div className="qsm__filter__classgroup">
523
- {qsmType !== PortalQsmType.Accommodation &&
524
- qsmType !== PortalQsmType.Car &&
525
- qsmType !== PortalQsmType.Ticket &&
526
- qsmType !== PortalQsmType.Cruise &&
527
- qsmType !== PortalQsmType.Transfer &&
528
- qsmType !== PortalQsmType.GroupTour &&
529
- askTravelClass && <TravelClassPicker />}
530
- {qsmType !== PortalQsmType.Multidestination &&
531
- qsmType !== PortalQsmType.Car &&
532
- qsmType !== PortalQsmType.Flight &&
533
- qsmType !== PortalQsmType.Transfer &&
534
- askTravelType && <TravelTypePicker />}
535
- {askNationality && <TravelNationalityPicker />}
536
538
  </div>
537
- </div>
539
+ )}
538
540
  <div className="qsm__input-group">
539
541
  {/* TODO, determine which fields to show for what type of QSM */}
540
542
  {(qsmType == PortalQsmType.Flight || qsmType == PortalQsmType.AccommodationAndFlight) && originDestinationField && (
@@ -551,6 +553,110 @@ const QSMContainer: React.FC = () => {
551
553
 
552
554
  {askTravelers && <TravelInputGroup />}
553
555
 
556
+ {isMobile && (
557
+ <div className="qsm__filter">
558
+ {(qsmType === PortalQsmType.Accommodation || qsmType === PortalQsmType.AccommodationAndFlight || qsmType === PortalQsmType.GroupTour) && (
559
+ <div className="radiobutton-group qsm__filter__inputgroup">
560
+ <div className="radiobutton">
561
+ <label className="radiobutton__label">
562
+ <input
563
+ type="radio"
564
+ name="numberOfAccommodations"
565
+ // onChange={handleMainBookerChange}
566
+ // onBlur={formik.handleBlur}
567
+ value=""
568
+ checked={true}
569
+ readOnly
570
+ className="radiobutton__input"
571
+ />
572
+ <span>{translations.QSM.ONE_ACCOMMODATION}</span>
573
+ </label>
574
+ </div>
575
+ <div className="radiobutton">
576
+ <label className="radiobutton__label">
577
+ <input
578
+ type="radio"
579
+ name="numberOfAccommodations"
580
+ // onChange={handleMainBookerChange}
581
+ // onBlur={formik.handleBlur}
582
+ value=""
583
+ className="radiobutton__input"
584
+ disabled={true}
585
+ />
586
+ <span>{translations.QSM.MULTIPLE_ACCOMMODATIONS}</span>
587
+ </label>
588
+ </div>
589
+ </div>
590
+ )}
591
+ {qsmType === PortalQsmType.Flight && (
592
+ <div className="radiobutton-group qsm__filter__inputgroup">
593
+ {allowOneWay && (
594
+ <div className="radiobutton">
595
+ <label className="radiobutton__label">
596
+ <input
597
+ type="radio"
598
+ name="tripType"
599
+ value="oneway"
600
+ checked={tripType === 'oneway'}
601
+ onChange={() => handleTripTypeChange('oneway')}
602
+ className="radiobutton__input"
603
+ />
604
+ <span>{translations.QSM.ONEWAY}</span>
605
+ </label>
606
+ </div>
607
+ )}
608
+
609
+ {allowRoundtrip && (
610
+ <div className="radiobutton">
611
+ <label className="radiobutton__label">
612
+ <input
613
+ type="radio"
614
+ name="tripType"
615
+ value="roundtrip"
616
+ checked={tripType === 'roundtrip'}
617
+ onChange={() => handleTripTypeChange('roundtrip')}
618
+ className="radiobutton__input"
619
+ />
620
+ <span>{translations.QSM.ROUNDTRIP}</span>
621
+ </label>
622
+ </div>
623
+ )}
624
+
625
+ {allowOpenJaw && (
626
+ <div className="radiobutton">
627
+ <label className="radiobutton__label">
628
+ <input
629
+ type="radio"
630
+ name="tripType"
631
+ value="openjaw"
632
+ checked={tripType === 'openjaw'}
633
+ onChange={() => handleTripTypeChange('openjaw')}
634
+ className="radiobutton__input"
635
+ />
636
+ <span>{translations.QSM.OPENJAW}</span>
637
+ </label>
638
+ </div>
639
+ )}
640
+ </div>
641
+ )}
642
+ <div className="qsm__filter__classgroup">
643
+ {qsmType !== PortalQsmType.Accommodation &&
644
+ qsmType !== PortalQsmType.Car &&
645
+ qsmType !== PortalQsmType.Ticket &&
646
+ qsmType !== PortalQsmType.Cruise &&
647
+ qsmType !== PortalQsmType.Transfer &&
648
+ qsmType !== PortalQsmType.GroupTour &&
649
+ askTravelClass && <TravelClassPicker />}
650
+ {qsmType !== PortalQsmType.Multidestination &&
651
+ qsmType !== PortalQsmType.Car &&
652
+ qsmType !== PortalQsmType.Flight &&
653
+ qsmType !== PortalQsmType.Transfer &&
654
+ askTravelType && <TravelTypePicker />}
655
+ {askNationality && <TravelNationalityPicker />}
656
+ </div>
657
+ </div>
658
+ )}
659
+
554
660
  <button type="button" className="cta" onClick={handleSubmit}>
555
661
  {submitIcon && submitIcon.toString().length > 0 && <span>{submitIcon}</span>}
556
662
  <span>{translations.QSM.CONFIRM}</span>
@@ -14,7 +14,6 @@ interface ItemPickerProps {
14
14
  const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeholder, classModifier, valueFormatter, onPick }) => {
15
15
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
16
16
  const dropdownRef = useRef<HTMLDivElement | null>(null);
17
- const toggleButtonRef = useRef<HTMLButtonElement | null>(null);
18
17
 
19
18
  const handlePick = (picked: string, id?: string) => {
20
19
  setIsDropdownOpen(false);
@@ -37,11 +36,8 @@ const ItemPicker: React.FC<ItemPickerProps> = ({ items, selection, label, placeh
37
36
  return (
38
37
  <div className={'dropdown__input ' + classModifier}>
39
38
  <span className="dropdown__label">{label}</span>
40
- <div className="dropdown">
41
- <button
42
- className={`dropdown-toggle ${isDropdownOpen ? 'dropdown-toggle--open' : ''}`}
43
- onClick={() => setIsDropdownOpen((prev) => !prev)}
44
- ref={toggleButtonRef}>
39
+ <div className="dropdown" ref={dropdownRef}>
40
+ <button className={`dropdown-toggle ${isDropdownOpen ? 'dropdown-toggle--open' : ''}`} onClick={() => setIsDropdownOpen((prev) => !prev)}>
45
41
  <span>{selection || placeholder}</span>
46
42
  <span className="arrow">&#9662;</span>
47
43
  </button>
@@ -35,6 +35,8 @@ const MobileFilterModal: React.FC = () => {
35
35
  destination
36
36
  } = useSelector((state: QSMRootState) => state.qsm);
37
37
 
38
+ const searchInputRef = useRef<HTMLInputElement>(null);
39
+
38
40
  const [inputValue, setInputValue] = useState('');
39
41
 
40
42
  /* ---------------------------------------------------------------- */
@@ -48,6 +50,18 @@ const MobileFilterModal: React.FC = () => {
48
50
  }
49
51
  }, [activeSearchFieldProps]);
50
52
 
53
+ useEffect(() => {
54
+ if (mobileFilterType !== 'search') return;
55
+
56
+ requestAnimationFrame(() => {
57
+ const input = searchInputRef.current;
58
+ if (!input) return;
59
+
60
+ input.focus();
61
+ input.setSelectionRange(input.value.length, input.value.length);
62
+ });
63
+ }, [mobileFilterType, activeSearchFieldProps?.fieldKey]);
64
+
51
65
  /* ---------------------------------------------------------------- */
52
66
  /* Helpers */
53
67
  /* ---------------------------------------------------------------- */
@@ -229,12 +243,12 @@ const MobileFilterModal: React.FC = () => {
229
243
  <div className="qsm__double-input qsm__double-input--search-modal">
230
244
  <label className="qsm__input-wrapper">
231
245
  <input
246
+ ref={searchInputRef}
232
247
  type="text"
233
248
  id="search"
234
249
  value={inputValue}
235
250
  onClick={(e) => e.stopPropagation()}
236
251
  onChange={(e) => handleInputChange(e.target.value)}
237
- // onChange={(e) => handleLocationChange(e.target.value)}
238
252
  className="qsm__input qsm__input--modal qsm__from-to u-ps-2"
239
253
  placeholder={activeSearchFieldProps.placeholder}
240
254
  />
@@ -0,0 +1,48 @@
1
+ import React, { useContext } from 'react';
2
+ import { getTranslations } from '../../../shared/utils/localization-util';
3
+ import { SearchResultsRootState } from '../../store/search-results-store';
4
+ import { useDispatch, useSelector } from 'react-redux';
5
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
6
+ import BookingPanel from '../../../shared/booking/BookingPanel';
7
+ import StepIndicators from '../../../shared/booking/StepIndicators';
8
+ import WLSidebar from './wl-sidebar';
9
+ import { SearchSeed } from '../../types';
10
+
11
+ interface BookPackagingEntryProps {
12
+ activeSearchSeed: SearchSeed | null;
13
+ }
14
+
15
+ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed }) => {
16
+ const context = useContext(SearchResultsConfigurationContext);
17
+ if (!context) {
18
+ return null;
19
+ }
20
+ const translations = getTranslations(context.languageCode ?? 'en-GB');
21
+ const { currentStep } = useSelector((state: SearchResultsRootState) => state.searchResults);
22
+ const dispatch = useDispatch();
23
+
24
+ const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
25
+
26
+ return (
27
+ <div className="booking">
28
+ <div className="booking__content">
29
+ <BookingPanel
30
+ currentStep={currentStep}
31
+ stepLabels={stepLabels}
32
+ StepIndicatorsComponent={StepIndicators}
33
+ renderTitle={(step) => (
34
+ <>
35
+ {step + 1}.&nbsp;{stepLabels[step]}
36
+ </>
37
+ )}>
38
+ {/* Panel body content goes here */}
39
+ <div></div>
40
+ </BookingPanel>
41
+ <div className="backdrop" id="backdrop"></div>
42
+ <WLSidebar activeSearchSeed={activeSearchSeed} />
43
+ </div>
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default BookPackagingEntry;
@@ -0,0 +1,165 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { formatDate, getDateOnlyTime, getTranslations } from '../../../shared/utils/localization-util';
3
+ import { SearchResultsRootState } from '../../store/search-results-store';
4
+ import { useSelector } from 'react-redux';
5
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
6
+ import SharedSidebar from '../../../shared/booking/Sidebar';
7
+
8
+ import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
9
+ import { first, last, sum } from 'lodash';
10
+ import { SearchSeed } from '../../types';
11
+ import { getTravelersText } from '../../../booking-wizard/features/sidebar/sidebar-util';
12
+ import { RoomTraveler } from '../../../booking-wizard/types';
13
+
14
+ import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingEntryLine } from '@qite/tide-client';
15
+ import Spinner from '../spinner/spinner';
16
+
17
+ interface WLSidebarProps {
18
+ activeSearchSeed: SearchSeed | null;
19
+ }
20
+
21
+ const mapToSidebarFlightMetaData = (entryLine: PackagingEntryLine) => {
22
+ return {
23
+ flightLines: entryLine.flightInformation?.flightLines.map((f) => ({
24
+ number: f.flightNumber,
25
+ departureDate: f.departureDate,
26
+ departureAirport: f.departureAirportCode,
27
+ departureAirportDescription: f.departureAirportDescription,
28
+ departureTime: f.departureTime,
29
+ arrivalDate: f.arrivalDate,
30
+ arrivalAirport: f.arrivalAirportCode,
31
+ arrivalAirportDescription: f.arrivalAirportDescription,
32
+ arrivalTime: f.arrivalTime,
33
+ flightClass: '',
34
+ travelClass: '',
35
+ airline: f.airlineDescription,
36
+ airlineCode: f.airlineCode,
37
+ operatingAirlineCode: f.operatingAirlineCode,
38
+ operatingAirlineDescription: f.operatingAirlineDescription,
39
+ durationInTicks: f.durationInTicks
40
+ })),
41
+ luggageIncluded: false, // Not present in editablePackagingEntry, set default
42
+ bagageAllowed: false, // Not present in editablePackagingEntry, set default
43
+ bagage: '', // Not present in editablePackagingEntry, set default
44
+ mealIncluded: false, // Not present in editablePackagingEntry, set default
45
+ meal: '', // Not present in editablePackagingEntry, set default
46
+ durationInTicks: entryLine.flightInformation?.flightLines.reduce((s, { durationInTicks }) => s + (durationInTicks ?? 0), 0)
47
+ } as BookingPackageFlightMetaData;
48
+ };
49
+
50
+ const selectSeparatePackagePriceDetails = (priceDetails: BookingPriceDetail[]) => {
51
+ const result: BookingPriceDetail[] = [];
52
+ const filteredPriceDetails = priceDetails.filter((priceDetail) => priceDetail.isSeparate);
53
+
54
+ filteredPriceDetails.forEach((priceDetail) => {
55
+ const priceDetailToMerge = result.find(
56
+ (x) => x.productCode === priceDetail.productCode && x.accommodationCode === priceDetail.accommodationCode && x.productType === priceDetail.productType
57
+ );
58
+ if (priceDetailToMerge) {
59
+ priceDetailToMerge.total += priceDetail.total;
60
+ priceDetailToMerge.price += priceDetail.price;
61
+ } else {
62
+ result.push(Object.assign({}, priceDetail));
63
+ }
64
+ });
65
+
66
+ return result;
67
+ };
68
+
69
+ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
70
+ const context = useContext(SearchResultsConfigurationContext);
71
+ if (!context) {
72
+ return null;
73
+ }
74
+ const translations = getTranslations(context.languageCode ?? 'en-GB');
75
+ const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
76
+
77
+ // Map editablePackagingEntry to sidebar props (example, adjust as needed)
78
+ if (!editablePackagingEntry) {
79
+ return null;
80
+ }
81
+
82
+ console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
83
+ console.log('priceDetails in WLSidebar:', priceDetails);
84
+
85
+ const sortedLines = useMemo(() => {
86
+ return [...(editablePackagingEntry?.lines ?? [])].sort((a, b) => {
87
+ const dateA = getDateOnlyTime(a.from);
88
+ const dateB = getDateOnlyTime(b.from);
89
+
90
+ if (dateA !== dateB) {
91
+ return dateA - dateB;
92
+ }
93
+
94
+ return (a.order ?? Infinity) - (b.order ?? Infinity);
95
+ });
96
+ }, [editablePackagingEntry]);
97
+
98
+ const firstEntryLine = first(sortedLines);
99
+ const accommodationLines = editablePackagingEntry.lines.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE);
100
+ const accommodationLine = first(accommodationLines) ?? firstEntryLine;
101
+
102
+ const location =
103
+ accommodationLine?.location?.name ??
104
+ accommodationLine?.oord?.name ??
105
+ accommodationLine?.region?.name ??
106
+ accommodationLine?.country?.name ??
107
+ firstEntryLine?.location?.name;
108
+
109
+ const rooms =
110
+ activeSearchSeed?.rooms.map((room) => {
111
+ const adults = room.pax
112
+ .filter((p) => p.age && p.age >= 18)
113
+ .map((p) => {
114
+ return { id: p.id, age: p.age } as RoomTraveler;
115
+ });
116
+ const children = room.pax
117
+ .filter((p) => p.age && p.age < 18)
118
+ .map((p) => {
119
+ return { id: p.id, age: p.age } as RoomTraveler;
120
+ });
121
+ return { adults, children };
122
+ }) || [];
123
+
124
+ const travelerRooms = getTravelersText(rooms, translations);
125
+
126
+ const flightSegments = sortedLines.filter(
127
+ (line) => line.serviceType === FLIGHT_SERVICE_TYPE && line.flightInformation && Array.isArray(line.flightInformation.flightLines)
128
+ );
129
+ const outboundFlight = first(flightSegments);
130
+ const returnFlight = flightSegments.length > 1 ? last(flightSegments) : undefined;
131
+ const outboundFlightMetaData = outboundFlight ? mapToSidebarFlightMetaData(outboundFlight) : undefined;
132
+ const returnFlightMetaData = returnFlight ? mapToSidebarFlightMetaData(returnFlight) : undefined;
133
+
134
+ const basePrice = sum(priceDetails?.details.filter((pd) => pd.isInPackage).map((pd) => pd.price * pd.amount) ?? []);
135
+ const separateExtraPriceDetails = priceDetails?.details.filter((pd) => !pd.isInPackage && pd.isSeparate) ?? [];
136
+ const totalPrice = sum([basePrice, ...separateExtraPriceDetails.map((priceDetail) => priceDetail.price * priceDetail.amount)]);
137
+
138
+ const includedCosts = selectSeparatePackagePriceDetails(priceDetails?.details.filter((pd) => pd.isInPackage) ?? []);
139
+ return (
140
+ <SharedSidebar
141
+ productName={location ?? ''}
142
+ thumbnailUrl={context.destinationImage?.url}
143
+ translations={translations}
144
+ travelerRooms={travelerRooms}
145
+ startDateText={first(sortedLines)?.from && formatDate(new Date(first(sortedLines)!.from))}
146
+ endDateText={last(sortedLines)?.to && formatDate(new Date(last(sortedLines)!.to))}
147
+ isLoading={!editablePackagingEntry || !priceDetails}
148
+ loaderComponent={<Spinner />}
149
+ departureFlightMetaData={outboundFlightMetaData}
150
+ returnFlightMetaData={returnFlightMetaData}
151
+ includedServiceTypes={editablePackagingEntry.lines.map((line) => line.serviceType)}
152
+ packagingAccommodations={accommodationLines}
153
+ basePrice={basePrice}
154
+ commission={priceDetails?.commission}
155
+ totalPrice={totalPrice}
156
+ includedCosts={includedCosts}
157
+ extraCosts={separateExtraPriceDetails}
158
+ deposit={priceDetails?.deposit}
159
+ isUnavailable={false}
160
+ agent={context.agentId}
161
+ />
162
+ );
163
+ };
164
+
165
+ export default WLSidebar;
@@ -93,10 +93,14 @@ const DayByDayExcursions: React.FC<DayByDayExcursionsProps> = () => {
93
93
  <React.Fragment key={dayKey}>
94
94
  <div className="search__results__label search__results__label--secondary">
95
95
  <div className="search__results__label__date">
96
- <Icon name="ui-excursion" height={16} fill="white" />
96
+ <p className="search__results__label__date-date">{format(day, 'd', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
97
+ <p>{format(day, 'MMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
97
98
  </div>
98
99
  <div className="search__results__label__text">
99
- <h3>{format(day, 'EEEE d MMMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</h3>
100
+ <Icon name="ui-excursion" height={16} />
101
+ <h3>
102
+ {translations.SRP.SELECT} <strong>{translations.SRP.EXCURSION}</strong>
103
+ </h3>
100
104
  </div>
101
105
  </div>
102
106
 
@@ -122,7 +122,7 @@ const ExcursionResults: React.FC<ExcursionResultsProps> = ({ isFlyIn, activeSear
122
122
  };
123
123
 
124
124
  return isLoading ? (
125
- <Spinner />
125
+ <Spinner label={translations.SRP.LOADING_EXCURSIONS} />
126
126
  ) : (
127
127
  <div className="flyin__content flyin__content--columns">
128
128
  {/* <Filters