@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.
- package/build/build-cjs/index.js +1607 -886
- package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
- package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +5 -1
- package/build/build-cjs/src/shared/booking/BookingPanel.d.ts +13 -0
- package/build/build-cjs/src/shared/booking/Sidebar.d.ts +34 -0
- package/build/build-cjs/src/shared/booking/StepIndicators.d.ts +7 -0
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +1591 -881
- package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
- package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +5 -1
- package/build/build-esm/src/shared/booking/BookingPanel.d.ts +13 -0
- package/build/build-esm/src/shared/booking/Sidebar.d.ts +34 -0
- package/build/build-esm/src/shared/booking/StepIndicators.d.ts +7 -0
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +1 -1
- package/src/booking-wizard/components/step-indicator.tsx +10 -31
- package/src/booking-wizard/components/step-route.tsx +39 -14
- package/src/booking-wizard/features/sidebar/index.tsx +10 -4
- package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
- package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
- package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
- package/src/content/components/image-with-text.tsx +0 -1
- package/src/qsm/components/QSMContainer/qsm-container.tsx +189 -83
- package/src/qsm/components/item-picker/index.tsx +2 -6
- package/src/qsm/components/mobile-filter-modal/index.tsx +15 -1
- package/src/search-results/components/book-packaging-entry/index.tsx +48 -0
- package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +165 -0
- package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
- package/src/search-results/components/excursions/excursion-results.tsx +1 -1
- package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
- package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
- package/src/search-results/components/itinerary/index.tsx +13 -12
- package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +239 -204
- package/src/search-results/components/spinner/spinner.tsx +12 -4
- package/src/search-results/store/search-results-slice.ts +16 -2
- package/src/shared/booking/BookingPanel.tsx +25 -0
- package/src/shared/booking/Sidebar.tsx +432 -0
- package/src/shared/booking/StepIndicators.tsx +30 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
- package/src/shared/components/flyin/flights-flyin.tsx +1 -1
- package/src/shared/components/flyin/flyin.tsx +12 -4
- package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
- package/src/shared/components/icon.tsx +13 -0
- package/src/shared/translations/ar-SA.json +7 -1
- package/src/shared/translations/da-DK.json +7 -1
- package/src/shared/translations/de-DE.json +7 -1
- package/src/shared/translations/en-GB.json +8 -2
- package/src/shared/translations/es-ES.json +7 -1
- package/src/shared/translations/fr-BE.json +7 -1
- package/src/shared/translations/fr-FR.json +7 -1
- package/src/shared/translations/is-IS.json +7 -1
- package/src/shared/translations/it-IT.json +7 -1
- package/src/shared/translations/ja-JP.json +7 -1
- package/src/shared/translations/nl-BE.json +7 -1
- package/src/shared/translations/nl-NL.json +7 -1
- package/src/shared/translations/no-NO.json +7 -1
- package/src/shared/translations/pl-PL.json +7 -1
- package/src/shared/translations/pt-PT.json +7 -1
- package/src/shared/translations/sv-SE.json +7 -1
- package/src/shared/utils/localization-util.ts +8 -0
- package/styles/components/_footer.scss +2 -8
- package/styles/components/_loader.scss +82 -0
- package/styles/components/_search.scss +14 -7
- package/styles/content-blocks-variables.scss +14 -14
- /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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="
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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.
|
|
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="
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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.
|
|
467
|
+
<span>{translations.QSM.MULTIPLE_ACCOMMODATIONS}</span>
|
|
501
468
|
</label>
|
|
502
469
|
</div>
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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">▾</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}. {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
|
-
<
|
|
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
|
-
<
|
|
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
|