@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.
- package/.husky/pre-commit +1 -2
- package/build/build-cjs/index.js +4731 -252
- package/build/build-cjs/src/search-results/components/item-picker/index.d.ts +1 -1
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -3
- package/build/build-cjs/src/search-results/utils/flight-utils.d.ts +1 -2
- package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +7 -3
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +5 -0
- package/build/build-esm/index.js +4735 -246
- package/build/build-esm/src/search-results/components/item-picker/index.d.ts +1 -1
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -3
- package/build/build-esm/src/search-results/utils/flight-utils.d.ts +1 -2
- package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +7 -3
- package/build/build-esm/src/shared/utils/localization-util.d.ts +5 -0
- package/package.json +3 -1
- package/src/booking-wizard/features/confirmation/confirmation.tsx +1 -1
- package/src/booking-wizard/features/error/error.tsx +2 -1
- package/src/booking-wizard/features/flight-options/index.tsx +1 -1
- package/src/booking-wizard/features/product-options/options-form.tsx +2 -2
- package/src/booking-wizard/features/room-options/index.tsx +1 -1
- package/src/booking-wizard/features/summary/summary.tsx +1 -1
- package/src/booking-wizard/features/travelers-form/travelers-form.tsx +1 -1
- package/src/search-results/components/group-tour/group-tour-results.tsx +11 -3
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +114 -131
- package/src/search-results/components/hotel/hotel-card.tsx +2 -1
- package/src/search-results/components/item-picker/index.tsx +1 -1
- package/src/search-results/components/round-trip/round-trip-results.tsx +14 -4
- package/src/search-results/components/search-results-container/flight-search-results.tsx +4 -9
- package/src/search-results/components/search-results-container/search-results-container.tsx +45 -20
- package/src/search-results/components/tab-views/index.tsx +10 -7
- package/src/search-results/store/search-results-slice.ts +7 -7
- package/src/search-results/utils/flight-utils.ts +0 -13
- package/src/search-results/utils/search-results-utils.ts +31 -5
- package/src/shared/components/flyin/accommodation-flyin.tsx +169 -3
- package/src/shared/translations/ar-SA.json +3 -1
- package/src/shared/translations/da-DK.json +3 -1
- package/src/shared/translations/de-DE.json +3 -1
- package/src/shared/translations/en-GB.json +3 -1
- package/src/shared/translations/es-ES.json +3 -1
- package/src/shared/translations/fr-BE.json +3 -1
- package/src/shared/translations/fr-FR.json +3 -1
- package/src/shared/translations/is-IS.json +3 -1
- package/src/shared/translations/it-IT.json +3 -1
- package/src/shared/translations/ja-JP.json +3 -1
- package/src/shared/translations/nl-BE.json +3 -1
- package/src/shared/translations/nl-NL.json +3 -1
- package/src/shared/translations/no-NO.json +3 -1
- package/src/shared/translations/pl-PL.json +3 -1
- package/src/shared/translations/pt-PT.json +3 -1
- package/src/shared/translations/sv-SE.json +3 -1
- package/src/shared/utils/localization-util.ts +18 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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: (
|
|
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.
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
26
|
-
|
|
75
|
+
const key = `${result.id ?? result.code}-${index}`;
|
|
76
|
+
|
|
27
77
|
if (context?.showCustomCards && context?.customCardRenderer) {
|
|
28
78
|
return (
|
|
29
|
-
<div key={
|
|
30
|
-
{context.customCardRenderer(
|
|
79
|
+
<div key={key} className="search__result-card">
|
|
80
|
+
{context.customCardRenderer(result)}
|
|
31
81
|
</div>
|
|
32
82
|
);
|
|
33
83
|
}
|
|
34
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
if (isLoading) {
|
|
70
|
-
return <>{context?.customSpinner ?? <Spinner />}</>;
|
|
94
|
+
if (!context) {
|
|
95
|
+
return null;
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
const
|
|
98
|
+
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
74
99
|
|
|
75
|
-
|
|
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)
|
|
107
|
+
if (code) {
|
|
108
|
+
map.set(code, item);
|
|
109
|
+
}
|
|
84
110
|
});
|
|
111
|
+
|
|
85
112
|
return map;
|
|
86
113
|
}, [context.cmsHotelData]);
|
|
87
114
|
|
|
88
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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>
|