@qite/tide-booking-component 1.2.5 → 1.3.0

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