@qite/tide-booking-component 1.4.30 → 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/package.json +1 -1
- 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/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/package.json
CHANGED
|
@@ -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);
|
|
@@ -31,6 +31,7 @@ interface SidebarProps {
|
|
|
31
31
|
headerComponent?: JSX.Element;
|
|
32
32
|
footerComponent?: JSX.Element;
|
|
33
33
|
loaderComponent?: JSX.Element;
|
|
34
|
+
isUnavailable?: boolean;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const Sidebar: React.FC<SidebarProps> = ({
|
|
@@ -52,7 +53,8 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|
|
52
53
|
isOnRequest,
|
|
53
54
|
headerComponent,
|
|
54
55
|
footerComponent,
|
|
55
|
-
loaderComponent
|
|
56
|
+
loaderComponent,
|
|
57
|
+
isUnavailable
|
|
56
58
|
}) => {
|
|
57
59
|
const [active, setActive] = useState<boolean>(false);
|
|
58
60
|
const translations = useSelector(selectTranslations);
|
|
@@ -75,127 +77,133 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|
|
75
77
|
<div className="booking__sidebar-frame">
|
|
76
78
|
<ProductCard productName={productName} thumbnailUrl={thumbnailUrl} handleToggleClick={handleToggleClick} />
|
|
77
79
|
<div className="pricing-summary">
|
|
78
|
-
|
|
79
|
-
<div className=
|
|
80
|
-
<div className=
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
travelerRooms
|
|
84
|
-
|
|
85
|
-
<div className="pricing-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
{!isUnavailable ? (
|
|
81
|
+
<div className="pricing-summary__wrapper">
|
|
82
|
+
<div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
83
|
+
<div className="pricing-summary__group">
|
|
84
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.TRAVEL_INFO}</h6>
|
|
85
|
+
{!isEmpty(travelerRooms) &&
|
|
86
|
+
travelerRooms?.map((room, rIndex) => (
|
|
87
|
+
<div className="pricing-summary__row" key={rIndex}>
|
|
88
|
+
<div className="pricing-summary__property">
|
|
89
|
+
{travelerRooms.length > 1 && `${translations.SHARED.ROOM} ${rIndex + 1}`}
|
|
90
|
+
{travelerRooms.length === 1 && translations.ROOM_OPTIONS_FORM.TRAVELER_GROUP}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="pricing-summary__value">{room}</div>
|
|
88
94
|
</div>
|
|
95
|
+
))}
|
|
89
96
|
|
|
90
|
-
|
|
97
|
+
{startDateText && (
|
|
98
|
+
<div className="pricing-summary__row">
|
|
99
|
+
<div className="pricing-summary__property">
|
|
100
|
+
{startDateText && endDateText ? translations.SIDEBAR.DEPARTURE : translations.SIDEBAR.DEPARTURE_SINGLE}
|
|
101
|
+
</div>
|
|
102
|
+
<div className="pricing-summary__value">{startDateText}</div>
|
|
91
103
|
</div>
|
|
92
|
-
)
|
|
104
|
+
)}
|
|
93
105
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
106
|
+
{endDateText && (
|
|
107
|
+
<div className="pricing-summary__row">
|
|
108
|
+
<div className="pricing-summary__property">{translations.SIDEBAR.ARRIVAL}</div>
|
|
109
|
+
<div className="pricing-summary__value">{endDateText}</div>
|
|
98
110
|
</div>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)}
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
102
113
|
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<div className="pricing-summary__value">{endDateText}</div>
|
|
107
|
-
</div>
|
|
114
|
+
{isLoading && loaderComponent}
|
|
115
|
+
{!isLoading && departureFlightMetaData && (
|
|
116
|
+
<SidebarFlight title={translations.SIDEBAR.DEPARTURE_FLIGHT} flightMetaData={departureFlightMetaData} />
|
|
108
117
|
)}
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
{isLoading && loaderComponent}
|
|
112
|
-
{!isLoading && departureFlightMetaData && (
|
|
113
|
-
<SidebarFlight title={translations.SIDEBAR.DEPARTURE_FLIGHT} flightMetaData={departureFlightMetaData} />
|
|
114
|
-
)}
|
|
115
118
|
|
|
116
|
-
|
|
119
|
+
{!isLoading && returnFlightMetaData && <SidebarFlight title={translations.SIDEBAR.ARRIVAL_FLIGHT} flightMetaData={returnFlightMetaData} />}
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
{accommodations && (
|
|
122
|
+
<div className="pricing-summary__group">
|
|
123
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
|
|
124
|
+
{accommodations.map((accommodation) => {
|
|
125
|
+
let option = accommodation.options.find((x) => x.isSelected);
|
|
126
|
+
return (
|
|
127
|
+
<div key={accommodation.index}>
|
|
128
|
+
<div className="pricing-summary__row">
|
|
129
|
+
<div className="pricing-summary__property">
|
|
130
|
+
{option?.accommodationName}
|
|
131
|
+
{isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
|
|
132
|
+
{option?.isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
|
|
133
|
+
</div>
|
|
134
|
+
{/*<div className="pricing-summary__value">
|
|
132
135
|
{option && option.price > 0 && formatPrice(option?.price)}
|
|
133
136
|
</div>*/}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="pricing-summary__row">
|
|
139
|
+
<div className="price-summarty__property">{option?.regimeName}</div>
|
|
140
|
+
<div className="price-summary__value">{!isFlightOnly && getDatePeriodText(translations, option?.from, option?.to, true)}</div>
|
|
141
|
+
</div>
|
|
134
142
|
</div>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{!isLoading && canShowPriceBreakdownSection && (
|
|
150
|
+
<div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
151
|
+
{basePrice !== undefined && basePrice > 0 && (
|
|
152
|
+
<div className="pricing-summary__group">
|
|
153
|
+
<div className="pricing-summary__row">
|
|
154
|
+
<div className="pricing-summary__property">
|
|
155
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.BASE_PRICE}</h6>
|
|
138
156
|
</div>
|
|
157
|
+
<div className="pricing-summary__value">{formatPrice(basePrice, currencyCode)}</div>
|
|
139
158
|
</div>
|
|
140
|
-
|
|
141
|
-
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
{!isEmpty(includedCosts) && (
|
|
162
|
+
<div className="pricing-summary__group">
|
|
163
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.INCLUDED_COSTS}</h6>
|
|
164
|
+
{includedCosts?.map((priceDetail, index) => (
|
|
165
|
+
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
166
|
+
<div className="pricing-summary__row">
|
|
167
|
+
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
168
|
+
{priceDetail.showPrice && (
|
|
169
|
+
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
<div className="pricing-summary__row">
|
|
173
|
+
<div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
174
|
+
</div>
|
|
175
|
+
</React.Fragment>
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
{!isEmpty(extraCosts) && (
|
|
180
|
+
<div className="pricing-summary__group">
|
|
181
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.EXTRA_COSTS}</h6>
|
|
182
|
+
{extraCosts?.map((priceDetail, index) => (
|
|
183
|
+
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
184
|
+
<div className="pricing-summary__row">
|
|
185
|
+
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
186
|
+
{priceDetail.showPrice && (
|
|
187
|
+
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
<div className="pricing-summary__row">
|
|
191
|
+
<div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
192
|
+
</div>
|
|
193
|
+
</React.Fragment>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
142
197
|
</div>
|
|
143
198
|
)}
|
|
144
199
|
</div>
|
|
200
|
+
) : (
|
|
201
|
+
<div className="pricing-summary__region">
|
|
202
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.PACKAGE_NOT_AVAILABLE}</h6>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
145
205
|
|
|
146
|
-
|
|
147
|
-
<div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
148
|
-
{basePrice !== undefined && basePrice > 0 && (
|
|
149
|
-
<div className="pricing-summary__group">
|
|
150
|
-
<div className="pricing-summary__row">
|
|
151
|
-
<div className="pricing-summary__property">
|
|
152
|
-
<h6 className="pricing-summary__title">{translations.SIDEBAR.BASE_PRICE}</h6>
|
|
153
|
-
</div>
|
|
154
|
-
<div className="pricing-summary__value">{formatPrice(basePrice, currencyCode)}</div>
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
{!isEmpty(includedCosts) && (
|
|
159
|
-
<div className="pricing-summary__group">
|
|
160
|
-
<h6 className="pricing-summary__title">{translations.SIDEBAR.INCLUDED_COSTS}</h6>
|
|
161
|
-
{includedCosts?.map((priceDetail, index) => (
|
|
162
|
-
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
163
|
-
<div className="pricing-summary__row">
|
|
164
|
-
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
165
|
-
{priceDetail.showPrice && (
|
|
166
|
-
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
<div className="pricing-summary__row">
|
|
170
|
-
<div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
171
|
-
</div>
|
|
172
|
-
</React.Fragment>
|
|
173
|
-
))}
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
{!isEmpty(extraCosts) && (
|
|
177
|
-
<div className="pricing-summary__group">
|
|
178
|
-
<h6 className="pricing-summary__title">{translations.SIDEBAR.EXTRA_COSTS}</h6>
|
|
179
|
-
{extraCosts?.map((priceDetail, index) => (
|
|
180
|
-
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
181
|
-
<div className="pricing-summary__row">
|
|
182
|
-
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
183
|
-
{priceDetail.showPrice && (
|
|
184
|
-
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
185
|
-
)}
|
|
186
|
-
</div>
|
|
187
|
-
<div className="pricing-summary__row">
|
|
188
|
-
<div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
189
|
-
</div>
|
|
190
|
-
</React.Fragment>
|
|
191
|
-
))}
|
|
192
|
-
</div>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
{!isLoading && canShowTotalPriceSection && (
|
|
206
|
+
{!isLoading && canShowTotalPriceSection && !isUnavailable && (
|
|
199
207
|
<div className={`pricing-summary__region pricing-summary__region--pricing ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
200
208
|
{deposit && remainingAmount > 0 ? (
|
|
201
209
|
<div className="pricing-summary__group">
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { compact, get, sortBy } from 'lodash';
|
|
2
2
|
import React, { useContext, useEffect, useState } from 'react';
|
|
3
3
|
import { useSelector } from 'react-redux';
|
|
4
|
-
import { setCurrentStep } from '../booking/booking-slice';
|
|
4
|
+
import { fetchPackage, setCurrentStep, setHasMounted, setIsFetching } from '../booking/booking-slice';
|
|
5
|
+
import { selectBookingAttributes, selectHasMounted, selectIsFetching, selectIsUnavailable, selectTravelersFirstStep } from '../booking/selectors';
|
|
5
6
|
import { selectFormRooms, selectTravelersFormValues, setFormValues } from './travelers-form-slice';
|
|
6
7
|
|
|
7
8
|
import { Link, navigate } from '@reach/router';
|
|
@@ -16,12 +17,12 @@ import SettingsContext from '../../settings-context';
|
|
|
16
17
|
import { useAppDispatch } from '../../store';
|
|
17
18
|
import { RoomTraveler, Traveler, TravelersFormValues } from '../../types';
|
|
18
19
|
import { setBookingType } from '../booking/booking-slice';
|
|
19
|
-
import { OPTIONS_FORM_STEP, SUMMARY_STEP } from '../booking/constants';
|
|
20
|
+
import { FLIGHT_OPTIONS_FORM_STEP, OPTIONS_FORM_STEP, SUMMARY_STEP } from '../booking/constants';
|
|
20
21
|
import { selectAgentAdressId, selectAgents, selectBookingQueryString, selectBookingType, selectStartDate, selectTranslations } from '../booking/selectors';
|
|
21
22
|
import { fetchPriceDetails } from '../price-details/price-details-slice';
|
|
23
|
+
import GenderControl from './controls/gender-control';
|
|
22
24
|
import TypeAheadInput from './type-ahead-input';
|
|
23
25
|
import validateForm from './validate-form';
|
|
24
|
-
import GenderControl from './controls/gender-control';
|
|
25
26
|
import PhoneInput from '../../components/phone-input';
|
|
26
27
|
|
|
27
28
|
interface TravelersFormProps {}
|
|
@@ -94,6 +95,12 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
94
95
|
const agents = useSelector(selectAgents);
|
|
95
96
|
const agentAdressId = useSelector(selectAgentAdressId);
|
|
96
97
|
const translations = useSelector(selectTranslations);
|
|
98
|
+
const travelersFirstStep = useSelector(selectTravelersFirstStep);
|
|
99
|
+
const isUnavailable = useSelector(selectIsUnavailable);
|
|
100
|
+
const bookingAttributes = useSelector(selectBookingAttributes);
|
|
101
|
+
const isFetching = useSelector(selectIsFetching);
|
|
102
|
+
const hasMounted = useSelector(selectHasMounted);
|
|
103
|
+
|
|
97
104
|
const useCompactForm = !!settings.travellers.compactForm && !!settings.agentAdressId;
|
|
98
105
|
|
|
99
106
|
const initialValues =
|
|
@@ -135,12 +142,20 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
135
142
|
validateForm(values, settings.agentRequired, bookingType, translations, settings.travellers.formFields, settings.travellers.mainBookerFormFields),
|
|
136
143
|
onSubmit: (values) => {
|
|
137
144
|
dispatch(setFormValues(values));
|
|
138
|
-
dispatch(
|
|
145
|
+
dispatch(fetchPackage());
|
|
139
146
|
|
|
140
147
|
if (settings.skipRouter) {
|
|
141
|
-
|
|
148
|
+
if (travelersFirstStep) {
|
|
149
|
+
dispatch(setCurrentStep(FLIGHT_OPTIONS_FORM_STEP));
|
|
150
|
+
} else {
|
|
151
|
+
dispatch(setCurrentStep(SUMMARY_STEP));
|
|
152
|
+
}
|
|
142
153
|
} else {
|
|
143
|
-
|
|
154
|
+
if (travelersFirstStep) {
|
|
155
|
+
navigate(`${settings.basePath}${settings.flightOptions.pathSuffix}?${bookingQueryString}`);
|
|
156
|
+
} else {
|
|
157
|
+
navigate(`${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`);
|
|
158
|
+
}
|
|
144
159
|
}
|
|
145
160
|
}
|
|
146
161
|
});
|
|
@@ -149,6 +164,51 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
149
164
|
dispatch(fetchPriceDetails());
|
|
150
165
|
}, []);
|
|
151
166
|
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!bookingAttributes?.rooms?.length || isFetching) return;
|
|
169
|
+
|
|
170
|
+
if (!hasMounted) {
|
|
171
|
+
dispatch(setHasMounted(true));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fetchAll = async () => {
|
|
176
|
+
dispatch(setIsFetching(true));
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await dispatch(fetchPackage());
|
|
180
|
+
await dispatch(fetchPriceDetails());
|
|
181
|
+
} finally {
|
|
182
|
+
dispatch(setIsFetching(false));
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
fetchAll();
|
|
187
|
+
}, [bookingAttributes?.rooms]);
|
|
188
|
+
|
|
189
|
+
// Update URL querystring when form data changes
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (settings.skipRouter || !travelersFirstStep) return;
|
|
192
|
+
const params = new URLSearchParams(bookingQueryString);
|
|
193
|
+
params.delete('rooms');
|
|
194
|
+
const roomsString = formik.values.rooms
|
|
195
|
+
.map((room) => {
|
|
196
|
+
const adults = room.adults ? room.adults.length : 0;
|
|
197
|
+
const childAges = room.children && room.children.length ? room.children.map((c) => c.age).join(',') : '';
|
|
198
|
+
return `adults:${adults},childAges:(${childAges})`;
|
|
199
|
+
})
|
|
200
|
+
.map((s) => `(${s})`)
|
|
201
|
+
.join(',');
|
|
202
|
+
let query = params.toString();
|
|
203
|
+
if (query) {
|
|
204
|
+
query += `&rooms=(${roomsString})`;
|
|
205
|
+
} else {
|
|
206
|
+
query = `rooms=(${roomsString})`;
|
|
207
|
+
}
|
|
208
|
+
const newUrl = `${window.location.pathname}?${query}`;
|
|
209
|
+
navigate(newUrl, { replace: true });
|
|
210
|
+
}, [formik.values]);
|
|
211
|
+
|
|
152
212
|
useEffect(() => {
|
|
153
213
|
if (agents && settings.affiliateSlug) {
|
|
154
214
|
const agent = agents.find((x) => x.affiliateSlug && x.affiliateSlug === settings.affiliateSlug);
|
|
@@ -206,6 +266,55 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
206
266
|
dispatch(setBookingType('b2c'));
|
|
207
267
|
};
|
|
208
268
|
|
|
269
|
+
const handleAddTraveler = (roomIndex: number) => {
|
|
270
|
+
const rooms = [...formik.values.rooms];
|
|
271
|
+
const newAdult = {
|
|
272
|
+
id: Date.now(),
|
|
273
|
+
firstName: '',
|
|
274
|
+
lastName: '',
|
|
275
|
+
birthDate: '',
|
|
276
|
+
gender: ''
|
|
277
|
+
};
|
|
278
|
+
rooms[roomIndex] = {
|
|
279
|
+
...rooms[roomIndex],
|
|
280
|
+
adults: [...rooms[roomIndex].adults, newAdult]
|
|
281
|
+
};
|
|
282
|
+
formik.setFieldValue('rooms', rooms);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const handleRemoveTraveler = (roomIndex: number, travelerIndex: number) => {
|
|
286
|
+
const rooms = [...formik.values.rooms];
|
|
287
|
+
const adults = [...rooms[roomIndex].adults];
|
|
288
|
+
if (adults.length <= 1) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
adults.splice(travelerIndex, 1);
|
|
292
|
+
rooms[roomIndex] = {
|
|
293
|
+
...rooms[roomIndex],
|
|
294
|
+
adults
|
|
295
|
+
};
|
|
296
|
+
formik.setFieldValue('rooms', rooms);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const handleAddRoom = () => {
|
|
300
|
+
const rooms = [...formik.values.rooms];
|
|
301
|
+
const newAdult = {
|
|
302
|
+
id: Date.now(),
|
|
303
|
+
firstName: '',
|
|
304
|
+
lastName: '',
|
|
305
|
+
birthDate: '',
|
|
306
|
+
gender: ''
|
|
307
|
+
};
|
|
308
|
+
rooms.push({ adults: [newAdult], children: [] });
|
|
309
|
+
formik.setFieldValue('rooms', rooms);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const handleRemoveRoom = (roomIndex: number) => {
|
|
313
|
+
const rooms = [...formik.values.rooms];
|
|
314
|
+
rooms.splice(roomIndex, 1);
|
|
315
|
+
formik.setFieldValue('rooms', rooms);
|
|
316
|
+
};
|
|
317
|
+
|
|
209
318
|
const toggleAgent = (value: boolean) => {
|
|
210
319
|
setShowAgents(value);
|
|
211
320
|
|
|
@@ -440,6 +549,7 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
440
549
|
) : (
|
|
441
550
|
<>
|
|
442
551
|
<div className="form__travelers__wrapper">
|
|
552
|
+
{/* map each room */}
|
|
443
553
|
{formik.values.rooms.map((room, rIndex) => (
|
|
444
554
|
<div key={rIndex}>
|
|
445
555
|
{formik.values.rooms.length > 1 && (
|
|
@@ -460,8 +570,14 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
460
570
|
]).join('')}
|
|
461
571
|
</p>
|
|
462
572
|
</div>
|
|
573
|
+
{travelersFirstStep && formik.values.rooms.length > 1 && (
|
|
574
|
+
<button type="button" className="cta cta--secondary" onClick={() => handleRemoveRoom(rIndex)}>
|
|
575
|
+
Verwijder reisgezelschap
|
|
576
|
+
</button>
|
|
577
|
+
)}
|
|
463
578
|
</div>
|
|
464
579
|
)}
|
|
580
|
+
{/* map adults here for the room */}
|
|
465
581
|
{room.adults.map((travelerValues, index) => (
|
|
466
582
|
<div className="form__region" key={travelerValues.id}>
|
|
467
583
|
<div className="form__region-header">
|
|
@@ -581,6 +697,11 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
581
697
|
value={travelerValues.birthDate}
|
|
582
698
|
/>
|
|
583
699
|
</div>
|
|
700
|
+
{travelersFirstStep && room.adults.length > 1 && (
|
|
701
|
+
<button type="button" className="cta cta--secondary" onClick={() => handleRemoveTraveler(rIndex, index)}>
|
|
702
|
+
{translations.TRAVELERS_FORM.REMOVE_TRAVELER}
|
|
703
|
+
</button>
|
|
704
|
+
)}
|
|
584
705
|
</>
|
|
585
706
|
)}
|
|
586
707
|
</div>
|
|
@@ -694,6 +815,13 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
694
815
|
)}
|
|
695
816
|
</div>
|
|
696
817
|
))}
|
|
818
|
+
{travelersFirstStep && (
|
|
819
|
+
<div className="form__region">
|
|
820
|
+
<button type="button" className="cta cta--select" onClick={() => handleAddTraveler(rIndex)}>
|
|
821
|
+
{translations.TRAVELERS_FORM.ADD_TRAVELER}
|
|
822
|
+
</button>
|
|
823
|
+
</div>
|
|
824
|
+
)}
|
|
697
825
|
</div>
|
|
698
826
|
))}
|
|
699
827
|
|
|
@@ -918,20 +1046,28 @@ const TravelersForm: React.FC<TravelersFormProps> = () => {
|
|
|
918
1046
|
)}
|
|
919
1047
|
</>
|
|
920
1048
|
)}
|
|
1049
|
+
{travelersFirstStep && (
|
|
1050
|
+
<div className="booking__navigator">
|
|
1051
|
+
<button type="button" className="cta cta--select" onClick={handleAddRoom}>
|
|
1052
|
+
{translations.TRAVELERS_FORM.ADD_ROOM}
|
|
1053
|
+
</button>
|
|
1054
|
+
</div>
|
|
1055
|
+
)}
|
|
1056
|
+
|
|
921
1057
|
<div className="booking__navigator">
|
|
922
|
-
{settings.skipRouter ? (
|
|
1058
|
+
{!travelersFirstStep && settings.skipRouter ? (
|
|
923
1059
|
<button type="button" title={translations.STEPS.PREVIOUS} onClick={() => goPrevious()} className="cta cta--secondary">
|
|
924
1060
|
{translations.STEPS.PREVIOUS}
|
|
925
1061
|
</button>
|
|
926
|
-
) : (
|
|
1062
|
+
) : !travelersFirstStep ? (
|
|
927
1063
|
<Link
|
|
928
1064
|
to={`${settings.basePath}${settings.options.pathSuffix}?${bookingQueryString}`}
|
|
929
1065
|
title={translations.STEPS.PREVIOUS}
|
|
930
1066
|
className="cta cta--secondary">
|
|
931
1067
|
{translations.STEPS.PREVIOUS}
|
|
932
1068
|
</Link>
|
|
933
|
-
)}
|
|
934
|
-
<button type="submit" title={translations.STEPS.NEXT} className=
|
|
1069
|
+
) : null}
|
|
1070
|
+
<button type="submit" title={translations.STEPS.NEXT} className={'cta' + (isUnavailable ? ' cta--disabled' : '')}>
|
|
935
1071
|
{translations.STEPS.NEXT}
|
|
936
1072
|
</button>
|
|
937
1073
|
</div>
|
|
@@ -25,7 +25,8 @@ const SettingsContext = React.createContext<WizardSettingsContextProps>({
|
|
|
25
25
|
reportPrintActionId: null
|
|
26
26
|
},
|
|
27
27
|
travellers: {
|
|
28
|
-
pathSuffix: '/reizigers'
|
|
28
|
+
pathSuffix: '/reizigers',
|
|
29
|
+
travelersFirstStep: false
|
|
29
30
|
},
|
|
30
31
|
summary: {
|
|
31
32
|
pathSuffix: '/samenvatting',
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Departure",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Arrival",
|
|
124
124
|
"ON_REQUEST": "On request",
|
|
125
|
-
"CHANGES": "transfers"
|
|
125
|
+
"CHANGES": "transfers",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Package not available"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Age",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "I want to book through my local travel agent",
|
|
156
157
|
"CHOOSE_OFFICE": "I choose an office",
|
|
157
158
|
"PERSON": "Person",
|
|
159
|
+
"REMOVE_TRAVELER": "Remove traveler",
|
|
160
|
+
"ADD_TRAVELER": "Add traveler",
|
|
161
|
+
"ADD_ROOM": "Add travel party",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "Belgium",
|
|
160
164
|
"NETHERLANDS": "Netherlands",
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Départ",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Arrivée",
|
|
124
124
|
"ON_REQUEST": "Sur demande",
|
|
125
|
-
"CHANGES": "correspondances"
|
|
125
|
+
"CHANGES": "correspondances",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Forfait non disponible"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Age",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "Je souhaite réserver auprès de mon agent de voyage local",
|
|
156
157
|
"CHOOSE_OFFICE": "Je choisis une agence",
|
|
157
158
|
"PERSON": "Personne",
|
|
159
|
+
"REMOVE_TRAVELER": "Supprimer le voyageur",
|
|
160
|
+
"ADD_TRAVELER": "Ajouter un voyageur",
|
|
161
|
+
"ADD_ROOM": "Ajouter un groupe de voyageurs",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "Belgique",
|
|
160
164
|
"NETHERLANDS": "Pays-Bas",
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Vertrek",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Aankomst",
|
|
124
124
|
"ON_REQUEST": "Op aanvraag",
|
|
125
|
-
"CHANGES": "overstappen"
|
|
125
|
+
"CHANGES": "overstappen",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Pakket niet beschikbaar"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Leeftijd",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "Ik wens te boeken bij mijn lokale reisagent",
|
|
156
157
|
"CHOOSE_OFFICE": "Ik kies een kantoor",
|
|
157
158
|
"PERSON": "Persoon",
|
|
159
|
+
"REMOVE_TRAVELER": "Verwijder reiziger",
|
|
160
|
+
"ADD_TRAVELER": "Voeg reiziger toe",
|
|
161
|
+
"ADD_ROOM": "Voeg reisgezelschap toe",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "België",
|
|
160
164
|
"NETHERLANDS": "Nederland",
|