@qite/tide-booking-component 1.4.29 → 1.4.31
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 +621 -2822
- package/build/build-cjs/search-results/store/search-results-slice.d.ts +6 -3
- package/build/build-cjs/search-results/types.d.ts +9 -12
- package/build/build-esm/index.js +617 -2625
- package/build/build-esm/search-results/store/search-results-slice.d.ts +6 -3
- package/build/build-esm/search-results/types.d.ts +9 -12
- package/package.json +2 -3
- package/src/booking-product/components/dates.tsx +9 -4
- package/src/booking-wizard/components/step-indicator.tsx +11 -2
- package/src/booking-wizard/features/booking/booking-slice.ts +27 -2
- package/src/booking-wizard/features/booking/booking.tsx +32 -15
- package/src/booking-wizard/features/booking/selectors.ts +6 -0
- package/src/booking-wizard/features/flight-options/index.tsx +27 -3
- package/src/booking-wizard/features/product-options/option-room.tsx +1 -1
- package/src/booking-wizard/features/product-options/options-form.tsx +14 -4
- package/src/booking-wizard/features/room-options/room.tsx +1 -1
- package/src/booking-wizard/features/sidebar/index.tsx +4 -1
- package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -3
- package/src/booking-wizard/features/sidebar/sidebar.tsx +112 -104
- package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +1 -0
- package/src/booking-wizard/features/travelers-form/travelers-form.tsx +146 -10
- package/src/booking-wizard/settings-context.ts +2 -1
- package/src/booking-wizard/types.ts +1 -0
- package/src/qsm/components/search-input-group/index.tsx +17 -8
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +78 -83
- package/src/search-results/components/hotel/hotel-card.tsx +54 -24
- package/src/search-results/components/icon.tsx +13 -0
- package/src/search-results/components/item-picker/index.tsx +5 -7
- package/src/search-results/components/search-results-container/search-results-container.tsx +72 -117
- package/src/search-results/components/tab-views/index.tsx +22 -3
- package/src/search-results/features/flights/flight-search-results-self-contained.tsx +0 -13
- package/src/search-results/features/hotels/hotel-flight-search-results-self-contained.tsx +1 -8
- package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +1 -14
- package/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.tsx +1 -9
- package/src/search-results/store/search-results-slice.ts +11 -4
- package/src/search-results/types.ts +11 -16
- package/src/shared/translations/en-GB.json +5 -1
- package/src/shared/translations/fr-BE.json +5 -1
- package/src/shared/translations/nl-BE.json +5 -1
- package/styles/components/_form.scss +51 -2
- package/styles/components/_passenger-picker.scss +3 -2
- package/styles/components/_qsm.scss +1 -1
- package/styles/qsm/_qsm.scss +67 -6
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { Filter
|
|
1
|
+
import { Filter } from '../types';
|
|
2
|
+
import { BookingPackageItem } from '@qite/tide-client/build/types';
|
|
2
3
|
export interface SearchResultsState {
|
|
3
|
-
results:
|
|
4
|
+
results: BookingPackageItem[];
|
|
4
5
|
isLoading: boolean;
|
|
5
6
|
filters: Filter[];
|
|
6
7
|
sortKey: string | null;
|
|
8
|
+
activeTab: string | null;
|
|
7
9
|
currentPage: number;
|
|
8
10
|
}
|
|
9
11
|
export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<
|
|
10
12
|
{
|
|
11
|
-
results:
|
|
13
|
+
results: BookingPackageItem[];
|
|
12
14
|
},
|
|
13
15
|
'searchResults/setResults'
|
|
14
16
|
>,
|
|
@@ -16,6 +18,7 @@ export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPay
|
|
|
16
18
|
setFilters: import('@reduxjs/toolkit').ActionCreatorWithPayload<Filter[], 'searchResults/setFilters'>,
|
|
17
19
|
resetFilters: import('@reduxjs/toolkit').ActionCreatorWithPayload<Filter[], 'searchResults/resetFilters'>,
|
|
18
20
|
setSortKey: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setSortKey'>,
|
|
21
|
+
setActiveTab: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setActiveTab'>,
|
|
19
22
|
setCurrentPage: import('@reduxjs/toolkit').ActionCreatorWithPayload<number, 'searchResults/setCurrentPage'>,
|
|
20
23
|
resetSearchState: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<'searchResults/resetSearchState'>;
|
|
21
24
|
declare const _default: import('redux').Reducer<SearchResultsState, import('redux').AnyAction>;
|
|
@@ -19,8 +19,6 @@ export interface SearchResultsConfiguration {
|
|
|
19
19
|
showCustomCards?: boolean;
|
|
20
20
|
customCardRenderer?: (result: SearchResult) => ReactNode;
|
|
21
21
|
onResultClick?: (id: string) => void;
|
|
22
|
-
sortingOptions?: SortingOption[];
|
|
23
|
-
onSortChange?: (sortKey: string) => void;
|
|
24
22
|
showMapView?: boolean;
|
|
25
23
|
noResultsLabel?: string;
|
|
26
24
|
isLoading?: boolean;
|
|
@@ -38,6 +36,7 @@ export interface SearchResultsConfiguration {
|
|
|
38
36
|
loading?: string;
|
|
39
37
|
searchResultCTA?: string;
|
|
40
38
|
};
|
|
39
|
+
cmsHotelData?: any[];
|
|
41
40
|
}
|
|
42
41
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
43
42
|
export type FilterProperty = 'regime' | 'max-duration' | 'price' | 'rating' | 'theme';
|
|
@@ -59,15 +58,6 @@ export interface Filter {
|
|
|
59
58
|
selectedMax?: number;
|
|
60
59
|
selectedRating?: number;
|
|
61
60
|
}
|
|
62
|
-
export interface Sort {
|
|
63
|
-
label: string;
|
|
64
|
-
icon?: ReactNode;
|
|
65
|
-
}
|
|
66
|
-
export interface SortingOption {
|
|
67
|
-
key: 'price-asc' | 'price-desc' | 'duration-asc' | 'duration-desc' | 'rating-asc' | 'rating-desc';
|
|
68
|
-
label: string;
|
|
69
|
-
icon?: ReactNode;
|
|
70
|
-
}
|
|
71
61
|
export interface PaginationConfig {
|
|
72
62
|
totalResults: number;
|
|
73
63
|
currentPage: number;
|
|
@@ -81,9 +71,11 @@ export interface BaseSearchResult {
|
|
|
81
71
|
description?: string;
|
|
82
72
|
location?: string;
|
|
83
73
|
tags?: Tag[];
|
|
84
|
-
price: string;
|
|
74
|
+
price: string | number;
|
|
85
75
|
ctaText: string;
|
|
86
76
|
stars?: number;
|
|
77
|
+
accommodation?: string;
|
|
78
|
+
regime?: string;
|
|
87
79
|
}
|
|
88
80
|
export interface HotelResult extends BaseSearchResult {
|
|
89
81
|
type: 'hotel';
|
|
@@ -121,3 +113,8 @@ export interface TravelClass {
|
|
|
121
113
|
label: string;
|
|
122
114
|
icon?: ReactNode;
|
|
123
115
|
}
|
|
116
|
+
export interface SortingOption {
|
|
117
|
+
key: 'price-asc' | 'price-desc' | 'departure-date' | 'duration-asc' | 'duration-desc' | 'rating-asc' | 'rating-desc';
|
|
118
|
+
label: string;
|
|
119
|
+
icon?: ReactNode;
|
|
120
|
+
}
|
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.31",
|
|
4
4
|
"description": "React Booking wizard & Booking product component for Tide",
|
|
5
5
|
"main": "build/build-cjs/index.js",
|
|
6
6
|
"module": "build/build-esm/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@jsonurl/jsonurl": "^1.1.4",
|
|
29
29
|
"@popperjs/core": "^2.10.2",
|
|
30
|
-
"@qite/tide-client": "^1.1.
|
|
30
|
+
"@qite/tide-client": "^1.1.123",
|
|
31
31
|
"@reach/router": "^1.3.4",
|
|
32
32
|
"@reduxjs/toolkit": "^1.6.0",
|
|
33
33
|
"@rollup/plugin-commonjs": "^19.0.1",
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
"uuid": "^8.3.2"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@qite/tide-client": "^1.1.122",
|
|
76
75
|
"react-html-comment": "^2.0.16"
|
|
77
76
|
}
|
|
78
77
|
}
|
|
@@ -17,15 +17,20 @@ interface DatesProps {
|
|
|
17
17
|
const Dates: React.FC<DatesProps> = ({ value, duration, onChange }) => {
|
|
18
18
|
const { language } = useContext(SettingsContext);
|
|
19
19
|
const translations = getTranslations(language);
|
|
20
|
-
const mql = typeof window !== 'undefined' ? window.matchMedia('(min-width:
|
|
20
|
+
const mql = typeof window !== 'undefined' ? window.matchMedia('(min-width: 992px)') : undefined;
|
|
21
|
+
const mqm = typeof window !== 'undefined' ? window.matchMedia('(min-width: 768px)') : undefined;
|
|
21
22
|
|
|
22
23
|
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
|
|
23
24
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
24
25
|
const [panelActive, setPanelActive] = useState<boolean>(false);
|
|
25
26
|
|
|
26
27
|
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
|
27
|
-
placement: 'top',
|
|
28
|
+
placement: mql?.matches ? 'top' : 'bottom',
|
|
28
29
|
modifiers: [
|
|
30
|
+
{
|
|
31
|
+
name: 'flip',
|
|
32
|
+
enabled: false
|
|
33
|
+
},
|
|
29
34
|
{
|
|
30
35
|
name: 'offset',
|
|
31
36
|
options: {
|
|
@@ -100,9 +105,9 @@ const Dates: React.FC<DatesProps> = ({ value, duration, onChange }) => {
|
|
|
100
105
|
className={buildClassName([
|
|
101
106
|
'qsm__panel qsm__panel--bordered qsm__panel--dates-pricing',
|
|
102
107
|
panelActive && 'qsm__panel--active',
|
|
103
|
-
!
|
|
108
|
+
!mqm?.matches && 'qsm__panel--mobile'
|
|
104
109
|
])}
|
|
105
|
-
style={
|
|
110
|
+
style={mqm?.matches ? styles.popper : undefined}
|
|
106
111
|
{...attributes.popper}>
|
|
107
112
|
<DateRangePicker
|
|
108
113
|
fromDate={value?.fromDate}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import { useSelector } from 'react-redux';
|
|
3
3
|
import { buildClassName } from '../../shared/utils/class-util';
|
|
4
|
-
import { selectTranslations } from '../features/booking/selectors';
|
|
4
|
+
import { selectTranslations, selectTravelersFirstStep } from '../features/booking/selectors';
|
|
5
5
|
import SettingsContext from '../settings-context';
|
|
6
6
|
|
|
7
7
|
interface StepIndicatorsProps {
|
|
@@ -10,9 +10,15 @@ interface StepIndicatorsProps {
|
|
|
10
10
|
|
|
11
11
|
const StepIndicators: React.FC<StepIndicatorsProps> = ({ currentStep }) => {
|
|
12
12
|
const { flightOptions, roomOptions } = useContext(SettingsContext);
|
|
13
|
+
|
|
13
14
|
const translations = useSelector(selectTranslations);
|
|
15
|
+
const travelersFirstStep = useSelector(selectTravelersFirstStep);
|
|
14
16
|
|
|
15
17
|
const allSteps = [];
|
|
18
|
+
|
|
19
|
+
if (travelersFirstStep) {
|
|
20
|
+
allSteps.push(translations.STEPS.PERSONAL_DETAILS);
|
|
21
|
+
}
|
|
16
22
|
if (!flightOptions.isHidden) {
|
|
17
23
|
allSteps.push(translations.STEPS.FLIGHT_OPTIONS);
|
|
18
24
|
}
|
|
@@ -21,7 +27,10 @@ const StepIndicators: React.FC<StepIndicatorsProps> = ({ currentStep }) => {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
allSteps.push(translations.STEPS.EXTRA_OPTIONS);
|
|
24
|
-
|
|
30
|
+
|
|
31
|
+
if (!travelersFirstStep) {
|
|
32
|
+
allSteps.push(translations.STEPS.PERSONAL_DETAILS);
|
|
33
|
+
}
|
|
25
34
|
allSteps.push(translations.STEPS.SUMMARY);
|
|
26
35
|
allSteps.push(translations.STEPS.CONFIRMATION);
|
|
27
36
|
|
|
@@ -64,6 +64,9 @@ export interface BookingState {
|
|
|
64
64
|
accommodationViewId?: number;
|
|
65
65
|
accommodationViews?: { [key: string]: string };
|
|
66
66
|
isOption?: boolean;
|
|
67
|
+
travelersFirstStep: boolean;
|
|
68
|
+
isFetching?: boolean;
|
|
69
|
+
hasMounted: boolean;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
const initialState: BookingState = {
|
|
@@ -99,7 +102,10 @@ const initialState: BookingState = {
|
|
|
99
102
|
tagIds: [],
|
|
100
103
|
agentAdressId: undefined,
|
|
101
104
|
currentStep: OPTIONS_FORM_STEP,
|
|
102
|
-
translations: undefined
|
|
105
|
+
translations: undefined,
|
|
106
|
+
travelersFirstStep: false,
|
|
107
|
+
isFetching: false,
|
|
108
|
+
hasMounted: false
|
|
103
109
|
};
|
|
104
110
|
|
|
105
111
|
export const fetchPackage = createAsyncThunk('booking/fetchPackage', async (_, { dispatch }) => {
|
|
@@ -333,6 +339,12 @@ const bookingSlice = createSlice({
|
|
|
333
339
|
name: 'booking',
|
|
334
340
|
initialState,
|
|
335
341
|
reducers: {
|
|
342
|
+
setHasMounted(state, action: PayloadAction<boolean>) {
|
|
343
|
+
state.hasMounted = action.payload;
|
|
344
|
+
},
|
|
345
|
+
setIsFetching(state, action: PayloadAction<boolean>) {
|
|
346
|
+
state.isFetching = action.payload;
|
|
347
|
+
},
|
|
336
348
|
setOfficeId(state, action: PayloadAction<number>) {
|
|
337
349
|
state.officeId = action.payload;
|
|
338
350
|
},
|
|
@@ -429,6 +441,12 @@ const bookingSlice = createSlice({
|
|
|
429
441
|
},
|
|
430
442
|
setIsOption(state, action: PayloadAction<boolean>) {
|
|
431
443
|
state.isOption = action.payload;
|
|
444
|
+
},
|
|
445
|
+
setTravelersFirstStep(state, action: PayloadAction<boolean>) {
|
|
446
|
+
state.travelersFirstStep = action.payload;
|
|
447
|
+
},
|
|
448
|
+
setIsUnavailable(state, action: PayloadAction<boolean>) {
|
|
449
|
+
state.isUnavailable = action.payload;
|
|
432
450
|
}
|
|
433
451
|
},
|
|
434
452
|
extraReducers: (builder) => {
|
|
@@ -443,9 +461,12 @@ const bookingSlice = createSlice({
|
|
|
443
461
|
|
|
444
462
|
if (!action.payload.payload) {
|
|
445
463
|
state.isUnavailable = true;
|
|
464
|
+
/* state.package = undefined; */
|
|
446
465
|
return;
|
|
447
466
|
}
|
|
448
467
|
|
|
468
|
+
state.isUnavailable = false;
|
|
469
|
+
|
|
449
470
|
const bookingRooms = state.bookingAttributes?.rooms;
|
|
450
471
|
const flight = state.bookingAttributes?.flight;
|
|
451
472
|
const packageDetails = action.payload.payload;
|
|
@@ -562,6 +583,8 @@ export const {
|
|
|
562
583
|
setBookingNumber,
|
|
563
584
|
setIsRetry,
|
|
564
585
|
setFetchingPackage,
|
|
586
|
+
setIsFetching,
|
|
587
|
+
setHasMounted,
|
|
565
588
|
setPackage,
|
|
566
589
|
setPackageRooms,
|
|
567
590
|
setPackageOptionPax,
|
|
@@ -578,7 +601,9 @@ export const {
|
|
|
578
601
|
setPackageAirportGroups,
|
|
579
602
|
setFlights,
|
|
580
603
|
setAccommodationViewId,
|
|
581
|
-
setIsOption
|
|
604
|
+
setIsOption,
|
|
605
|
+
setTravelersFirstStep,
|
|
606
|
+
setIsUnavailable
|
|
582
607
|
} = bookingSlice.actions;
|
|
583
608
|
|
|
584
609
|
export default bookingSlice.reducer;
|
|
@@ -20,13 +20,15 @@ import {
|
|
|
20
20
|
setCalculateDeposit,
|
|
21
21
|
setGeneratePaymentUrl,
|
|
22
22
|
setIsRetry,
|
|
23
|
+
setIsUnavailable,
|
|
23
24
|
setLanguageCode,
|
|
24
25
|
setOfficeId,
|
|
25
26
|
setPackage,
|
|
26
27
|
setProductAttributes,
|
|
27
28
|
setSkipPayment,
|
|
28
29
|
setTagIds,
|
|
29
|
-
setTranslations
|
|
30
|
+
setTranslations,
|
|
31
|
+
setTravelersFirstStep
|
|
30
32
|
} from './booking-slice';
|
|
31
33
|
|
|
32
34
|
import { isEqual, isNil } from 'lodash';
|
|
@@ -51,7 +53,8 @@ import {
|
|
|
51
53
|
selectIsUnavailable,
|
|
52
54
|
selectPackageDetails,
|
|
53
55
|
selectProductAttributes,
|
|
54
|
-
selectTranslations
|
|
56
|
+
selectTranslations,
|
|
57
|
+
selectTravelersFirstStep
|
|
55
58
|
} from './selectors';
|
|
56
59
|
|
|
57
60
|
interface BookingProps {
|
|
@@ -95,8 +98,9 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
95
98
|
const bookingNumber = useSelector(selectBookingNumber);
|
|
96
99
|
const isRetry = useSelector(selectIsRetry);
|
|
97
100
|
const packageDetails = useSelector(selectPackageDetails);
|
|
98
|
-
const
|
|
101
|
+
const isUnavailable = useSelector(selectIsUnavailable);
|
|
99
102
|
const translations = useSelector(selectTranslations);
|
|
103
|
+
const travelersFirstStep = useSelector(selectTravelersFirstStep);
|
|
100
104
|
|
|
101
105
|
useEffect(() => {
|
|
102
106
|
return () => {
|
|
@@ -116,7 +120,18 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
116
120
|
const startDate = getDateFromParams(params, 'startDate');
|
|
117
121
|
const endDate = getDateFromParams(params, 'endDate');
|
|
118
122
|
const catalogueId = getNumberFromParams(params, 'catalogueId') ?? getNumberFromParams(params, 'catalog');
|
|
119
|
-
|
|
123
|
+
|
|
124
|
+
let rooms = getRoomsFromParams(params, 'rooms');
|
|
125
|
+
|
|
126
|
+
if (!rooms || !rooms.length) {
|
|
127
|
+
dispatch(setTravelersFirstStep(true));
|
|
128
|
+
rooms = [{ adults: 2, children: 0, childAges: [] }];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (travellers.travelersFirstStep === true) {
|
|
132
|
+
dispatch(setTravelersFirstStep(true));
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
const flight = getFlightsFromParams(params, 'flight');
|
|
121
136
|
const flightRouteId = getStringFromParams(params, 'flightRouteId');
|
|
122
137
|
const vendorConfigurationId = getNumberFromParams(params, 'vendorConfigurationId');
|
|
@@ -169,6 +184,7 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
169
184
|
// Retried
|
|
170
185
|
dispatch(setIsRetry(false));
|
|
171
186
|
|
|
187
|
+
dispatch(setIsUnavailable(false));
|
|
172
188
|
// Fetch data
|
|
173
189
|
const promise = dispatch(fetchPackage());
|
|
174
190
|
return () => {
|
|
@@ -235,7 +251,7 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
235
251
|
return () => {
|
|
236
252
|
promise.abort();
|
|
237
253
|
};
|
|
238
|
-
}, [productAttributes
|
|
254
|
+
}, [productAttributes]);
|
|
239
255
|
|
|
240
256
|
let numberIndex = 1;
|
|
241
257
|
|
|
@@ -246,30 +262,31 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
246
262
|
<div className="booking__content">
|
|
247
263
|
<div className="booking__panel">
|
|
248
264
|
<Router basepath={basePath}>
|
|
265
|
+
{travelersFirstStep && (
|
|
266
|
+
<StepRoute path={'/'} number={numberIndex++} title={translations.STEPS.PERSONAL_DETAILS} component={<TravelersForm />} />
|
|
267
|
+
)}
|
|
249
268
|
{!flightOptions.isHidden && flightOptions.pathSuffix && (
|
|
250
|
-
<StepRoute
|
|
251
|
-
path={flightOptions.pathSuffix}
|
|
252
|
-
number={numberIndex++}
|
|
253
|
-
title={translations.STEPS.FLIGHT_OPTIONS}
|
|
254
|
-
component={<FlightOptionsForm />}
|
|
255
|
-
/>
|
|
269
|
+
<StepRoute path={'/'} number={numberIndex++} title={translations.STEPS.FLIGHT_OPTIONS} component={<FlightOptionsForm />} />
|
|
256
270
|
)}
|
|
257
271
|
{!roomOptions.isHidden && roomOptions.pathSuffix && (
|
|
258
272
|
<StepRoute path={roomOptions.pathSuffix} number={numberIndex++} title={translations.STEPS.ROOM_OPTIONS} component={<RoomOptionsForm />} />
|
|
259
273
|
)}
|
|
260
274
|
<StepRoute path={options.pathSuffix} number={numberIndex++} title={translations.STEPS.EXTRA_OPTIONS} component={<OptionsForm />} />
|
|
261
|
-
|
|
275
|
+
{!travelersFirstStep && (
|
|
276
|
+
<StepRoute path={travellers.pathSuffix} number={numberIndex++} title={translations.STEPS.PERSONAL_DETAILS} component={<TravelersForm />} />
|
|
277
|
+
)}
|
|
262
278
|
<StepRoute path={summary.pathSuffix} number={numberIndex++} title={translations.STEPS.SUMMARY} component={<Summary />} />
|
|
263
279
|
<StepRoute path={confirmation.pathSuffix} number={numberIndex++} title={translations.STEPS.CONFIRMATION} component={<Confirmation />} />
|
|
264
280
|
<StepRoute path={error.pathSuffix} number={numberIndex++} title={translations.STEPS.ERROR} component={<Error />} />
|
|
265
281
|
</Router>
|
|
266
282
|
</div>
|
|
267
283
|
<div className="backdrop" id="backdrop"></div>
|
|
268
|
-
|
|
284
|
+
<Sidebar productName={productName} thumbnailUrl={thumbnailUrl} />
|
|
269
285
|
</div>
|
|
270
286
|
</div>
|
|
271
287
|
)}
|
|
272
|
-
|
|
288
|
+
|
|
289
|
+
{!packageDetails && !bookingNumber && !isUnavailable && (
|
|
273
290
|
<div className="booking">
|
|
274
291
|
<div className="booking__loader">
|
|
275
292
|
{loaderComponent}
|
|
@@ -279,7 +296,7 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
279
296
|
</div>
|
|
280
297
|
</div>
|
|
281
298
|
)}
|
|
282
|
-
{
|
|
299
|
+
{isUnavailable && !travelersFirstStep && (
|
|
283
300
|
<div className="booking">
|
|
284
301
|
<div className="booking__loader">
|
|
285
302
|
<p className="booking__loader-text">{translations.MAIN.PRODUCT_UNAVAILABLE}</p>
|
|
@@ -10,6 +10,10 @@ import { FlightInfo, Room, Traveler } from '../../types';
|
|
|
10
10
|
import { selectNotifications } from '../price-details/price-details-slice';
|
|
11
11
|
import { selectAgentId, selectTravelersFormValues } from '../travelers-form/travelers-form-slice';
|
|
12
12
|
|
|
13
|
+
export const selectHasMounted = (state: RootState) => state.booking.hasMounted;
|
|
14
|
+
|
|
15
|
+
export const selectIsFetching = (state: RootState) => state.booking.isFetching;
|
|
16
|
+
|
|
13
17
|
export const selectCurrentStep = (state: RootState) => state.booking.currentStep;
|
|
14
18
|
|
|
15
19
|
export const selectGeneratePaymentUrl = (state: RootState) => state.booking.generatePaymentUrl;
|
|
@@ -373,6 +377,8 @@ export const selectBookingPackageBookRequest = createSelector(
|
|
|
373
377
|
}
|
|
374
378
|
);
|
|
375
379
|
|
|
380
|
+
export const selectTravelersFirstStep = (state: any) => state.booking.travelersFirstStep;
|
|
381
|
+
|
|
376
382
|
const buildPax = (traveler: Traveler, mainBookerId?: number) => {
|
|
377
383
|
return {
|
|
378
384
|
id: traveler.id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { BookingPackageFlight } from '@qite/tide-client/build/types';
|
|
4
|
-
import { navigate } from '@reach/router';
|
|
4
|
+
import { Link, navigate } from '@reach/router';
|
|
5
5
|
import { isEmpty } from 'lodash';
|
|
6
6
|
import { useSelector } from 'react-redux';
|
|
7
7
|
import { buildClassName } from '../../../shared/utils/class-util';
|
|
@@ -9,8 +9,15 @@ import SettingsContext from '../../settings-context';
|
|
|
9
9
|
import { useAppDispatch } from '../../store';
|
|
10
10
|
import { FlightFilterOptions, GroupedFlights } from '../../types';
|
|
11
11
|
import { setCurrentStep, setFlights, setPackage } from '../booking/booking-slice';
|
|
12
|
-
import { ROOM_OPTIONS_FORM_STEP } from '../booking/constants';
|
|
13
|
-
import {
|
|
12
|
+
import { ROOM_OPTIONS_FORM_STEP, TRAVELERS_FORM_STEP } from '../booking/constants';
|
|
13
|
+
import {
|
|
14
|
+
selectBookingQueryString,
|
|
15
|
+
selectIsFetchingProductOptions,
|
|
16
|
+
selectPackageDetails,
|
|
17
|
+
selectPackageFlights,
|
|
18
|
+
selectTranslations,
|
|
19
|
+
selectTravelersFirstStep
|
|
20
|
+
} from '../booking/selectors';
|
|
14
21
|
import { fetchPriceDetails } from '../price-details/price-details-slice';
|
|
15
22
|
import FlightFilter from './flight-filter';
|
|
16
23
|
import FlightOption from './flight-option';
|
|
@@ -28,6 +35,7 @@ const FlightOptionsForm: React.FC<FlightOptionsFormProps> = () => {
|
|
|
28
35
|
const bookingQueryString = useSelector(selectBookingQueryString);
|
|
29
36
|
const isLoading = useSelector(selectIsFetchingProductOptions);
|
|
30
37
|
const flights = useSelector(selectPackageFlights);
|
|
38
|
+
const travelersFirstStep = useSelector(selectTravelersFirstStep);
|
|
31
39
|
|
|
32
40
|
const [filterOptions, setFilterOptions] = useState<FlightFilterOptions | undefined>();
|
|
33
41
|
const [flightGroups, setFlightGroups] = useState<GroupedFlights[]>([]);
|
|
@@ -92,6 +100,10 @@ const FlightOptionsForm: React.FC<FlightOptionsFormProps> = () => {
|
|
|
92
100
|
const filteredGroups = filterGroupedFlights(flightGroups, filterOptions);
|
|
93
101
|
const resultCount = filteredGroups.length;
|
|
94
102
|
|
|
103
|
+
const goPrevious = () => {
|
|
104
|
+
dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
|
|
105
|
+
};
|
|
106
|
+
|
|
95
107
|
return (
|
|
96
108
|
<>
|
|
97
109
|
<FlightOptionModal />
|
|
@@ -129,6 +141,18 @@ const FlightOptionsForm: React.FC<FlightOptionsFormProps> = () => {
|
|
|
129
141
|
)}
|
|
130
142
|
</div>
|
|
131
143
|
<div className="booking__navigator">
|
|
144
|
+
{travelersFirstStep && settings.skipRouter ? (
|
|
145
|
+
<button type="button" title={translations.STEPS.PREVIOUS} onClick={() => goPrevious()} className="cta cta--secondary">
|
|
146
|
+
{translations.STEPS.PREVIOUS}
|
|
147
|
+
</button>
|
|
148
|
+
) : travelersFirstStep ? (
|
|
149
|
+
<Link
|
|
150
|
+
to={`${settings.basePath}${settings.travellers.pathSuffix}?${bookingQueryString}`}
|
|
151
|
+
title={translations.STEPS.PREVIOUS}
|
|
152
|
+
className="cta cta--secondary">
|
|
153
|
+
{translations.STEPS.PREVIOUS}
|
|
154
|
+
</Link>
|
|
155
|
+
) : null}
|
|
132
156
|
<button type="submit" title={translations.STEPS.NEXT} disabled={isLoading} className={buildClassName(['cta', isLoading && 'cta--disabled'])}>
|
|
133
157
|
{translations.STEPS.NEXT}
|
|
134
158
|
</button>
|
|
@@ -32,7 +32,7 @@ const OptionRoom: React.FC<OptionRoomProps> = ({ packageRoom, pax, optionPax, on
|
|
|
32
32
|
if (selectedOption) {
|
|
33
33
|
startDate = getDateText(selectedOption.from, true) ?? '';
|
|
34
34
|
endDate = getDateText(selectedOption.to, true) ?? '';
|
|
35
|
-
daysAndNightsText = getDatePeriodText(selectedOption.from, selectedOption.to) ?? '';
|
|
35
|
+
daysAndNightsText = getDatePeriodText(translations, selectedOption.from, selectedOption.to) ?? '';
|
|
36
36
|
|
|
37
37
|
const productAttributes = useSelector(selectProductAttributes);
|
|
38
38
|
productName = productAttributes?.productName ?? '';
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
setPackageRooms,
|
|
30
30
|
setTagIds
|
|
31
31
|
} from '../booking/booking-slice';
|
|
32
|
-
import { FLIGHT_OPTIONS_FORM_STEP, ROOM_OPTIONS_FORM_STEP, TRAVELERS_FORM_STEP } from '../booking/constants';
|
|
32
|
+
import { FLIGHT_OPTIONS_FORM_STEP, ROOM_OPTIONS_FORM_STEP, SUMMARY_STEP, TRAVELERS_FORM_STEP } from '../booking/constants';
|
|
33
33
|
import {
|
|
34
34
|
selectAvailabilities,
|
|
35
35
|
selectBookingPackagePax,
|
|
@@ -46,7 +46,8 @@ import {
|
|
|
46
46
|
selectPackageTags,
|
|
47
47
|
selectRequestRooms,
|
|
48
48
|
selectTagIds,
|
|
49
|
-
selectTranslations
|
|
49
|
+
selectTranslations,
|
|
50
|
+
selectTravelersFirstStep
|
|
50
51
|
} from '../booking/selectors';
|
|
51
52
|
import { fetchPriceDetails } from '../price-details/price-details-slice';
|
|
52
53
|
import { updatePackageRooms } from '../room-options/room-utils';
|
|
@@ -78,6 +79,7 @@ const OptionsForm: React.FC<OptionsFormProps> = () => {
|
|
|
78
79
|
const optionPax = useSelector(selectPackageOptionPax);
|
|
79
80
|
const availabilities = useSelector(selectAvailabilities);
|
|
80
81
|
const includedServiceTypes = useSelector(selectIncludedServiceTypes);
|
|
82
|
+
const travelersFirstStep = useSelector(selectTravelersFirstStep);
|
|
81
83
|
|
|
82
84
|
// ROOMS
|
|
83
85
|
const showRoomOptions = settings.options.showRoomOptions ?? settings.roomOptions.isHidden;
|
|
@@ -111,9 +113,17 @@ const OptionsForm: React.FC<OptionsFormProps> = () => {
|
|
|
111
113
|
|
|
112
114
|
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
|
|
113
115
|
if (settings.skipRouter) {
|
|
114
|
-
|
|
116
|
+
if (travelersFirstStep) {
|
|
117
|
+
dispatch(setCurrentStep(SUMMARY_STEP));
|
|
118
|
+
} else {
|
|
119
|
+
dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
|
|
120
|
+
}
|
|
115
121
|
} else {
|
|
116
|
-
|
|
122
|
+
if (travelersFirstStep) {
|
|
123
|
+
navigate(`${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`);
|
|
124
|
+
} else {
|
|
125
|
+
navigate(`${settings.basePath}${settings.travellers.pathSuffix}?${bookingQueryString}`);
|
|
126
|
+
}
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
e.preventDefault();
|
|
@@ -86,7 +86,7 @@ const RoomOption: React.FC<RoomOptionProps> = ({ room, hasAlternatives, selected
|
|
|
86
86
|
<p className="form__room__dates">
|
|
87
87
|
{getDateText(room.from)} - {getDateText(room.to)}
|
|
88
88
|
</p>
|
|
89
|
-
<span className="form__room__days">{getDatePeriodText(room.from, room.to)}</span>
|
|
89
|
+
<span className="form__room__days">{getDatePeriodText(translations, room.from, room.to)}</span>
|
|
90
90
|
</div>
|
|
91
91
|
<div className="form__room__footer__bottom">
|
|
92
92
|
{selectedRoomPrice != undefined && (
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
selectIncludedServiceTypes,
|
|
4
4
|
selectIsFetchingProductOptions,
|
|
5
5
|
selectIsOnRequest,
|
|
6
|
+
selectIsUnavailable,
|
|
7
|
+
selectPackageDetails,
|
|
6
8
|
selectPackageRooms,
|
|
7
9
|
selectReturnFlight,
|
|
8
10
|
selectRoomOptionDepartureFlightsMetaData,
|
|
@@ -44,7 +46,7 @@ const SidebarContainer: React.FC<SidebarProps> = ({ productName, thumbnailUrl })
|
|
|
44
46
|
const isFetchingPriceDetails = useSelector(selectIsFetchingPriceDetails);
|
|
45
47
|
const accommodations = useSelector(selectPackageRooms);
|
|
46
48
|
const includedServiceTypes = useSelector(selectIncludedServiceTypes);
|
|
47
|
-
|
|
49
|
+
const isUnavailable = useSelector(selectIsUnavailable) || false;
|
|
48
50
|
const isLoading = isFetchingProductOptions || isFetchingPriceDetails;
|
|
49
51
|
|
|
50
52
|
return (
|
|
@@ -68,6 +70,7 @@ const SidebarContainer: React.FC<SidebarProps> = ({ productName, thumbnailUrl })
|
|
|
68
70
|
headerComponent={sidebarHeaderComponent ?? undefined}
|
|
69
71
|
footerComponent={sidebarFooterComponent ?? undefined}
|
|
70
72
|
loaderComponent={loaderComponent ?? undefined}
|
|
73
|
+
isUnavailable={isUnavailable}
|
|
71
74
|
/>
|
|
72
75
|
);
|
|
73
76
|
};
|
|
@@ -35,13 +35,11 @@ export const getDateText = (date: string | undefined, hideYear?: boolean): strin
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
export const getDatePeriodText = (from?: string, to?: string, nightsOnly?: boolean) => {
|
|
38
|
+
export const getDatePeriodText = (translations: Record<string, any>, from?: string, to?: string, nightsOnly?: boolean) => {
|
|
39
39
|
if (!from || !to) {
|
|
40
40
|
return undefined;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const translations = useSelector(selectTranslations);
|
|
44
|
-
|
|
45
43
|
try {
|
|
46
44
|
const fromDate = parseISO(from);
|
|
47
45
|
const toDate = parseISO(to);
|