@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-booking-component",
3
- "version": "1.4.30",
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",
@@ -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
- allSteps.push(translations.STEPS.PERSONAL_DETAILS);
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 isUnvailable = useSelector(selectIsUnavailable);
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
- const rooms = getRoomsFromParams(params, 'rooms');
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, bookingAttributes, rooms, bookingNumber, packageDetails]);
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
- <StepRoute path={travellers.pathSuffix} number={numberIndex++} title={translations.STEPS.PERSONAL_DETAILS} component={<TravelersForm />} />
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
- {packageDetails && <Sidebar productName={productName} thumbnailUrl={thumbnailUrl} />}
284
+ <Sidebar productName={productName} thumbnailUrl={thumbnailUrl} />
269
285
  </div>
270
286
  </div>
271
287
  )}
272
- {!packageDetails && !bookingNumber && !isUnvailable && (
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
- {isUnvailable && (
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 { selectBookingQueryString, selectIsFetchingProductOptions, selectPackageDetails, selectPackageFlights, selectTranslations } from '../booking/selectors';
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
- dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
116
+ if (travelersFirstStep) {
117
+ dispatch(setCurrentStep(SUMMARY_STEP));
118
+ } else {
119
+ dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
120
+ }
115
121
  } else {
116
- navigate(`${settings.basePath}${settings.travellers.pathSuffix}?${bookingQueryString}`);
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
- <div className="pricing-summary__wrapper">
79
- <div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
80
- <div className="pricing-summary__group">
81
- <h6 className="pricing-summary__title">{translations.SIDEBAR.TRAVEL_INFO}</h6>
82
- {!isEmpty(travelerRooms) &&
83
- travelerRooms?.map((room, rIndex) => (
84
- <div className="pricing-summary__row" key={rIndex}>
85
- <div className="pricing-summary__property">
86
- {travelerRooms.length > 1 && `${translations.SHARED.ROOM} ${rIndex + 1}`}
87
- {travelerRooms.length === 1 && translations.ROOM_OPTIONS_FORM.TRAVELER_GROUP}
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
- <div className="pricing-summary__value">{room}</div>
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
- {startDateText && (
95
- <div className="pricing-summary__row">
96
- <div className="pricing-summary__property">
97
- {startDateText && endDateText ? translations.SIDEBAR.DEPARTURE : translations.SIDEBAR.DEPARTURE_SINGLE}
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
- <div className="pricing-summary__value">{startDateText}</div>
100
- </div>
101
- )}
111
+ )}
112
+ </div>
102
113
 
103
- {endDateText && (
104
- <div className="pricing-summary__row">
105
- <div className="pricing-summary__property">{translations.SIDEBAR.ARRIVAL}</div>
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
- {!isLoading && returnFlightMetaData && <SidebarFlight title={translations.SIDEBAR.ARRIVAL_FLIGHT} flightMetaData={returnFlightMetaData} />}
119
+ {!isLoading && returnFlightMetaData && <SidebarFlight title={translations.SIDEBAR.ARRIVAL_FLIGHT} flightMetaData={returnFlightMetaData} />}
117
120
 
118
- {accommodations && (
119
- <div className="pricing-summary__group">
120
- <h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
121
- {accommodations.map((accommodation) => {
122
- let option = accommodation.options.find((x) => x.isSelected);
123
- return (
124
- <div key={accommodation.index}>
125
- <div className="pricing-summary__row">
126
- <div className="pricing-summary__property">
127
- {option?.accommodationName}
128
- {isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
129
- {option?.isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
130
- </div>
131
- {/*<div className="pricing-summary__value">
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
- <div className="pricing-summary__row">
136
- <div className="price-summarty__property">{option?.regimeName}</div>
137
- <div className="price-summary__value">{!isFlightOnly && getDatePeriodText(option?.from, option?.to, true)}</div>
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
- {!isLoading && canShowPriceBreakdownSection && (
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">
@@ -6,6 +6,7 @@ import { TravelersFormValues, RoomTraveler } from '../../types';
6
6
 
7
7
  export interface TravelersFormState {
8
8
  formValues?: TravelersFormValues;
9
+ isTravelersFirst?: boolean;
9
10
  }
10
11
 
11
12
  export const CHILD_MAX_AGE = 17;
@@ -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(fetchPriceDetails());
145
+ dispatch(fetchPackage());
139
146
 
140
147
  if (settings.skipRouter) {
141
- dispatch(setCurrentStep(SUMMARY_STEP));
148
+ if (travelersFirstStep) {
149
+ dispatch(setCurrentStep(FLIGHT_OPTIONS_FORM_STEP));
150
+ } else {
151
+ dispatch(setCurrentStep(SUMMARY_STEP));
152
+ }
142
153
  } else {
143
- navigate(`${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`);
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="cta">
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',
@@ -25,6 +25,7 @@ export interface Settings {
25
25
  formFields?: FormField[];
26
26
  mainBookerFormFields?: FormField[];
27
27
  countries?: Country[];
28
+ travelersFirstStep?: boolean;
28
29
  };
29
30
  summary: {
30
31
  pathSuffix: string;
@@ -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",