@qite/tide-booking-component 1.4.82 → 1.4.83

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 (51) hide show
  1. package/.husky/pre-commit +1 -2
  2. package/build/build-cjs/index.js +4731 -252
  3. package/build/build-cjs/src/search-results/components/item-picker/index.d.ts +1 -1
  4. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -3
  5. package/build/build-cjs/src/search-results/utils/flight-utils.d.ts +1 -2
  6. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +7 -3
  7. package/build/build-cjs/src/shared/utils/localization-util.d.ts +5 -0
  8. package/build/build-esm/index.js +4735 -246
  9. package/build/build-esm/src/search-results/components/item-picker/index.d.ts +1 -1
  10. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -3
  11. package/build/build-esm/src/search-results/utils/flight-utils.d.ts +1 -2
  12. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +7 -3
  13. package/build/build-esm/src/shared/utils/localization-util.d.ts +5 -0
  14. package/package.json +3 -1
  15. package/src/booking-wizard/features/confirmation/confirmation.tsx +1 -1
  16. package/src/booking-wizard/features/error/error.tsx +2 -1
  17. package/src/booking-wizard/features/flight-options/index.tsx +1 -1
  18. package/src/booking-wizard/features/product-options/options-form.tsx +2 -2
  19. package/src/booking-wizard/features/room-options/index.tsx +1 -1
  20. package/src/booking-wizard/features/summary/summary.tsx +1 -1
  21. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +1 -1
  22. package/src/search-results/components/group-tour/group-tour-results.tsx +11 -3
  23. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +114 -131
  24. package/src/search-results/components/hotel/hotel-card.tsx +2 -1
  25. package/src/search-results/components/item-picker/index.tsx +1 -1
  26. package/src/search-results/components/round-trip/round-trip-results.tsx +14 -4
  27. package/src/search-results/components/search-results-container/flight-search-results.tsx +4 -9
  28. package/src/search-results/components/search-results-container/search-results-container.tsx +45 -20
  29. package/src/search-results/components/tab-views/index.tsx +10 -7
  30. package/src/search-results/store/search-results-slice.ts +7 -7
  31. package/src/search-results/utils/flight-utils.ts +0 -13
  32. package/src/search-results/utils/search-results-utils.ts +31 -5
  33. package/src/shared/components/flyin/accommodation-flyin.tsx +169 -3
  34. package/src/shared/translations/ar-SA.json +3 -1
  35. package/src/shared/translations/da-DK.json +3 -1
  36. package/src/shared/translations/de-DE.json +3 -1
  37. package/src/shared/translations/en-GB.json +3 -1
  38. package/src/shared/translations/es-ES.json +3 -1
  39. package/src/shared/translations/fr-BE.json +3 -1
  40. package/src/shared/translations/fr-FR.json +3 -1
  41. package/src/shared/translations/is-IS.json +3 -1
  42. package/src/shared/translations/it-IT.json +3 -1
  43. package/src/shared/translations/ja-JP.json +3 -1
  44. package/src/shared/translations/nl-BE.json +3 -1
  45. package/src/shared/translations/nl-NL.json +3 -1
  46. package/src/shared/translations/no-NO.json +3 -1
  47. package/src/shared/translations/pl-PL.json +3 -1
  48. package/src/shared/translations/pt-PT.json +3 -1
  49. package/src/shared/translations/sv-SE.json +3 -1
  50. package/src/shared/utils/localization-util.ts +18 -0
  51. package/styles/components/_flyin.scss +121 -1
@@ -4,7 +4,7 @@ import { TravelClass, TravelType } from '../../../shared/types';
4
4
  interface ItemPickerProps {
5
5
  items: TravelType[] | TravelClass[] | SortByType[] | SortingOption[];
6
6
  selection: string | undefined;
7
- selectedSortByType?: SortByType;
7
+ selectedSortByType?: SortByType | null;
8
8
  label: string;
9
9
  placeholder: string;
10
10
  classModifier: string;
@@ -1,4 +1,4 @@
1
- import { ExtendedFlightSearchResponseItem, Filter } from '../types';
1
+ import { ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
2
2
  import { BookingPackage, BookingPackageItem, EntryLight, PackagingAccommodationResponse } from '@qite/tide-client/build/types';
3
3
  export interface SearchResultsState {
4
4
  results: BookingPackageItem[];
@@ -13,7 +13,7 @@ export interface SearchResultsState {
13
13
  entry: EntryLight | null;
14
14
  isLoading: boolean;
15
15
  filters: Filter[];
16
- sortKey: string | null;
16
+ selectedSortType: SortByType | null;
17
17
  activeTab: string | null;
18
18
  currentPage: number;
19
19
  flyInIsOpen: boolean;
@@ -54,7 +54,7 @@ export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPay
54
54
  setIsLoading: import('@reduxjs/toolkit').ActionCreatorWithPayload<boolean, 'searchResults/setIsLoading'>,
55
55
  setFilters: import('@reduxjs/toolkit').ActionCreatorWithPayload<Filter[], 'searchResults/setFilters'>,
56
56
  resetFilters: import('@reduxjs/toolkit').ActionCreatorWithPayload<Filter[], 'searchResults/resetFilters'>,
57
- setSortKey: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setSortKey'>,
57
+ setSortType: import('@reduxjs/toolkit').ActionCreatorWithPayload<SortByType | null, 'searchResults/setSortType'>,
58
58
  setActiveTab: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setActiveTab'>,
59
59
  setCurrentPage: import('@reduxjs/toolkit').ActionCreatorWithPayload<number, 'searchResults/setCurrentPage'>,
60
60
  resetSearchState: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<'searchResults/resetSearchState'>,
@@ -1,5 +1,5 @@
1
1
  import { FlightSearchResponseFlight, FlightSearchResponseFlightSegment } from '@qite/tide-client';
2
- import { ExtendedFlightSearchResponseItem, SortByType } from '../types';
2
+ import { ExtendedFlightSearchResponseItem } from '../types';
3
3
  import { DepartureRange } from '../../shared/types';
4
4
  export declare const getOutwardFlight: (flightResult: ExtendedFlightSearchResponseItem) => FlightSearchResponseFlight | undefined;
5
5
  export declare const getFlightSegments: (flight: FlightSearchResponseFlight | undefined) => FlightSearchResponseFlightSegment[];
@@ -13,4 +13,3 @@ export declare const getNumberOfStopsLabel: (
13
13
  stopLabel: string
14
14
  ) => string;
15
15
  export declare const getDepartureRangeName: (translations: any, range: DepartureRange | undefined) => string;
16
- export declare const getSortingName: (translations: any, sortByType: SortByType) => string;
@@ -1,10 +1,14 @@
1
1
  import { BookingPackageItem, PackagingAccommodationResponse } from '@qite/tide-client';
2
- import { Filter, TideTag } from '../types';
2
+ import { Filter, SortByType, TideTag } from '../types';
3
3
  export declare const enrichFiltersWithResults: (results: BookingPackageItem[], filters: Filter[] | undefined, tags: TideTag[]) => Filter[];
4
4
  export declare const enrichFiltersWithPackageAccoResults: (
5
5
  results: PackagingAccommodationResponse[],
6
6
  filters: Filter[] | undefined,
7
7
  tags: TideTag[]
8
8
  ) => Filter[];
9
- export declare const applyFilters: (results: BookingPackageItem[], filters: Filter[]) => BookingPackageItem[];
10
- export declare const applyFiltersToPackageAccoResults: (results: PackagingAccommodationResponse[], filters: Filter[]) => PackagingAccommodationResponse[];
9
+ export declare const applyFilters: (results: BookingPackageItem[], filters: Filter[], sortBy: SortByType | null) => BookingPackageItem[];
10
+ export declare const applyFiltersToPackageAccoResults: (
11
+ results: PackagingAccommodationResponse[],
12
+ filters: Filter[],
13
+ sortBy: SortByType | null
14
+ ) => PackagingAccommodationResponse[];
@@ -3,6 +3,7 @@ export declare const defaultLanguage = 'nl-BE';
3
3
  export declare const formatPrice: (price: number, currencyCode: string, locale?: string) => string;
4
4
  import { DateStruct } from '@qite/tide-client';
5
5
  import { DepartureRange } from '../types';
6
+ import { SortByType } from '../../search-results/types';
6
7
  export declare const getTranslations: (language: string) => {
7
8
  STEPS: {
8
9
  PERSONAL_DETAILS: string;
@@ -172,6 +173,8 @@ export declare const getTranslations: (language: string) => {
172
173
  BELGIUM: string;
173
174
  NETHERLANDS: string;
174
175
  FRANCE: string;
176
+ DENMARK: string;
177
+ ITALY: string;
175
178
  };
176
179
  CHOOSE_AGENT_PLACEHOLDER: string;
177
180
  VALIDATION: {
@@ -414,3 +417,5 @@ export declare const minutesToHoursString: (totalMinutes: number) => string;
414
417
  export declare const rangeFromDateTimeInMinutes: (dateTime: Date | undefined) => DepartureRange;
415
418
  export declare const calculateNights: (fromDate: Date, toDate: Date) => number;
416
419
  export declare const calculateDays: (fromDate: Date, toDate: Date) => number;
420
+ export declare const getSortingName: (translations: any, sortByType: SortByType) => string;
421
+ export declare const findSortByType: (sortByTypes: SortByType[], sortKey: string, direction: string) => SortByType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-booking-component",
3
- "version": "1.4.82",
3
+ "version": "1.4.83",
4
4
  "description": "React Booking wizard & Booking product component for Tide",
5
5
  "main": "build/build-cjs/index.js",
6
6
  "types": "build/build-cjs/src/index.d.ts",
@@ -35,6 +35,7 @@
35
35
  "@rollup/plugin-json": "^4.1.0",
36
36
  "@rollup/plugin-node-resolve": "^13.0.2",
37
37
  "@types/flat": "^5.0.2",
38
+ "@types/he": "^1.2.3",
38
39
  "@types/lodash": "^4.14.171",
39
40
  "@types/react": "^18.2.0",
40
41
  "@types/react-dom": "^18.2.0",
@@ -72,6 +73,7 @@
72
73
  "uuid": "^11.1.0"
73
74
  },
74
75
  "dependencies": {
76
+ "he": "^1.2.0",
75
77
  "jwt-decode": "^4.0.0",
76
78
  "react-html-comment": "^2.0.16",
77
79
  "react-router-dom": "^6.30.3",
@@ -17,7 +17,7 @@ interface ConfirmationProps {}
17
17
  const Confirmation: React.FC<ConfirmationProps> = () => {
18
18
  const dispatch = useAppDispatch();
19
19
  const settings = useContext(SettingsContext);
20
- const navigate = useNavigate();
20
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
21
21
 
22
22
  const bookingNumber = useSelector(selectBookingNumber);
23
23
  const bookingQueryString = useSelector(selectBookingQueryString);
@@ -12,9 +12,10 @@ interface ErrorProps {}
12
12
 
13
13
  const Error: React.FC<ErrorProps> = () => {
14
14
  const dispatch = useAppDispatch();
15
- const navigate = useNavigate();
16
15
 
17
16
  const settings = useContext(SettingsContext);
17
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
18
+
18
19
  const bookingQueryString = useSelector(selectBookingQueryString);
19
20
 
20
21
  const tryAgainUrl = `${!settings.skipBasePathInRouting ? settings.basePath : ''}?${bookingQueryString}`;
@@ -28,7 +28,7 @@ interface FlightOptionsFormProps {}
28
28
 
29
29
  const FlightOptionsForm: React.FC<FlightOptionsFormProps> = () => {
30
30
  const settings = useContext(SettingsContext);
31
- const navigate = useNavigate();
31
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
32
32
 
33
33
  const translations = useSelector(selectTranslations);
34
34
  const dispatch = useAppDispatch();
@@ -63,7 +63,7 @@ interface OptionsFormProps {}
63
63
 
64
64
  const OptionsForm: React.FC<OptionsFormProps> = () => {
65
65
  const settings = useContext(SettingsContext);
66
- const navigate = useNavigate();
66
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
67
67
  const { token } = settings;
68
68
  const translations = useSelector(selectTranslations);
69
69
  const dispatch = useAppDispatch();
@@ -83,7 +83,7 @@ const OptionsForm: React.FC<OptionsFormProps> = () => {
83
83
  const travelersFirstStep = useSelector(selectTravelersFirstStep);
84
84
 
85
85
  // ROOMS
86
- const showRoomOptions = settings.options.showRoomOptions ?? settings.roomOptions.isHidden;
86
+ const showRoomOptions = settings?.options?.showRoomOptions ?? settings.roomOptions.isHidden;
87
87
  const packageRooms = useSelector(selectPackageRooms);
88
88
  const pax = useSelector(selectBookingPackagePax);
89
89
 
@@ -24,7 +24,7 @@ interface RoomOptionsFormProps {}
24
24
 
25
25
  const RoomOptionsForm: React.FC<RoomOptionsFormProps> = () => {
26
26
  const settings = useContext(SettingsContext);
27
- const navigate = useNavigate();
27
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
28
28
 
29
29
  const translations = useSelector(selectTranslations);
30
30
  const dispatch = useAppDispatch();
@@ -47,9 +47,9 @@ interface SummaryProps {}
47
47
 
48
48
  const Summary: React.FC<SummaryProps> = () => {
49
49
  const dispatch = useAppDispatch();
50
- const navigate = useNavigate();
51
50
 
52
51
  const settings = useContext(SettingsContext);
52
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
53
53
 
54
54
  const [isSubmitting, setIsSubmitting] = useState(false);
55
55
  const [checkboxes, setCheckboxes] = useState<SummaryCheckbox[] | undefined | null>(settings.summary?.checkboxes);
@@ -93,8 +93,8 @@ function createInitialValues(
93
93
 
94
94
  const TravelersForm: React.FC<TravelersFormProps> = () => {
95
95
  const dispatch = useAppDispatch();
96
- const navigate = useNavigate();
97
96
  const settings = useContext(SettingsContext);
97
+ const navigate = settings.skipRouter ? () => {} : useNavigate();
98
98
  const bookingQueryString = useSelector(selectBookingQueryString);
99
99
  const startDate = useSelector(selectStartDate);
100
100
  const formRooms = useSelector(selectFormRooms);
@@ -1,11 +1,12 @@
1
- import React, { useContext } from 'react';
1
+ import React, { useContext, useEffect } from 'react';
2
2
  import Spinner from '../spinner/spinner';
3
3
  import SearchResultsConfigurationContext from '../../search-results-configuration-context';
4
4
  import { getTranslations } from '../../../shared/utils/localization-util';
5
5
  import { SearchResultsRootState } from '../../store/search-results-store';
6
- import { useSelector } from 'react-redux';
6
+ import { useDispatch, useSelector } from 'react-redux';
7
7
  import { isEmpty } from 'lodash';
8
8
  import GroupTourCard from './group-tour-card';
9
+ import { setActiveTab } from '../../store/search-results-slice';
9
10
 
10
11
  interface GroupTourResultsProps {
11
12
  isLoading: boolean;
@@ -23,13 +24,20 @@ const GroupTourResults: React.FC<GroupTourResultsProps> = ({ isLoading }) => {
23
24
 
24
25
  const translations = getTranslations(context.languageCode ?? 'en-GB');
25
26
  const { filteredResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
27
+ const dispatch = useDispatch();
26
28
 
27
29
  if (isEmpty(filteredResults)) {
28
30
  return <div className="no-results">{translations.SRP.NO_RESULTS}</div>;
29
31
  }
30
32
 
33
+ useEffect(() => {
34
+ if (activeTab === 'compact') {
35
+ dispatch(setActiveTab('list'));
36
+ }
37
+ }, [activeTab]);
38
+
31
39
  return (
32
- <div className="search__results__cards search__results__cards--list">
40
+ <div className={`search__results__cards search__results__cards--${activeTab}`}>
33
41
  {filteredResults.map((result, index) => (
34
42
  <GroupTourCard key={index} result={result} />
35
43
  ))}
@@ -4,7 +4,7 @@ import Spinner from '../spinner/spinner';
4
4
  import HotelCard from './hotel-card';
5
5
  import { useSelector } from 'react-redux';
6
6
  import { SearchResultsRootState } from '../../store/search-results-store';
7
- import { BookingPackageItem, PackagingAccommodationResponse } from '@qite/tide-client/build/types';
7
+ import { BookingPackageItem, PackagingAccommodationResponse, PortalQsmType } from '@qite/tide-client';
8
8
  import { format, parseISO } from 'date-fns';
9
9
  import { calculateNights, formatPrice, getTranslations } from '../../../shared/utils/localization-util';
10
10
  import SearchResultsConfigurationContext from '../../search-results-configuration-context';
@@ -14,82 +14,145 @@ interface HotelAccommodationResultsProps {
14
14
  isLoading: boolean;
15
15
  }
16
16
 
17
- const renderResults = (
18
- results: PackagingAccommodationResponse[],
19
- context: SearchResultsConfiguration,
20
- cmsMap: Map<any, any>,
21
- activeTab: string | null,
22
- translations: any
23
- ) => {
17
+ const getLocation = (locationName?: string, regionName?: string, countryName?: string, fallback?: string): string => {
18
+ if (locationName || regionName) {
19
+ return `${locationName || regionName}${countryName ? `, ${countryName}` : ''}`;
20
+ }
21
+
22
+ return fallback || '';
23
+ };
24
+
25
+ const getBaseHotelResult = (
26
+ searchResult: {
27
+ code: string;
28
+ name: string;
29
+ locationName?: string;
30
+ regionName?: string;
31
+ countryName?: string;
32
+ price: number;
33
+ currencyCode: string;
34
+ },
35
+ cmsItem: any,
36
+ languageCode?: string,
37
+ translations?: any
38
+ ) => ({
39
+ type: 'hotel' as const,
40
+ code: searchResult.code,
41
+ title: cmsItem?.content?.general?.title || searchResult.name,
42
+ image: cmsItem?.content?.images?.thumbnailPicture?.url,
43
+ description: cmsItem?.content?.descriptions?.introductionTitle || '',
44
+ location: getLocation(searchResult.locationName, searchResult.regionName, searchResult.countryName, cmsItem?.parentItem?.name),
45
+ price: formatPrice(searchResult.price, searchResult.currencyCode, languageCode),
46
+ ctaText: translations?.SRP.VIEW_DETAILS
47
+ });
48
+
49
+ const mapBookingPackageResult = (searchResult: BookingPackageItem, cmsItem: any, languageCode?: string, translations?: any): HotelResult => {
50
+ return {
51
+ ...getBaseHotelResult(searchResult, cmsItem, languageCode, translations),
52
+ id: searchResult.productId,
53
+ days: `${calculateNights(searchResult.stayFromDate, searchResult.stayToDate)} ${translations?.SRP.NIGHTS}`,
54
+ accommodation: searchResult.accommodationName,
55
+ regime: searchResult.regimeName,
56
+ stars: cmsItem?.content?.general?.stars || searchResult.hotelStars
57
+ };
58
+ };
59
+
60
+ const mapPackagingAccoResult = (searchResult: PackagingAccommodationResponse, cmsItem: any, languageCode?: string, translations?: any): HotelResult => {
61
+ const selectedOption = first(searchResult.rooms)?.options?.find((x) => x.isSelected);
62
+
63
+ return {
64
+ ...getBaseHotelResult(searchResult, cmsItem, languageCode, translations),
65
+ days: `${calculateNights(new Date(searchResult.fromDate), new Date(searchResult.toDate))} ${translations?.SRP.NIGHTS}`,
66
+ accommodation: selectedOption?.accommodationName || '',
67
+ regime: selectedOption?.regimeName || '',
68
+ stars: cmsItem?.content?.general?.stars,
69
+ contents: searchResult.contents
70
+ };
71
+ };
72
+
73
+ const renderHotelResults = (results: HotelResult[], context: SearchResultsConfiguration, activeTab: string | null, translations: any) => {
24
74
  const renderedResults = results.map((result, index) => {
25
- const cmsItem = cmsMap.get(result.code);
26
- const mappedResult: HotelResult = mapSearchResult(result, cmsItem, context.languageCode, translations);
75
+ const key = `${result.id ?? result.code}-${index}`;
76
+
27
77
  if (context?.showCustomCards && context?.customCardRenderer) {
28
78
  return (
29
- <div key={`${mappedResult.id}-${index}`} className="search__result-card">
30
- {context.customCardRenderer(mappedResult)}
79
+ <div key={key} className="search__result-card">
80
+ {context.customCardRenderer(result)}
31
81
  </div>
32
82
  );
33
83
  }
34
- return <HotelCard key={`${mappedResult.id}-${index}`} result={mappedResult} translations={translations} />;
84
+
85
+ return <HotelCard key={key} result={result} translations={translations} />;
35
86
  });
36
87
 
37
88
  return <div className={`search__results__cards ${activeTab ? `search__results__cards--${activeTab}` : ''}`}>{renderedResults}</div>;
38
89
  };
39
90
 
40
- const mapSearchResult = (searchResult: PackagingAccommodationResponse, cmsItem: any, languageCode?: string, translations?: any): HotelResult => {
41
- return {
42
- type: 'hotel',
43
- code: searchResult.code,
44
- title: cmsItem?.content?.general?.title || searchResult.name,
45
- image: cmsItem?.content?.images?.thumbnailPicture?.url,
46
- description: cmsItem?.content?.descriptions?.introductionTitle || '',
47
- location:
48
- searchResult.locationName || searchResult.regionName
49
- ? `${searchResult.locationName || searchResult.regionName}${searchResult.countryName && `, ${searchResult.countryName}`}`
50
- : cmsItem?.parentItem?.name || '',
51
- price: formatPrice(searchResult.price, searchResult.currencyCode, languageCode),
52
- ctaText: translations?.SRP.VIEW_DETAILS,
53
- days: `${calculateNights(new Date(searchResult.fromDate), new Date(searchResult.toDate))} ${translations?.SRP.NIGHTS}`,
54
- accommodation: first(searchResult.rooms)?.options?.find((x) => x.isSelected)?.accommodationName || '',
55
- regime: first(searchResult.rooms)?.options?.find((x) => x.isSelected)?.regimeName || '',
56
- stars: cmsItem?.content?.general?.stars,
57
- contents: searchResult.contents
58
- };
59
- };
60
-
61
91
  const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading }) => {
62
92
  const context = useContext(SearchResultsConfigurationContext);
63
- if (!context) {
64
- return;
65
- }
66
93
 
67
- const translations = getTranslations(context?.languageCode ?? 'en-GB');
68
-
69
- if (isLoading) {
70
- return <>{context?.customSpinner ?? <Spinner />}</>;
94
+ if (!context) {
95
+ return null;
71
96
  }
72
97
 
73
- const { filteredPackagingAccoResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
98
+ const translations = getTranslations(context.languageCode ?? 'en-GB');
74
99
 
75
- if (!filteredPackagingAccoResults.length) {
76
- return <div className="no-results">{translations.SRP.NO_RESULTS}</div>;
77
- }
100
+ const { filteredResults, filteredPackagingAccoResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
78
101
 
79
102
  const cmsMap = React.useMemo(() => {
80
103
  const map = new Map();
104
+
81
105
  context.cmsHotelData?.forEach((item) => {
82
106
  const code = item?.content?.general?.product?.code;
83
- if (code) map.set(code, item);
107
+ if (code) {
108
+ map.set(code, item);
109
+ }
84
110
  });
111
+
85
112
  return map;
86
113
  }, [context.cmsHotelData]);
87
114
 
88
- const firstResult = filteredPackagingAccoResults?.[0];
115
+ const mappedResults = React.useMemo(() => {
116
+ if (context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.searchConfiguration.enableManualPackaging) {
117
+ return filteredResults.map((result) => mapBookingPackageResult(result, cmsMap.get(result.code), context.languageCode, translations));
118
+ }
89
119
 
90
- const firstResultDay = firstResult?.fromDate ? format(parseISO(firstResult.fromDate), 'd') : null;
120
+ if (
121
+ context.searchConfiguration.qsmType === PortalQsmType.Accommodation ||
122
+ (context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && context.searchConfiguration.enableManualPackaging)
123
+ ) {
124
+ return filteredPackagingAccoResults.map((result) => mapPackagingAccoResult(result, cmsMap.get(result.code), context.languageCode, translations));
125
+ }
91
126
 
92
- const firstResultMonth = firstResult?.fromDate ? format(parseISO(firstResult.fromDate), 'MMM') : null;
127
+ return [];
128
+ }, [
129
+ context.searchConfiguration.qsmType,
130
+ context.searchConfiguration.enableManualPackaging,
131
+ context.languageCode,
132
+ filteredResults,
133
+ filteredPackagingAccoResults,
134
+ cmsMap,
135
+ translations
136
+ ]);
137
+
138
+ const firstBookingResult = filteredResults?.[0];
139
+ const firstPackagingResult = filteredPackagingAccoResults?.[0];
140
+
141
+ const firstResultDate =
142
+ context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.searchConfiguration.enableManualPackaging
143
+ ? firstBookingResult?.fromDate
144
+ : firstPackagingResult?.fromDate;
145
+
146
+ const firstResultDay = firstResultDate ? format(parseISO(firstResultDate), 'd') : null;
147
+ const firstResultMonth = firstResultDate ? format(parseISO(firstResultDate), 'MMM') : null;
148
+
149
+ if (isLoading) {
150
+ return <>{context.customSpinner ?? <Spinner />}</>;
151
+ }
152
+
153
+ if (mappedResults.length === 0) {
154
+ return <div className="no-results">{translations.SRP.NO_RESULTS}</div>;
155
+ }
93
156
 
94
157
  return (
95
158
  <>
@@ -104,90 +167,10 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
104
167
  </h3>
105
168
  </div>
106
169
  </div>
107
- {renderResults(filteredPackagingAccoResults, context, cmsMap, activeTab, translations)}
170
+
171
+ {renderHotelResults(mappedResults, context, activeTab, translations)}
108
172
  </>
109
173
  );
110
174
  };
111
175
 
112
176
  export default HotelAccommodationResults;
113
-
114
- // const showMocukups = (context: any) => {
115
- // const mockedHotelResults = [
116
- // {
117
- // type: 'hotel',
118
- // id: 2,
119
- // title: 'HTFSWILLCARL',
120
- // image: 'https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=1925&auto=format&fit=crop',
121
- // description: '2 persoons kamer',
122
- // location: 'Tenerif, Spanje',
123
- // price: '$2244',
124
- // ctaText: 'Bekijk details',
125
- // days: '7 nights',
126
- // flightInfo: null,
127
- // accommodation: 'Hotel XYZ',
128
- // regime: 'All-inclusive',
129
- // stars: 5
130
- // } as HotelResult,
131
- // {
132
- // type: 'hotel',
133
- // id: 3,
134
- // title: 'HTFSSOFTROCK',
135
- // image: 'https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=1925&auto=format&fit=crop',
136
- // description: '3 persoons kamer',
137
- // location: 'Tenerif, Spanje',
138
- // price: '$2244',
139
- // ctaText: 'Bekijk details',
140
- // days: '7 nights',
141
- // flightInfo: null,
142
- // accommodation: 'Hotel ABC',
143
- // regime: 'Half-board',
144
- // stars: 4
145
- // } as HotelResult,
146
- // {
147
- // type: 'hotel',
148
- // id: 4,
149
- // title: 'HTFSROYGAR',
150
- // image: 'https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=1925&auto=format&fit=crop',
151
- // description: '4 persoons kamer',
152
- // location: 'Tenerif, Spanje',
153
- // price: '$2496',
154
- // ctaText: 'Bekijk details',
155
- // days: '7 nights',
156
- // flightInfo: null,
157
- // accommodation: 'Hotel DEF',
158
- // regime: 'Full-board',
159
- // stars: 5
160
- // } as HotelResult,
161
- // {
162
- // type: 'hotel',
163
- // id: 5,
164
- // title: 'HTFSCONBEL',
165
- // image: 'https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=1925&auto=format&fit=crop',
166
- // description: '5 persoons kamer',
167
- // location: 'Tenerif, Spanje',
168
- // price: '$6784.8',
169
- // ctaText: 'Bekijk details',
170
- // days: '7 nights',
171
- // flightInfo: null,
172
- // accommodation: 'Hotel GHI',
173
- // regime: 'All-inclusive',
174
- // stars: 5
175
- // } as HotelResult
176
- // ] as HotelResult[];
177
- // return <>{renderMockupResults(mockedHotelResults, context)}</>;
178
- // };
179
-
180
- // const renderMockupResults = (results: any[], context: any) => {
181
- // const renderedResults = results.map((result, index) => {
182
- // if (context?.showCustomCards && context?.customCardRenderer) {
183
- // return (
184
- // <div key={`${result.id}-${index}`} className="search__result-card">
185
- // {context.customCardRenderer(result)}
186
- // </div>
187
- // );
188
- // }
189
- // return <HotelCard key={`${result.id}-${index}`} result={result} />;
190
- // });
191
-
192
- // return <div className="search__results__cards">{renderedResults}</div>;
193
- // };
@@ -4,6 +4,7 @@ import Icon from '../icon';
4
4
  import { useDispatch, useSelector } from 'react-redux';
5
5
  import { SearchResultsRootState } from '../../store/search-results-store';
6
6
  import { setSelectedPackagingAccoResult } from '../../store/search-results-slice';
7
+ import he from 'he';
7
8
 
8
9
  interface HotelCardProps {
9
10
  result: HotelResult;
@@ -27,7 +28,7 @@ const HotelCard: React.FC<HotelCardProps> = ({ result, translations }) => {
27
28
  onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}>
28
29
  <div
29
30
  dangerouslySetInnerHTML={{
30
- __html: result.contents
31
+ __html: he.decode(result.contents)
31
32
  }}></div>
32
33
  <div className="search__result-card__footer">
33
34
  <button
@@ -5,7 +5,7 @@ import { TravelClass, TravelType } from '../../../shared/types';
5
5
  interface ItemPickerProps {
6
6
  items: TravelType[] | TravelClass[] | SortByType[] | SortingOption[];
7
7
  selection: string | undefined;
8
- selectedSortByType?: SortByType;
8
+ selectedSortByType?: SortByType | null;
9
9
  label: string;
10
10
  placeholder: string;
11
11
  classModifier: string;
@@ -1,13 +1,23 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import Icon from '../icon';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { SearchResultsRootState } from '../../store/search-results-store';
5
+ import { setActiveTab } from '../../store/search-results-slice';
3
6
 
4
7
  interface RoundTripResultsProps {}
5
8
 
6
- // search__results__cards--list
7
-
8
9
  const RoundTripResults: React.FC<RoundTripResultsProps> = () => {
10
+ const { activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
11
+ const dispatch = useDispatch();
12
+
13
+ useEffect(() => {
14
+ if (activeTab === 'compact') {
15
+ dispatch(setActiveTab('list'));
16
+ }
17
+ }, [activeTab]);
18
+
9
19
  return (
10
- <div className="search__results__cards search__results__cards--list">
20
+ <div className={`search__results__cards search__results__cards--${activeTab}`}>
11
21
  <div className="search__result-card">
12
22
  <div className="search__result-card__allotment">
13
23
  <div className="search__result-card__allotment__img-wrapper">
@@ -4,12 +4,11 @@ import SearchResultsConfigurationContext from '../../search-results-configuratio
4
4
  import ItemPicker from '../item-picker';
5
5
 
6
6
  import Icon from '../icon';
7
- import { getTranslations } from '../../../shared/utils/localization-util';
7
+ import { findSortByType, getSortingName, getTranslations } from '../../../shared/utils/localization-util';
8
8
  import FlightSelection from '../flight/flight-selection/index';
9
9
  import { useFlightSearch } from '../flight/flight-search-context';
10
10
  import FlightFilters from '../filters/flight-filters';
11
11
  import { ExtendedFlightSearchResponseItem, FlightSelectionMode } from '../../types';
12
- import { getSortingName } from '../../utils/flight-utils';
13
12
  import Spinner from '../spinner/spinner';
14
13
  import { PortalQsmType } from '@qite/tide-client';
15
14
 
@@ -33,12 +32,8 @@ const FlightResultsContainer: React.FC<FlightResultsContainerProps> = ({ isMobil
33
32
  const [results, setResults] = React.useState<ExtendedFlightSearchResponseItem[]>([]);
34
33
  const [flightSelectionType, setFlightSelectionType] = useState<FlightSelectionMode>('independent');
35
34
 
36
- const findSortByType = (sortKey: string, direction: string) => {
37
- return sortByTypes.find((s) => s.label === sortKey && s.direction === direction) || sortByTypes[0];
38
- };
39
-
40
35
  const handleSortChange = (sortKey: string, direction?: string) => {
41
- setSelectedSortByType(findSortByType(sortKey, direction ?? 'asc'));
36
+ setSelectedSortByType(findSortByType(sortByTypes, sortKey, direction ?? 'asc'));
42
37
  };
43
38
 
44
39
  useEffect(() => {
@@ -77,7 +72,7 @@ const FlightResultsContainer: React.FC<FlightResultsContainerProps> = ({ isMobil
77
72
  label={translations.SRP.SORTBY}
78
73
  placeholder={translations.SRP.SORTBY}
79
74
  classModifier="travel-class-picker__items"
80
- valueFormatter={(value, direction) => getSortingName(translations, findSortByType(value, direction ?? 'asc'))}
75
+ valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
81
76
  onPick={handleSortChange}
82
77
  />
83
78
  )}
@@ -118,7 +113,7 @@ const FlightResultsContainer: React.FC<FlightResultsContainerProps> = ({ isMobil
118
113
  label={translations.SRP.SORTBY}
119
114
  placeholder={translations.SRP.SORTBY}
120
115
  classModifier="travel-class-picker__items"
121
- valueFormatter={(value, direction) => getSortingName(translations, findSortByType(value, direction ?? 'asc'))}
116
+ valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
122
117
  onPick={handleSortChange}
123
118
  />
124
119
  </div>