@qite/tide-booking-component 1.3.2 → 1.3.4

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 (133) hide show
  1. package/.vs/ProjectSettings.json +3 -3
  2. package/.vs/VSWorkspaceState.json +5 -5
  3. package/README.md +8 -8
  4. package/build/build-cjs/booking-wizard/types.d.ts +1 -5
  5. package/build/build-cjs/index.js +58 -101
  6. package/build/build-esm/booking-wizard/types.d.ts +1 -5
  7. package/build/build-esm/index.js +58 -101
  8. package/package.json +75 -75
  9. package/rollup.config.js +23 -23
  10. package/src/booking-product/components/age-select.tsx +35 -35
  11. package/src/booking-product/components/amount-input.tsx +78 -78
  12. package/src/booking-product/components/date-range-picker/calendar-day.tsx +58 -58
  13. package/src/booking-product/components/date-range-picker/calendar.tsx +178 -178
  14. package/src/booking-product/components/date-range-picker/index.tsx +196 -196
  15. package/src/booking-product/components/dates.tsx +136 -136
  16. package/src/booking-product/components/footer.tsx +69 -69
  17. package/src/booking-product/components/header.tsx +79 -79
  18. package/src/booking-product/components/icon.tsx +251 -251
  19. package/src/booking-product/components/product.tsx +314 -314
  20. package/src/booking-product/components/rating.tsx +21 -21
  21. package/src/booking-product/components/rooms.tsx +195 -195
  22. package/src/booking-product/index.tsx +30 -30
  23. package/src/booking-product/settings-context.ts +14 -14
  24. package/src/booking-product/types.ts +28 -28
  25. package/src/booking-product/utils/api.ts +25 -25
  26. package/src/booking-product/utils/price.ts +29 -29
  27. package/src/booking-wizard/api-settings-slice.ts +24 -24
  28. package/src/booking-wizard/components/icon.tsx +508 -508
  29. package/src/booking-wizard/components/labeled-input.tsx +64 -64
  30. package/src/booking-wizard/components/labeled-select.tsx +69 -69
  31. package/src/booking-wizard/components/message.tsx +34 -34
  32. package/src/booking-wizard/components/multi-range-filter.tsx +113 -113
  33. package/src/booking-wizard/components/print-offer-button.tsx +63 -66
  34. package/src/booking-wizard/components/product-card.tsx +37 -37
  35. package/src/booking-wizard/components/step-indicator.tsx +51 -51
  36. package/src/booking-wizard/components/step-route.tsx +27 -27
  37. package/src/booking-wizard/declarations.d.ts +4 -4
  38. package/src/booking-wizard/features/booking/api.ts +49 -49
  39. package/src/booking-wizard/features/booking/booking-self-contained.tsx +389 -389
  40. package/src/booking-wizard/features/booking/booking-slice.ts +663 -663
  41. package/src/booking-wizard/features/booking/booking.tsx +361 -361
  42. package/src/booking-wizard/features/booking/constants.ts +16 -16
  43. package/src/booking-wizard/features/booking/selectors.ts +441 -441
  44. package/src/booking-wizard/features/confirmation/confirmation.tsx +97 -97
  45. package/src/booking-wizard/features/error/error.tsx +78 -78
  46. package/src/booking-wizard/features/flight-options/flight-filter.tsx +432 -432
  47. package/src/booking-wizard/features/flight-options/flight-option-flight.tsx +385 -385
  48. package/src/booking-wizard/features/flight-options/flight-option-modal.tsx +229 -229
  49. package/src/booking-wizard/features/flight-options/flight-option.tsx +81 -81
  50. package/src/booking-wizard/features/flight-options/flight-utils.ts +522 -516
  51. package/src/booking-wizard/features/flight-options/index.tsx +196 -196
  52. package/src/booking-wizard/features/price-details/price-details-api.ts +24 -24
  53. package/src/booking-wizard/features/price-details/price-details-slice.ts +178 -178
  54. package/src/booking-wizard/features/price-details/util.ts +155 -155
  55. package/src/booking-wizard/features/product-options/no-options.tsx +21 -21
  56. package/src/booking-wizard/features/product-options/none-option.tsx +120 -120
  57. package/src/booking-wizard/features/product-options/option-booking-airline-group.tsx +64 -64
  58. package/src/booking-wizard/features/product-options/option-booking-group.tsx +216 -216
  59. package/src/booking-wizard/features/product-options/option-item.tsx +317 -317
  60. package/src/booking-wizard/features/product-options/option-pax-card.tsx +201 -201
  61. package/src/booking-wizard/features/product-options/option-pax-group.tsx +175 -175
  62. package/src/booking-wizard/features/product-options/option-room.tsx +321 -321
  63. package/src/booking-wizard/features/product-options/option-unit-group.tsx +198 -198
  64. package/src/booking-wizard/features/product-options/option-units-card.tsx +185 -185
  65. package/src/booking-wizard/features/product-options/options-form.tsx +481 -563
  66. package/src/booking-wizard/features/room-options/index.tsx +187 -187
  67. package/src/booking-wizard/features/room-options/room-utils.ts +190 -190
  68. package/src/booking-wizard/features/room-options/room.tsx +160 -160
  69. package/src/booking-wizard/features/room-options/traveler-rooms.tsx +75 -75
  70. package/src/booking-wizard/features/sidebar/index.tsx +76 -76
  71. package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +68 -68
  72. package/src/booking-wizard/features/sidebar/sidebar-util.ts +177 -177
  73. package/src/booking-wizard/features/sidebar/sidebar.tsx +364 -364
  74. package/src/booking-wizard/features/summary/summary-booking-option-pax.tsx +25 -25
  75. package/src/booking-wizard/features/summary/summary-booking-option-unit.tsx +25 -25
  76. package/src/booking-wizard/features/summary/summary-flight.tsx +39 -39
  77. package/src/booking-wizard/features/summary/summary-per-booking-option-group.tsx +69 -69
  78. package/src/booking-wizard/features/summary/summary-per-pax-option-group.tsx +63 -63
  79. package/src/booking-wizard/features/summary/summary-per-unit-option-group.tsx +66 -66
  80. package/src/booking-wizard/features/summary/summary-slice.ts +28 -28
  81. package/src/booking-wizard/features/summary/summary.tsx +674 -674
  82. package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +164 -164
  83. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +754 -754
  84. package/src/booking-wizard/features/travelers-form/type-ahead-input.tsx +101 -101
  85. package/src/booking-wizard/features/travelers-form/validate-form.ts +245 -245
  86. package/src/booking-wizard/index.tsx +36 -36
  87. package/src/booking-wizard/settings-context.ts +62 -62
  88. package/src/booking-wizard/store.ts +31 -31
  89. package/src/booking-wizard/types.ts +279 -279
  90. package/src/booking-wizard/use-offer-printer.ts +117 -136
  91. package/src/index.ts +4 -4
  92. package/src/shared/components/loader.tsx +16 -16
  93. package/src/shared/translations/en-GB.json +237 -237
  94. package/src/shared/translations/fr-BE.json +238 -238
  95. package/src/shared/translations/nl-BE.json +237 -237
  96. package/src/shared/types.ts +4 -4
  97. package/src/shared/utils/class-util.ts +9 -9
  98. package/src/shared/utils/localization-util.ts +62 -62
  99. package/src/shared/utils/query-string-util.ts +119 -119
  100. package/src/shared/utils/tide-api-utils.ts +36 -36
  101. package/styles/booking-product-variables.scss +394 -394
  102. package/styles/booking-product.scss +446 -446
  103. package/styles/booking-wizard-variables.scss +873 -873
  104. package/styles/booking-wizard.scss +59 -59
  105. package/styles/components/_animations.scss +39 -39
  106. package/styles/components/_base.scss +107 -107
  107. package/styles/components/_booking.scss +879 -879
  108. package/styles/components/_button.scss +238 -238
  109. package/styles/components/_checkbox.scss +219 -219
  110. package/styles/components/_cta.scss +208 -208
  111. package/styles/components/_date-list.scss +41 -41
  112. package/styles/components/_date-range-picker.scss +225 -225
  113. package/styles/components/_decrement-increment.scss +35 -35
  114. package/styles/components/_dropdown.scss +72 -72
  115. package/styles/components/_flight-option.scss +1429 -1429
  116. package/styles/components/_form.scss +1583 -1583
  117. package/styles/components/_info-message.scss +71 -71
  118. package/styles/components/_input.scss +25 -25
  119. package/styles/components/_list.scss +187 -187
  120. package/styles/components/_loader.scss +72 -72
  121. package/styles/components/_mixins.scss +550 -550
  122. package/styles/components/_placeholders.scss +166 -166
  123. package/styles/components/_pricing-summary.scss +155 -155
  124. package/styles/components/_qsm.scss +17 -17
  125. package/styles/components/_radiobutton.scss +170 -170
  126. package/styles/components/_select-wrapper.scss +80 -80
  127. package/styles/components/_spinner.scss +29 -29
  128. package/styles/components/_step-indicators.scss +168 -168
  129. package/styles/components/_table.scss +81 -81
  130. package/styles/components/_tree.scss +530 -530
  131. package/styles/components/_typeahead.scss +281 -281
  132. package/styles/components/_variables.scss +89 -89
  133. package/tsconfig.json +24 -24
@@ -1,563 +1,481 @@
1
- import React, { useContext, useEffect } from "react";
2
-
3
- import {
4
- AirlineBookingPackageOption,
5
- AirportBookingPackageOption,
6
- BookingAirlineGroup,
7
- BookingAirportGroup,
8
- BookingOptionGroup,
9
- BookingOptionPax,
10
- BookingOptionUnit,
11
- BookingPackageFlight,
12
- Pax,
13
- PerBookingPackageOption,
14
- } from "@qite/tide-client/build/types";
15
- import { Link, navigate } from "@reach/router";
16
- import { isEmpty } from "lodash";
17
- import { useSelector } from "react-redux";
18
- import { buildClassName } from "../../../shared/utils/class-util";
19
- import SettingsContext from "../../settings-context";
20
- import { useAppDispatch } from "../../store";
21
- import {
22
- setCurrentStep,
23
- setPackage,
24
- setPackageAirlineGroups,
25
- setPackageAirportGroups,
26
- setPackageGroups,
27
- setPackageOptionPax,
28
- setPackageOptionUnits,
29
- setPackageRooms,
30
- setTagIds,
31
- } from "../booking/booking-slice";
32
- import {
33
- FLIGHT_OPTIONS_FORM_STEP,
34
- ROOM_OPTIONS_FORM_STEP,
35
- TRAVELERS_FORM_STEP,
36
- } from "../booking/constants";
37
- import {
38
- selectAvailabilities,
39
- selectBookingPackagePax,
40
- selectBookingQueryString,
41
- selectIsFetchingProductOptions,
42
- selectPackageAirlineGroups,
43
- selectPackageAirportGroups,
44
- selectPackageDetails,
45
- selectPackageGroups,
46
- selectPackageOptionPax,
47
- selectPackageOptionUnits,
48
- selectPackageRooms,
49
- selectPackageTags,
50
- selectRequestRooms,
51
- selectTagIds,
52
- selectTranslations,
53
- } from "../booking/selectors";
54
- import { fetchPriceDetails } from "../price-details/price-details-slice";
55
- import { updatePackageRooms } from "../room-options/room-utils";
56
- import NoOptions from "./no-options";
57
- import OptionBookingAirlineGroup from "./option-booking-airline-group";
58
- import OptionBookingGroup from "./option-booking-group";
59
- import OptionPaxCard from "./option-pax-card";
60
- import OptionRoom from "./option-room";
61
- import OptionUnitsCard from "./option-units-card";
62
- import PrintOfferButton from "../../components/print-offer-button";
63
-
64
- interface OptionsFormProps {}
65
-
66
- const OptionsForm: React.FC<OptionsFormProps> = () => {
67
- const settings = useContext(SettingsContext);
68
- const { token } = settings;
69
- const translations = useSelector(selectTranslations);
70
- const dispatch = useAppDispatch();
71
- const packageDetails = useSelector(selectPackageDetails);
72
- const requestRooms = useSelector(selectRequestRooms);
73
- const requestRoomsPax = requestRooms?.flatMap(
74
- (x) => x.pax
75
- );
76
- const bookingQueryString = useSelector(
77
- selectBookingQueryString
78
- );
79
- const isLoading = useSelector(
80
- selectIsFetchingProductOptions
81
- );
82
-
83
- const groups = useSelector(selectPackageGroups);
84
- const airlineGroups = useSelector(
85
- selectPackageAirlineGroups
86
- );
87
- const airportGroups = useSelector(
88
- selectPackageAirportGroups
89
- );
90
- const optionUnits = useSelector(selectPackageOptionUnits);
91
- const optionPax = useSelector(selectPackageOptionPax);
92
- const availabilities = useSelector(selectAvailabilities);
93
-
94
- // ROOMS
95
- const showRoomOptions = settings.roomOptions.isHidden;
96
- const packageRooms = useSelector(selectPackageRooms);
97
- const pax = useSelector(selectBookingPackagePax);
98
-
99
- const getRoomPax = (index: number) => {
100
- var room = requestRooms?.find((x) => x.index == index);
101
- var bookingPackagePax = pax.filter((x) =>
102
- room?.pax.some((y) => y.id == x.id)
103
- );
104
- return bookingPackagePax.length > 0
105
- ? bookingPackagePax
106
- : room?.pax ?? [];
107
- };
108
-
109
- const handleOnRoomChange = (
110
- index: number,
111
- accommodationCode: string,
112
- regimeCode: string | null
113
- ) => {
114
- if (!packageRooms) return;
115
-
116
- const updatedPackageRooms = updatePackageRooms(
117
- packageRooms,
118
- index,
119
- accommodationCode,
120
- regimeCode,
121
- availabilities!
122
- );
123
-
124
- dispatch(setPackageRooms(updatedPackageRooms));
125
- dispatch(fetchPriceDetails());
126
- };
127
-
128
- const getPax = () => {
129
- if (!packageDetails) return undefined;
130
- const selectedOption =
131
- packageDetails.options.find((o) => o.isSelected) ??
132
- packageDetails.options[0];
133
- return selectedOption?.requestRooms.flatMap(
134
- (r) => r.pax
135
- ) as Pax[];
136
- };
137
-
138
- // TAGS
139
- const packageTags = settings.hideTags
140
- ? []
141
- : useSelector(selectPackageTags);
142
- let tagIds = useSelector(selectTagIds) ?? [];
143
-
144
- const handleSubmit: React.FormEventHandler<
145
- HTMLFormElement
146
- > = (e) => {
147
- if (settings.skipRouter) {
148
- dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
149
- } else {
150
- navigate(
151
- `${settings.basePath}${settings.travellers.pathSuffix}?${bookingQueryString}`
152
- );
153
- }
154
-
155
- e.preventDefault();
156
- };
157
-
158
- const handleOnPaxChange = (pax: BookingOptionPax[]) => {
159
- dispatch(setPackageOptionPax(pax));
160
- dispatch(fetchPriceDetails());
161
- };
162
-
163
- const handleOnUnitsChange = (
164
- units: BookingOptionUnit[]
165
- ) => {
166
- dispatch(setPackageOptionUnits(units));
167
- dispatch(fetchPriceDetails());
168
- };
169
-
170
- const handleOnGroupChange = (
171
- group: BookingOptionGroup<PerBookingPackageOption>
172
- ) => {
173
- if (groups) {
174
- const updatedGroups = groups?.map((g) =>
175
- g.name === group.name ? group : g
176
- );
177
-
178
- dispatch(setPackageGroups(updatedGroups));
179
- dispatch(fetchPriceDetails());
180
- }
181
- };
182
-
183
- const handleOnAirlineGroupChange = (
184
- group: BookingAirlineGroup<AirlineBookingPackageOption>
185
- ) => {
186
- if (airlineGroups) {
187
- const updatedGroups = airlineGroups.map((g) =>
188
- g.label === group.label ? group : g
189
- );
190
-
191
- dispatch(setPackageAirlineGroups(updatedGroups));
192
- dispatch(fetchPriceDetails());
193
- }
194
- };
195
-
196
- const handleOnAirportGroupChange = (
197
- group: BookingAirportGroup<AirportBookingPackageOption>
198
- ) => {
199
- if (airportGroups) {
200
- const updatedGroups = airportGroups.map((g) =>
201
- g.label === group.label ? group : g
202
- );
203
-
204
- dispatch(setPackageAirportGroups(updatedGroups));
205
- dispatch(fetchPriceDetails());
206
- }
207
- };
208
-
209
- const handleOnTagChange = (
210
- id: number,
211
- checked: boolean
212
- ) => {
213
- let updatedTags = [...tagIds];
214
- if (checked) {
215
- if (!updatedTags?.includes(id)) {
216
- updatedTags?.push(id);
217
- }
218
- } else {
219
- updatedTags = updatedTags?.filter((x) => x != id);
220
- }
221
-
222
- dispatch(setTagIds(updatedTags));
223
- dispatch(fetchPriceDetails());
224
- };
225
-
226
- useEffect(() => {
227
- if (
228
- packageDetails &&
229
- settings.roomOptions.isHidden &&
230
- settings.flightOptions.isHidden
231
- ) {
232
- const params = new URLSearchParams(location.search);
233
-
234
- const outwardFlight =
235
- params.get("outwardflight") ?? undefined;
236
- const returnFlight =
237
- params.get("returnflight") ?? undefined;
238
- if (outwardFlight && returnFlight) {
239
- const desiredOutwardFlight =
240
- packageDetails.outwardFlights.find(
241
- (x) => x.entryLineGuid == outwardFlight
242
- );
243
- const desiredReturnFlight =
244
- packageDetails.returnFlights.find(
245
- (x) => x.entryLineGuid == returnFlight
246
- );
247
- if (desiredOutwardFlight && desiredReturnFlight) {
248
- dispatch(
249
- setPackage({
250
- ...packageDetails,
251
- outwardFlights:
252
- packageDetails.outwardFlights.map(
253
- (flight) => {
254
- return {
255
- ...flight,
256
- isSelected:
257
- flight.entryLineGuid ==
258
- desiredOutwardFlight.entryLineGuid
259
- ? true
260
- : false,
261
- } as BookingPackageFlight;
262
- }
263
- ),
264
- returnFlights:
265
- packageDetails.returnFlights.map(
266
- (flight) => {
267
- return {
268
- ...flight,
269
- isSelected:
270
- flight.entryLineGuid ==
271
- desiredReturnFlight.entryLineGuid
272
- ? true
273
- : false,
274
- } as BookingPackageFlight;
275
- }
276
- ),
277
- })
278
- );
279
- }
280
- }
281
- }
282
- dispatch(fetchPriceDetails());
283
- }, []);
284
-
285
- const goPrevious = () => {
286
- if (settings.roomOptions.isHidden) {
287
- dispatch(setCurrentStep(FLIGHT_OPTIONS_FORM_STEP));
288
- } else {
289
- dispatch(setCurrentStep(ROOM_OPTIONS_FORM_STEP));
290
- }
291
- };
292
-
293
- const previousUrl = settings.roomOptions.isHidden
294
- ? `${settings.basePath}${settings.flightOptions.pathSuffix}?${bookingQueryString}`
295
- : `${settings.basePath}${settings.roomOptions.pathSuffix}?${bookingQueryString}`;
296
- const hasPrevious =
297
- !settings.roomOptions.isHidden ||
298
- !settings.flightOptions.isHidden;
299
- const showPackageTagsOrRoomoptions =
300
- showRoomOptions ||
301
- (packageTags && !isEmpty(packageTags));
302
-
303
- return (
304
- <>
305
- <form
306
- className='form'
307
- name='booking--options'
308
- id='booking--options'
309
- noValidate
310
- onSubmit={handleSubmit}
311
- >
312
- {isLoading && settings.loaderComponent}
313
- {!isLoading && (
314
- <div className='form__region'>
315
- {showPackageTagsOrRoomoptions && (
316
- <div className='form__group'>
317
- <div className='booking-card'>
318
- <div className='booking-card__body'>
319
- <div
320
- className={buildClassName([
321
- "booking-card__group",
322
- "booking-card__group--package",
323
- ])}
324
- >
325
- {showRoomOptions && (
326
- <span className='booking-card__tag'>
327
- {
328
- translations.OPTIONS_FORM
329
- .PACKAGE
330
- }
331
- </span>
332
- )}
333
- <div className='booking-card__group-body'>
334
- {showRoomOptions && (
335
- <table className='table table--striped'>
336
- <tbody>
337
- {packageRooms &&
338
- packageRooms.map((room) => (
339
- <OptionRoom
340
- key={room.index}
341
- packageRoom={room}
342
- pax={getRoomPax(
343
- room.index
344
- )}
345
- optionPax={optionPax}
346
- onRoomChange={
347
- handleOnRoomChange
348
- }
349
- />
350
- ))}
351
- </tbody>
352
- </table>
353
- )}
354
- {packageTags &&
355
- !isEmpty(packageTags) && (
356
- <div className='booking-card__tag-translations'>
357
- {packageTags.map(
358
- (tag, index) => (
359
- <label
360
- key={index}
361
- htmlFor={`tag-translation-${index}-${tag.title}`}
362
- className='checkbox__label tag-translation'
363
- >
364
- <div className='tag-translation-input__container'>
365
- <input
366
- type='checkbox'
367
- id={`tag-translation-${index}-${tag.title}`}
368
- name={`tag-translation-${index}-${tag.title}`}
369
- className='checkbox__input'
370
- checked={tagIds?.includes(
371
- tag.id
372
- )}
373
- onChange={(e) =>
374
- handleOnTagChange(
375
- tag.id,
376
- e.target.checked
377
- )
378
- }
379
- />
380
- </div>
381
- <span className='tag-translation__title'>
382
- {tag.title}
383
- </span>
384
- &nbsp;
385
- <span className='tag-translation__description'>
386
- {tag.description}
387
- </span>
388
- </label>
389
- )
390
- )}
391
- </div>
392
- )}
393
- </div>
394
- </div>
395
- </div>
396
- </div>
397
- </div>
398
- )}
399
- {optionUnits && !isEmpty(optionUnits) && (
400
- <div className='form__group'>
401
- <div className='booking-card'>
402
- <div className='booking-card__header'>
403
- <h2 className='booking-card__header-heading'>
404
- {
405
- translations.OPTIONS_FORM
406
- .PER_UNIT_TITLE
407
- }
408
- </h2>
409
- </div>
410
- <div className='booking-card__body'>
411
- <OptionUnitsCard
412
- units={optionUnits}
413
- onUnitsChange={handleOnUnitsChange}
414
- />
415
- </div>
416
- </div>
417
- </div>
418
- )}
419
-
420
- {optionPax && !isEmpty(optionPax) && (
421
- <div className='form__group'>
422
- <div className='booking-card'>
423
- <div className='booking-card__header'>
424
- <h2 className='booking-card__header-heading'>
425
- {
426
- translations.OPTIONS_FORM
427
- .PER_PAX_TITLE
428
- }
429
- </h2>
430
- </div>
431
- <div className='booking-card__body'>
432
- <OptionPaxCard
433
- pax={optionPax}
434
- onPaxChange={handleOnPaxChange}
435
- requestRoomsPax={requestRoomsPax}
436
- />
437
- </div>
438
- </div>
439
- </div>
440
- )}
441
-
442
- {groups && !isEmpty(groups) && (
443
- <div className='form__group'>
444
- <div className='booking-card'>
445
- <div className='booking-card__header'>
446
- <h2 className='booking-card__header-heading'>
447
- {
448
- translations.OPTIONS_FORM
449
- .PER_BOOKING_TITLE
450
- }
451
- </h2>
452
- </div>
453
- <div className='booking-card__body'>
454
- <div className='booking-card__group booking-card__group--active'>
455
- {groups.map((group, i) => (
456
- <OptionBookingGroup
457
- key={`${group.name}_${i}`}
458
- group={group}
459
- firstClassName={
460
- "booking-card__group-body"
461
- }
462
- secondClassName={
463
- "booking-card__group-heading"
464
- }
465
- parentId={`booking_${group.name}`}
466
- onGroupChange={
467
- handleOnGroupChange
468
- }
469
- />
470
- ))}
471
- {airlineGroups?.map((group, i) => (
472
- <OptionBookingAirlineGroup
473
- key={`${group.label}_${i}`}
474
- airGroup={group}
475
- onGroupChange={
476
- handleOnAirlineGroupChange
477
- }
478
- />
479
- ))}
480
- {airportGroups?.map((group, i) => (
481
- <OptionBookingAirlineGroup
482
- key={`${group.label}_${i}`}
483
- airGroup={group}
484
- onGroupChange={
485
- handleOnAirportGroupChange
486
- }
487
- />
488
- ))}
489
- </div>
490
- </div>
491
- </div>
492
- </div>
493
- )}
494
-
495
- {isEmpty(groups) &&
496
- isEmpty(optionUnits) &&
497
- isEmpty(optionPax) && <NoOptions />}
498
- </div>
499
- )}
500
- <div className='booking__navigator'>
501
- {hasPrevious && (
502
- <>
503
- {settings.skipRouter ? (
504
- <button
505
- type='button'
506
- title={translations.STEPS.PREVIOUS}
507
- onClick={() => goPrevious()}
508
- className='cta cta--secondary'
509
- >
510
- {translations.STEPS.PREVIOUS}
511
- </button>
512
- ) : (
513
- <Link
514
- to={previousUrl}
515
- title={translations.STEPS.PREVIOUS}
516
- className='cta cta--secondary'
517
- >
518
- {translations.STEPS.PREVIOUS}
519
- </Link>
520
- )}
521
- </>
522
- )}
523
- {token &&
524
- settings.options.reportPrintActionId && (
525
- <PrintOfferButton
526
- bookingPackage={packageDetails}
527
- getPax={getPax}
528
- tagIds={tagIds}
529
- printActionId={
530
- settings.options.reportPrintActionId
531
- }
532
- labelIdle={
533
- translations.PRINT_OFFER_BUTTON.LABEL_IDLE
534
- }
535
- labelCreating={
536
- translations.PRINT_OFFER_BUTTON
537
- .LABEL_CREATING
538
- }
539
- labelPrinting={
540
- translations.PRINT_OFFER_BUTTON
541
- .LABEL_PRINTING
542
- }
543
- className='cta spinner-button'
544
- />
545
- )}
546
- <button
547
- type='submit'
548
- title={translations.STEPS.NEXT}
549
- disabled={isLoading}
550
- className={buildClassName([
551
- "cta",
552
- isLoading && "cta--disabled",
553
- ])}
554
- >
555
- {translations.STEPS.NEXT}
556
- </button>
557
- </div>
558
- </form>
559
- </>
560
- );
561
- };
562
-
563
- export default OptionsForm;
1
+ import React, { useContext, useEffect } from "react";
2
+
3
+ import {
4
+ AirlineBookingPackageOption,
5
+ AirportBookingPackageOption,
6
+ BookingAirlineGroup,
7
+ BookingAirportGroup,
8
+ BookingOptionGroup,
9
+ BookingOptionPax,
10
+ BookingOptionUnit,
11
+ BookingPackageFlight,
12
+ Pax,
13
+ PerBookingPackageOption,
14
+ } from "@qite/tide-client/build/types";
15
+ import { Link, navigate } from "@reach/router";
16
+ import { isEmpty } from "lodash";
17
+ import { useSelector } from "react-redux";
18
+ import { buildClassName } from "../../../shared/utils/class-util";
19
+ import SettingsContext from "../../settings-context";
20
+ import { useAppDispatch } from "../../store";
21
+ import {
22
+ setCurrentStep,
23
+ setPackage,
24
+ setPackageAirlineGroups,
25
+ setPackageAirportGroups,
26
+ setPackageGroups,
27
+ setPackageOptionPax,
28
+ setPackageOptionUnits,
29
+ setPackageRooms,
30
+ setTagIds,
31
+ } from "../booking/booking-slice";
32
+ import {
33
+ FLIGHT_OPTIONS_FORM_STEP,
34
+ ROOM_OPTIONS_FORM_STEP,
35
+ TRAVELERS_FORM_STEP,
36
+ } from "../booking/constants";
37
+ import {
38
+ selectAvailabilities,
39
+ selectBookingPackagePax,
40
+ selectBookingQueryString,
41
+ selectIsFetchingProductOptions,
42
+ selectPackageAirlineGroups,
43
+ selectPackageAirportGroups,
44
+ selectPackageDetails,
45
+ selectPackageGroups,
46
+ selectPackageOptionPax,
47
+ selectPackageOptionUnits,
48
+ selectPackageRooms,
49
+ selectPackageTags,
50
+ selectRequestRooms,
51
+ selectTagIds,
52
+ selectTranslations,
53
+ } from "../booking/selectors";
54
+ import { fetchPriceDetails } from "../price-details/price-details-slice";
55
+ import { updatePackageRooms } from "../room-options/room-utils";
56
+ import NoOptions from "./no-options";
57
+ import OptionBookingAirlineGroup from "./option-booking-airline-group";
58
+ import OptionBookingGroup from "./option-booking-group";
59
+ import OptionPaxCard from "./option-pax-card";
60
+ import OptionRoom from "./option-room";
61
+ import OptionUnitsCard from "./option-units-card";
62
+ import PrintOfferButton from "../../components/print-offer-button";
63
+
64
+ interface OptionsFormProps {}
65
+
66
+ const OptionsForm: React.FC<OptionsFormProps> = () => {
67
+ const settings = useContext(SettingsContext);
68
+ const { token } = settings;
69
+ const translations = useSelector(selectTranslations);
70
+ const dispatch = useAppDispatch();
71
+ const packageDetails = useSelector(selectPackageDetails);
72
+ const requestRooms = useSelector(selectRequestRooms);
73
+ const requestRoomsPax = requestRooms?.flatMap((x) => x.pax);
74
+ const bookingQueryString = useSelector(selectBookingQueryString);
75
+ const isLoading = useSelector(selectIsFetchingProductOptions);
76
+
77
+ const groups = useSelector(selectPackageGroups);
78
+ const airlineGroups = useSelector(selectPackageAirlineGroups);
79
+ const airportGroups = useSelector(selectPackageAirportGroups);
80
+ const optionUnits = useSelector(selectPackageOptionUnits);
81
+ const optionPax = useSelector(selectPackageOptionPax);
82
+ const availabilities = useSelector(selectAvailabilities);
83
+
84
+ // ROOMS
85
+ const showRoomOptions = settings.roomOptions.isHidden;
86
+ const packageRooms = useSelector(selectPackageRooms);
87
+ const pax = useSelector(selectBookingPackagePax);
88
+
89
+ const getRoomPax = (index: number) => {
90
+ var room = requestRooms?.find((x) => x.index == index);
91
+ var bookingPackagePax = pax.filter((x) =>
92
+ room?.pax.some((y) => y.id == x.id)
93
+ );
94
+ return bookingPackagePax.length > 0 ? bookingPackagePax : room?.pax ?? [];
95
+ };
96
+
97
+ const handleOnRoomChange = (
98
+ index: number,
99
+ accommodationCode: string,
100
+ regimeCode: string | null
101
+ ) => {
102
+ if (!packageRooms) return;
103
+
104
+ const updatedPackageRooms = updatePackageRooms(
105
+ packageRooms,
106
+ index,
107
+ accommodationCode,
108
+ regimeCode,
109
+ availabilities!
110
+ );
111
+
112
+ dispatch(setPackageRooms(updatedPackageRooms));
113
+ dispatch(fetchPriceDetails());
114
+ };
115
+
116
+ const getPax = () => {
117
+ if (!packageDetails) return undefined;
118
+ const selectedOption =
119
+ packageDetails.options.find((o) => o.isSelected) ??
120
+ packageDetails.options[0];
121
+ return selectedOption?.requestRooms.flatMap((r) => r.pax) as Pax[];
122
+ };
123
+
124
+ // TAGS
125
+ const packageTags = settings.hideTags ? [] : useSelector(selectPackageTags);
126
+ let tagIds = useSelector(selectTagIds) ?? [];
127
+
128
+ const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
129
+ if (settings.skipRouter) {
130
+ dispatch(setCurrentStep(TRAVELERS_FORM_STEP));
131
+ } else {
132
+ navigate(
133
+ `${settings.basePath}${settings.travellers.pathSuffix}?${bookingQueryString}`
134
+ );
135
+ }
136
+
137
+ e.preventDefault();
138
+ };
139
+
140
+ const handleOnPaxChange = (pax: BookingOptionPax[]) => {
141
+ dispatch(setPackageOptionPax(pax));
142
+ dispatch(fetchPriceDetails());
143
+ };
144
+
145
+ const handleOnUnitsChange = (units: BookingOptionUnit[]) => {
146
+ dispatch(setPackageOptionUnits(units));
147
+ dispatch(fetchPriceDetails());
148
+ };
149
+
150
+ const handleOnGroupChange = (
151
+ group: BookingOptionGroup<PerBookingPackageOption>
152
+ ) => {
153
+ if (groups) {
154
+ const updatedGroups = groups?.map((g) =>
155
+ g.name === group.name ? group : g
156
+ );
157
+
158
+ dispatch(setPackageGroups(updatedGroups));
159
+ dispatch(fetchPriceDetails());
160
+ }
161
+ };
162
+
163
+ const handleOnAirlineGroupChange = (
164
+ group: BookingAirlineGroup<AirlineBookingPackageOption>
165
+ ) => {
166
+ if (airlineGroups) {
167
+ const updatedGroups = airlineGroups.map((g) =>
168
+ g.label === group.label ? group : g
169
+ );
170
+
171
+ dispatch(setPackageAirlineGroups(updatedGroups));
172
+ dispatch(fetchPriceDetails());
173
+ }
174
+ };
175
+
176
+ const handleOnAirportGroupChange = (
177
+ group: BookingAirportGroup<AirportBookingPackageOption>
178
+ ) => {
179
+ if (airportGroups) {
180
+ const updatedGroups = airportGroups.map((g) =>
181
+ g.label === group.label ? group : g
182
+ );
183
+
184
+ dispatch(setPackageAirportGroups(updatedGroups));
185
+ dispatch(fetchPriceDetails());
186
+ }
187
+ };
188
+
189
+ const handleOnTagChange = (id: number, checked: boolean) => {
190
+ let updatedTags = [...tagIds];
191
+ if (checked) {
192
+ if (!updatedTags?.includes(id)) {
193
+ updatedTags?.push(id);
194
+ }
195
+ } else {
196
+ updatedTags = updatedTags?.filter((x) => x != id);
197
+ }
198
+
199
+ dispatch(setTagIds(updatedTags));
200
+ dispatch(fetchPriceDetails());
201
+ };
202
+
203
+ useEffect(() => {
204
+ if (
205
+ packageDetails &&
206
+ settings.roomOptions.isHidden &&
207
+ settings.flightOptions.isHidden
208
+ ) {
209
+ const params = new URLSearchParams(location.search);
210
+
211
+ const outwardFlight = params.get("outwardflight") ?? undefined;
212
+ const returnFlight = params.get("returnflight") ?? undefined;
213
+ if (outwardFlight && returnFlight) {
214
+ const desiredOutwardFlight = packageDetails.outwardFlights.find(
215
+ (x) => x.entryLineGuid == outwardFlight
216
+ );
217
+ const desiredReturnFlight = packageDetails.returnFlights.find(
218
+ (x) => x.entryLineGuid == returnFlight
219
+ );
220
+ if (desiredOutwardFlight && desiredReturnFlight) {
221
+ dispatch(
222
+ setPackage({
223
+ ...packageDetails,
224
+ outwardFlights: packageDetails.outwardFlights.map((flight) => {
225
+ return {
226
+ ...flight,
227
+ isSelected:
228
+ flight.entryLineGuid == desiredOutwardFlight.entryLineGuid
229
+ ? true
230
+ : false,
231
+ } as BookingPackageFlight;
232
+ }),
233
+ returnFlights: packageDetails.returnFlights.map((flight) => {
234
+ return {
235
+ ...flight,
236
+ isSelected:
237
+ flight.entryLineGuid == desiredReturnFlight.entryLineGuid
238
+ ? true
239
+ : false,
240
+ } as BookingPackageFlight;
241
+ }),
242
+ })
243
+ );
244
+ }
245
+ }
246
+ }
247
+ dispatch(fetchPriceDetails());
248
+ }, []);
249
+
250
+ const goPrevious = () => {
251
+ if (settings.roomOptions.isHidden) {
252
+ dispatch(setCurrentStep(FLIGHT_OPTIONS_FORM_STEP));
253
+ } else {
254
+ dispatch(setCurrentStep(ROOM_OPTIONS_FORM_STEP));
255
+ }
256
+ };
257
+
258
+ const previousUrl = settings.roomOptions.isHidden
259
+ ? `${settings.basePath}${settings.flightOptions.pathSuffix}?${bookingQueryString}`
260
+ : `${settings.basePath}${settings.roomOptions.pathSuffix}?${bookingQueryString}`;
261
+ const hasPrevious =
262
+ !settings.roomOptions.isHidden || !settings.flightOptions.isHidden;
263
+ const showPackageTagsOrRoomoptions =
264
+ showRoomOptions || (packageTags && !isEmpty(packageTags));
265
+
266
+ return (
267
+ <>
268
+ <form
269
+ className="form"
270
+ name="booking--options"
271
+ id="booking--options"
272
+ noValidate
273
+ onSubmit={handleSubmit}
274
+ >
275
+ {isLoading && settings.loaderComponent}
276
+ {!isLoading && (
277
+ <div className="form__region">
278
+ {showPackageTagsOrRoomoptions && (
279
+ <div className="form__group">
280
+ <div className="booking-card">
281
+ <div className="booking-card__body">
282
+ <div
283
+ className={buildClassName([
284
+ "booking-card__group",
285
+ "booking-card__group--package",
286
+ ])}
287
+ >
288
+ {showRoomOptions && (
289
+ <span className="booking-card__tag">
290
+ {translations.OPTIONS_FORM.PACKAGE}
291
+ </span>
292
+ )}
293
+ <div className="booking-card__group-body">
294
+ {showRoomOptions && (
295
+ <table className="table table--striped">
296
+ <tbody>
297
+ {packageRooms &&
298
+ packageRooms.map((room) => (
299
+ <OptionRoom
300
+ key={room.index}
301
+ packageRoom={room}
302
+ pax={getRoomPax(room.index)}
303
+ optionPax={optionPax}
304
+ onRoomChange={handleOnRoomChange}
305
+ />
306
+ ))}
307
+ </tbody>
308
+ </table>
309
+ )}
310
+ {packageTags && !isEmpty(packageTags) && (
311
+ <div className="booking-card__tag-translations">
312
+ {packageTags.map((tag, index) => (
313
+ <label
314
+ key={index}
315
+ htmlFor={`tag-translation-${index}-${tag.title}`}
316
+ className="checkbox__label tag-translation"
317
+ >
318
+ <div className="tag-translation-input__container">
319
+ <input
320
+ type="checkbox"
321
+ id={`tag-translation-${index}-${tag.title}`}
322
+ name={`tag-translation-${index}-${tag.title}`}
323
+ className="checkbox__input"
324
+ checked={tagIds?.includes(tag.id)}
325
+ onChange={(e) =>
326
+ handleOnTagChange(
327
+ tag.id,
328
+ e.target.checked
329
+ )
330
+ }
331
+ />
332
+ </div>
333
+ <span className="tag-translation__title">
334
+ {tag.title}
335
+ </span>
336
+ &nbsp;
337
+ <span className="tag-translation__description">
338
+ {tag.description}
339
+ </span>
340
+ </label>
341
+ ))}
342
+ </div>
343
+ )}
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ )}
350
+ {optionUnits && !isEmpty(optionUnits) && (
351
+ <div className="form__group">
352
+ <div className="booking-card">
353
+ <div className="booking-card__header">
354
+ <h2 className="booking-card__header-heading">
355
+ {translations.OPTIONS_FORM.PER_UNIT_TITLE}
356
+ </h2>
357
+ </div>
358
+ <div className="booking-card__body">
359
+ <OptionUnitsCard
360
+ units={optionUnits}
361
+ onUnitsChange={handleOnUnitsChange}
362
+ />
363
+ </div>
364
+ </div>
365
+ </div>
366
+ )}
367
+
368
+ {optionPax && !isEmpty(optionPax) && (
369
+ <div className="form__group">
370
+ <div className="booking-card">
371
+ <div className="booking-card__header">
372
+ <h2 className="booking-card__header-heading">
373
+ {translations.OPTIONS_FORM.PER_PAX_TITLE}
374
+ </h2>
375
+ </div>
376
+ <div className="booking-card__body">
377
+ <OptionPaxCard
378
+ pax={optionPax}
379
+ onPaxChange={handleOnPaxChange}
380
+ requestRoomsPax={requestRoomsPax}
381
+ />
382
+ </div>
383
+ </div>
384
+ </div>
385
+ )}
386
+
387
+ {groups && !isEmpty(groups) && (
388
+ <div className="form__group">
389
+ <div className="booking-card">
390
+ <div className="booking-card__header">
391
+ <h2 className="booking-card__header-heading">
392
+ {translations.OPTIONS_FORM.PER_BOOKING_TITLE}
393
+ </h2>
394
+ </div>
395
+ <div className="booking-card__body">
396
+ <div className="booking-card__group booking-card__group--active">
397
+ {groups.map((group, i) => (
398
+ <OptionBookingGroup
399
+ key={`${group.name}_${i}`}
400
+ group={group}
401
+ firstClassName={"booking-card__group-body"}
402
+ secondClassName={"booking-card__group-heading"}
403
+ parentId={`booking_${group.name}`}
404
+ onGroupChange={handleOnGroupChange}
405
+ />
406
+ ))}
407
+ {airlineGroups?.map((group, i) => (
408
+ <OptionBookingAirlineGroup
409
+ key={`${group.label}_${i}`}
410
+ airGroup={group}
411
+ onGroupChange={handleOnAirlineGroupChange}
412
+ />
413
+ ))}
414
+ {airportGroups?.map((group, i) => (
415
+ <OptionBookingAirlineGroup
416
+ key={`${group.label}_${i}`}
417
+ airGroup={group}
418
+ onGroupChange={handleOnAirportGroupChange}
419
+ />
420
+ ))}
421
+ </div>
422
+ </div>
423
+ </div>
424
+ </div>
425
+ )}
426
+
427
+ {isEmpty(groups) && isEmpty(optionUnits) && isEmpty(optionPax) && (
428
+ <NoOptions />
429
+ )}
430
+ </div>
431
+ )}
432
+ <div className="booking__navigator">
433
+ {hasPrevious && (
434
+ <>
435
+ {settings.skipRouter ? (
436
+ <button
437
+ type="button"
438
+ title={translations.STEPS.PREVIOUS}
439
+ onClick={() => goPrevious()}
440
+ className="cta cta--secondary"
441
+ >
442
+ {translations.STEPS.PREVIOUS}
443
+ </button>
444
+ ) : (
445
+ <Link
446
+ to={previousUrl}
447
+ title={translations.STEPS.PREVIOUS}
448
+ className="cta cta--secondary"
449
+ >
450
+ {translations.STEPS.PREVIOUS}
451
+ </Link>
452
+ )}
453
+ </>
454
+ )}
455
+ {token && settings.options.reportPrintActionId && (
456
+ <PrintOfferButton
457
+ bookingPackage={packageDetails}
458
+ getPax={getPax}
459
+ tagIds={tagIds}
460
+ printActionId={settings.options.reportPrintActionId}
461
+ labelIdle={translations.PRINT_OFFER_BUTTON.LABEL_IDLE}
462
+ labelCreating={translations.PRINT_OFFER_BUTTON.LABEL_CREATING}
463
+ labelPrinting={translations.PRINT_OFFER_BUTTON.LABEL_PRINTING}
464
+ className="cta spinner-button"
465
+ />
466
+ )}
467
+ <button
468
+ type="submit"
469
+ title={translations.STEPS.NEXT}
470
+ disabled={isLoading}
471
+ className={buildClassName(["cta", isLoading && "cta--disabled"])}
472
+ >
473
+ {translations.STEPS.NEXT}
474
+ </button>
475
+ </div>
476
+ </form>
477
+ </>
478
+ );
479
+ };
480
+
481
+ export default OptionsForm;