@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.
Files changed (43) hide show
  1. package/build/build-cjs/index.js +621 -2822
  2. package/build/build-cjs/search-results/store/search-results-slice.d.ts +6 -3
  3. package/build/build-cjs/search-results/types.d.ts +9 -12
  4. package/build/build-esm/index.js +617 -2625
  5. package/build/build-esm/search-results/store/search-results-slice.d.ts +6 -3
  6. package/build/build-esm/search-results/types.d.ts +9 -12
  7. package/package.json +2 -3
  8. package/src/booking-product/components/dates.tsx +9 -4
  9. package/src/booking-wizard/components/step-indicator.tsx +11 -2
  10. package/src/booking-wizard/features/booking/booking-slice.ts +27 -2
  11. package/src/booking-wizard/features/booking/booking.tsx +32 -15
  12. package/src/booking-wizard/features/booking/selectors.ts +6 -0
  13. package/src/booking-wizard/features/flight-options/index.tsx +27 -3
  14. package/src/booking-wizard/features/product-options/option-room.tsx +1 -1
  15. package/src/booking-wizard/features/product-options/options-form.tsx +14 -4
  16. package/src/booking-wizard/features/room-options/room.tsx +1 -1
  17. package/src/booking-wizard/features/sidebar/index.tsx +4 -1
  18. package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -3
  19. package/src/booking-wizard/features/sidebar/sidebar.tsx +112 -104
  20. package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +1 -0
  21. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +146 -10
  22. package/src/booking-wizard/settings-context.ts +2 -1
  23. package/src/booking-wizard/types.ts +1 -0
  24. package/src/qsm/components/search-input-group/index.tsx +17 -8
  25. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +78 -83
  26. package/src/search-results/components/hotel/hotel-card.tsx +54 -24
  27. package/src/search-results/components/icon.tsx +13 -0
  28. package/src/search-results/components/item-picker/index.tsx +5 -7
  29. package/src/search-results/components/search-results-container/search-results-container.tsx +72 -117
  30. package/src/search-results/components/tab-views/index.tsx +22 -3
  31. package/src/search-results/features/flights/flight-search-results-self-contained.tsx +0 -13
  32. package/src/search-results/features/hotels/hotel-flight-search-results-self-contained.tsx +1 -8
  33. package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +1 -14
  34. package/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.tsx +1 -9
  35. package/src/search-results/store/search-results-slice.ts +11 -4
  36. package/src/search-results/types.ts +11 -16
  37. package/src/shared/translations/en-GB.json +5 -1
  38. package/src/shared/translations/fr-BE.json +5 -1
  39. package/src/shared/translations/nl-BE.json +5 -1
  40. package/styles/components/_form.scss +51 -2
  41. package/styles/components/_passenger-picker.scss +3 -2
  42. package/styles/components/_qsm.scss +1 -1
  43. package/styles/qsm/_qsm.scss +67 -6
@@ -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;
@@ -74,22 +74,30 @@ const SearchInputGroup: React.FC<Props> = ({
74
74
 
75
75
  if (small) return;
76
76
 
77
- if (input.length === 3 && autoComplete) {
78
- const exactIataMatch = findExactIataMatch(options, input);
77
+ dispatch(setActiveSearchField(fieldKey));
78
+ dispatch(setSearchResults(match(input)));
79
+ },
80
+ [dispatch, fieldKey, small, match, options]
81
+ );
82
+
83
+ const handleKeyDown = useCallback(
84
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
85
+ if (!['Tab', 'Enter'].includes(e.key)) return;
86
+
87
+ if (value.length === 3 && autoComplete) {
88
+ const exactIataMatch = findExactIataMatch(options, value);
79
89
 
80
90
  if (exactIataMatch) {
91
+ if (e.key === 'Enter') {
92
+ e.preventDefault();
93
+ }
81
94
  dispatch(setFieldValue({ fieldKey, value: exactIataMatch.value }));
82
95
  dispatch(setSearchResults([]));
83
96
  dispatch(setActiveSearchField(null));
84
- return;
85
97
  }
86
98
  }
87
-
88
- // Normal typeahead behavior
89
- dispatch(setActiveSearchField(fieldKey));
90
- dispatch(setSearchResults(match(input)));
91
99
  },
92
- [dispatch, fieldKey, small, match, options]
100
+ [value, autoComplete, options, dispatch, fieldKey]
93
101
  );
94
102
 
95
103
  const handleOptionSelect = useCallback(
@@ -155,6 +163,7 @@ const SearchInputGroup: React.FC<Props> = ({
155
163
  readOnly={small}
156
164
  onFocus={click}
157
165
  onClick={(e) => e.stopPropagation()}
166
+ onKeyDown={handleKeyDown}
158
167
  onChange={(e) => !small && !readOnlyForced && handleInputChange(e.target.value)}
159
168
  className={`qsm__input${isSecondInput ? ' qsm__input--splittable' : ' u-ps-2'}`}
160
169
  placeholder={placeholder}