@qite/tide-booking-component 1.4.109 → 1.4.111

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 (106) hide show
  1. package/build/build-cjs/index.js +3613 -2276
  2. package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
  3. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  4. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  5. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  6. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  7. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
  8. package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  9. package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
  10. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +7 -1
  11. package/build/build-cjs/src/search-results/types.d.ts +3 -0
  12. package/build/build-cjs/src/shared/booking/booking-panel.d.ts +13 -0
  13. package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
  14. package/build/build-cjs/src/shared/booking/shared-sidebar.d.ts +34 -0
  15. package/build/build-cjs/src/shared/booking/step-indicators.d.ts +7 -0
  16. package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
  17. package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
  18. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
  19. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  20. package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
  21. package/build/build-cjs/src/shared/utils/localization-util.d.ts +7 -0
  22. package/build/build-esm/index.js +3572 -2247
  23. package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
  24. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
  25. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
  26. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
  27. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  28. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +8 -0
  29. package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
  30. package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
  31. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +7 -1
  32. package/build/build-esm/src/search-results/types.d.ts +3 -0
  33. package/build/build-esm/src/shared/booking/booking-panel.d.ts +13 -0
  34. package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
  35. package/build/build-esm/src/shared/booking/shared-sidebar.d.ts +34 -0
  36. package/build/build-esm/src/shared/booking/step-indicators.d.ts +7 -0
  37. package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
  38. package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
  39. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
  40. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
  41. package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
  42. package/build/build-esm/src/shared/utils/localization-util.d.ts +7 -0
  43. package/package.json +2 -2
  44. package/src/booking-wizard/components/step-indicator.tsx +10 -31
  45. package/src/booking-wizard/components/step-route.tsx +39 -14
  46. package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
  47. package/src/booking-wizard/features/sidebar/index.tsx +10 -4
  48. package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
  49. package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
  50. package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
  51. package/src/booking-wizard/features/summary/summary.tsx +1 -1
  52. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
  53. package/src/search-results/components/book-packaging-entry/index.tsx +229 -0
  54. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +162 -0
  55. package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
  56. package/src/search-results/components/excursions/excursion-results.tsx +1 -1
  57. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
  58. package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
  59. package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
  60. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
  61. package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
  62. package/src/search-results/components/itinerary/index.tsx +13 -12
  63. package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
  64. package/src/search-results/components/search-results-container/search-results-container.tsx +280 -217
  65. package/src/search-results/components/spinner/spinner.tsx +12 -4
  66. package/src/search-results/store/search-results-slice.ts +22 -2
  67. package/src/search-results/types.ts +4 -0
  68. package/src/shared/booking/booking-panel.tsx +25 -0
  69. package/src/shared/booking/shared-confirmation.tsx +105 -0
  70. package/src/shared/booking/shared-sidebar.tsx +432 -0
  71. package/src/shared/booking/step-indicators.tsx +30 -0
  72. package/src/shared/booking/summary.tsx +380 -0
  73. package/src/shared/booking/travelers-form.tsx +870 -0
  74. package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
  75. package/src/shared/components/flyin/flights-flyin.tsx +1 -1
  76. package/src/shared/components/flyin/flyin.tsx +16 -9
  77. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
  78. package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
  79. package/src/shared/components/icon.tsx +13 -0
  80. package/src/shared/translations/ar-SA.json +7 -1
  81. package/src/shared/translations/da-DK.json +7 -1
  82. package/src/shared/translations/de-DE.json +7 -1
  83. package/src/shared/translations/en-GB.json +8 -2
  84. package/src/shared/translations/es-ES.json +7 -1
  85. package/src/shared/translations/fr-BE.json +7 -1
  86. package/src/shared/translations/fr-FR.json +7 -1
  87. package/src/shared/translations/is-IS.json +7 -1
  88. package/src/shared/translations/it-IT.json +7 -1
  89. package/src/shared/translations/ja-JP.json +7 -1
  90. package/src/shared/translations/nl-BE.json +7 -1
  91. package/src/shared/translations/nl-NL.json +7 -1
  92. package/src/shared/translations/no-NO.json +7 -1
  93. package/src/shared/translations/pl-PL.json +7 -1
  94. package/src/shared/translations/pt-PT.json +7 -1
  95. package/src/shared/translations/sv-SE.json +7 -1
  96. package/src/shared/utils/booking-summary.tsx +46 -0
  97. package/src/shared/utils/localization-util.ts +8 -0
  98. package/src/shared/utils/tide-api-utils.ts +2 -2
  99. package/styles/components/_dropdown.scss +5 -0
  100. package/styles/components/_flyin.scss +43 -0
  101. package/styles/components/_loader.scss +82 -0
  102. package/styles/components/_search.scss +14 -2
  103. package/styles/content-blocks-variables.scss +14 -14
  104. /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  105. /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
  106. /package/src/{booking-wizard/components → shared/booking}/product-card.tsx +0 -0
@@ -0,0 +1,380 @@
1
+ import { compact, findIndex, isEmpty, isNil, uniqBy } from 'lodash';
2
+ import React, { ReactNode, useEffect, useState } from 'react';
3
+ import { SummaryCheckbox, TravelersFormValues } from '../../booking-wizard/types';
4
+ import Loader from '../components/loader';
5
+ import { buildClassName } from '../utils/class-util';
6
+ import Icon from '../components/icon';
7
+
8
+ export interface SummaryNotification {
9
+ id: number;
10
+ title?: string | null;
11
+ description?: string | null;
12
+ hasToBeConfirmed?: boolean;
13
+ isConfirmed?: boolean;
14
+ }
15
+
16
+ export interface SummaryVoucherState {
17
+ code?: string;
18
+ isValidated?: boolean;
19
+ isValid?: boolean;
20
+ }
21
+
22
+ export interface SharedSummaryProps {
23
+ translations: any;
24
+ travelerFormValues: TravelersFormValues;
25
+ checkboxes?: SummaryCheckbox[] | null;
26
+ notifications?: SummaryNotification[] | null;
27
+ remarks?: string;
28
+ voucher?: SummaryVoucherState;
29
+ voucherCodes?: string[];
30
+ enableVoucher?: boolean;
31
+ allowOption?: boolean;
32
+ isOffer?: boolean;
33
+ customValidateText?: string;
34
+ isSubmitting?: boolean;
35
+ userValidated?: boolean;
36
+ renderOptions: () => ReactNode;
37
+ renderPreviousButton: () => ReactNode;
38
+ renderLoader?: () => ReactNode;
39
+ onSubmit: React.FormEventHandler<HTMLFormElement>;
40
+ onRemarksChange?: (text: string) => void;
41
+ onCheckboxesChange?: (checkboxes: SummaryCheckbox[]) => void;
42
+ onNotificationsChange?: (notifications: SummaryNotification[]) => void;
43
+ onVoucherChange?: (voucher: SummaryVoucherState) => void;
44
+ onValidateVoucher?: () => void;
45
+ onAddVoucher?: React.MouseEventHandler<HTMLButtonElement>;
46
+ onRemoveVoucher?: (code: string) => void;
47
+ onUserValidatedChange?: (isValid: boolean) => void;
48
+ }
49
+
50
+ const formatBirthDate = (birthDate?: string | null) => {
51
+ if (!birthDate) return '';
52
+
53
+ return birthDate.split('T')[0].split('-').reverse().join('/');
54
+ };
55
+
56
+ const SharedSummary: React.FC<SharedSummaryProps> = ({
57
+ translations,
58
+ travelerFormValues,
59
+ checkboxes,
60
+ notifications,
61
+ remarks = '',
62
+ voucher = {},
63
+ voucherCodes = [],
64
+ enableVoucher = false,
65
+ allowOption = false,
66
+ isOffer = false,
67
+ customValidateText,
68
+ isSubmitting = false,
69
+ userValidated = true,
70
+ renderOptions,
71
+ renderPreviousButton,
72
+ renderLoader,
73
+ onSubmit,
74
+ onRemarksChange,
75
+ onCheckboxesChange,
76
+ onNotificationsChange,
77
+ onVoucherChange,
78
+ onValidateVoucher,
79
+ onAddVoucher,
80
+ onRemoveVoucher,
81
+ onUserValidatedChange
82
+ }) => {
83
+ const [localCheckboxes, setLocalCheckboxes] = useState<SummaryCheckbox[] | undefined | null>(checkboxes);
84
+
85
+ useEffect(() => {
86
+ setLocalCheckboxes(checkboxes);
87
+ }, [checkboxes]);
88
+
89
+ useEffect(() => {
90
+ const checkboxesValidated = !isNil(localCheckboxes) ? localCheckboxes.every((checkbox) => checkbox.isSelected) : true;
91
+ const notificationsValidated = !isNil(notifications)
92
+ ? notifications.filter((notification) => notification.hasToBeConfirmed).every((notification) => notification.isConfirmed)
93
+ : true;
94
+
95
+ onUserValidatedChange?.(checkboxesValidated && notificationsValidated);
96
+ }, [localCheckboxes, notifications, onUserValidatedChange]);
97
+
98
+ const handleNotificationChange = (id: number, checked: boolean) => {
99
+ const updatedNotifications = (notifications ?? []).map((notification) =>
100
+ notification.id === id ? { ...notification, isConfirmed: checked } : notification
101
+ );
102
+
103
+ onNotificationsChange?.(updatedNotifications);
104
+ };
105
+
106
+ const handleCheckboxChange = (id: string) => {
107
+ if (isNil(localCheckboxes)) return;
108
+
109
+ const newCheckboxes = [...localCheckboxes];
110
+ const index = findIndex(localCheckboxes, (checkbox) => checkbox.id === id);
111
+
112
+ if (index < 0) return;
113
+
114
+ newCheckboxes[index] = {
115
+ ...newCheckboxes[index],
116
+ isSelected: !newCheckboxes[index].isSelected
117
+ };
118
+
119
+ setLocalCheckboxes(newCheckboxes);
120
+ onCheckboxesChange?.(newCheckboxes);
121
+ };
122
+
123
+ return (
124
+ <>
125
+ {isSubmitting && (renderLoader?.() || <Loader />)}
126
+ {!isSubmitting && (
127
+ <form className="form" name="booking--summary" id="booking--summary" onSubmit={onSubmit}>
128
+ <div className="form__booking--summary">
129
+ <div className="form__region">
130
+ <div className="form__row">
131
+ <div className="form__group">
132
+ <div className="form__region-header">
133
+ <h5 className="form__region-heading">{translations.SUMMARY.PERSONAL_DETAILS}</h5>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ {travelerFormValues.rooms.map((room, roomIndex) => (
139
+ <div className="form__row" key={roomIndex}>
140
+ <div className="form__group">
141
+ <div className="form__region-header">
142
+ <h5 className="form__region-heading">
143
+ {travelerFormValues.rooms.length > 1 ? `${translations.SHARED.ROOM} ${roomIndex + 1}` : translations.ROOM_OPTIONS_FORM.TRAVELER_GROUP}
144
+ </h5>
145
+ <p className="form__region-label">
146
+ {`${room.adults.length + room.children.length} ${
147
+ room.adults.length + room.children.length === 1 ? translations.SUMMARY.TRAVELER : translations.SUMMARY.TRAVELERS
148
+ }: ${compact([
149
+ room.adults.length,
150
+ room.adults.length === 1 && ` ${translations.SUMMARY.ADULT}`,
151
+ room.adults.length > 1 && ` ${translations.SUMMARY.ADULTS}`,
152
+ room.adults?.length && room.children?.length && ', ',
153
+ room.children.length,
154
+ room.children.length === 1 && ` ${translations.SUMMARY.CHILD}`,
155
+ room.children.length > 1 && ` ${translations.SUMMARY.CHILDREN}`
156
+ ]).join('')}`}
157
+ </p>
158
+ </div>
159
+ </div>
160
+
161
+ {[...room.adults, ...room.children].map((traveler) => {
162
+ const isMainBooker = traveler.id === travelerFormValues.mainBookerId;
163
+
164
+ return (
165
+ <div className="form__group form__group--sm-50" key={traveler.id}>
166
+ <ul className="list list--plain">
167
+ <li className="list__item">
168
+ <strong>
169
+ {traveler.firstName} {traveler.lastName}
170
+ </strong>{' '}
171
+ {isMainBooker && <em>({translations.SUMMARY.MAIN_BOOKER})</em>}
172
+ </li>
173
+ <li className="list__item">{formatBirthDate(traveler.birthDate)}</li>
174
+ {isMainBooker && (
175
+ <>
176
+ {travelerFormValues.street && (
177
+ <li className="list__item">{`${travelerFormValues.street} ${compact([
178
+ travelerFormValues.houseNumber,
179
+ travelerFormValues.box
180
+ ]).join(' ')}, ${travelerFormValues.zipCode} ${travelerFormValues.place}`}</li>
181
+ )}
182
+ {travelerFormValues.phone && <li className="list__item">{travelerFormValues.phone}</li>}
183
+ {travelerFormValues.email && <li className="list__item">{travelerFormValues.email}</li>}
184
+ </>
185
+ )}
186
+ </ul>
187
+ </div>
188
+ );
189
+ })}
190
+ </div>
191
+ ))}
192
+ </div>
193
+
194
+ <div className="form__region">
195
+ <div className="form__row">
196
+ <div className="form__group">
197
+ <div className="form__region-header">
198
+ <h5 className="form__region-heading">{translations.SUMMARY.OPTIONS}</h5>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ <div className="form__row">
203
+ <div className="form__group">
204
+ <ul className="list list--booking-summary">{renderOptions()}</ul>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ {enableVoucher && (
210
+ <div className="form__region">
211
+ <div className="form__row">
212
+ <div className="form__group">
213
+ <div className="form__region-header">
214
+ <h5 className="form__region-heading">{translations.SUMMARY.VOUCHERS}</h5>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ <div className="form__row">
219
+ <div className="form__group">
220
+ <input
221
+ type="text"
222
+ className="form__input info-message__voucher__input"
223
+ value={voucher.code ?? ''}
224
+ onChange={(event) => onVoucherChange?.({ code: event.target.value })}
225
+ />
226
+ <button type="button" className={buildClassName(['cta', !voucher.code && 'cta--disabled'])} onClick={onValidateVoucher}>
227
+ {translations.SUMMARY.VOUCHER_VALIDATE}
228
+ </button>
229
+ </div>
230
+ </div>
231
+ <div className="form__row">
232
+ <div className="form__group info-message__voucher">
233
+ {voucher.isValid && voucher.isValidated && (
234
+ <div className="info-message info-message__voucher__valid">
235
+ <span>{translations.SUMMARY.VOUCHER_VALID}</span>
236
+ <button type="button" className="cta cta--secondary" onClick={onAddVoucher}>
237
+ {translations.SUMMARY.ADD_VOUCHER}
238
+ </button>
239
+ </div>
240
+ )}
241
+ {!voucher.isValid && voucher.isValidated && <div className="info-message--error">{translations.SUMMARY.VOUCHER_INVALID}</div>}
242
+ </div>
243
+ </div>
244
+ <div className="form__row">
245
+ <ul className="info-message__voucher">
246
+ {!isEmpty(voucherCodes) &&
247
+ voucherCodes.map((code, index) => (
248
+ <li key={`${code}-${index}`}>
249
+ <div className="info-message__voucher__list">
250
+ {code}{' '}
251
+ <button type="button" className="cta--add-remove" onClick={() => onRemoveVoucher?.(code)}>
252
+ <Icon height={16} name="ui-trashcan" />
253
+ </button>
254
+ </div>
255
+ </li>
256
+ ))}
257
+ </ul>
258
+ </div>
259
+ </div>
260
+ )}
261
+
262
+ {!isEmpty(notifications) && (
263
+ <div className="form__region">
264
+ <div className="form__row">
265
+ <div className="form__group">
266
+ <div className="info-message">
267
+ <Icon name="ui-tooltip" className="icon--secondary-color" />
268
+ <div className="info-message__copy">
269
+ <h5>{translations.SUMMARY.NOTIFICATIONS_TITLE}</h5>
270
+ {uniqBy(
271
+ (notifications ?? []).filter((notification) => !notification.hasToBeConfirmed),
272
+ 'id'
273
+ ).map((notification) => (
274
+ <span key={notification.id} className="checkbox__label-text">
275
+ <strong className="checkbox__label-text--title">{notification.title}</strong>
276
+ <span className="checkbox__label-text--description">{notification.description}</span>
277
+ </span>
278
+ ))}
279
+ {uniqBy(
280
+ (notifications ?? []).filter((notification) => notification.hasToBeConfirmed),
281
+ 'id'
282
+ ).map((notification) => (
283
+ <div className="checkbox" key={notification.id}>
284
+ <label className="checkbox__label">
285
+ <input
286
+ type="checkbox"
287
+ className="checkbox__input"
288
+ checked={notification.isConfirmed}
289
+ onChange={(event) => handleNotificationChange(notification.id, event.target.checked)}
290
+ />
291
+ <span className="checkbox__label-text">
292
+ <strong className="checkbox__label-text--title">{notification.title}</strong>
293
+ <span className="checkbox__label-text--description">{notification.description}</span>
294
+ </span>
295
+ </label>
296
+ </div>
297
+ ))}
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ )}
304
+
305
+ <div className="form__region">
306
+ <div className="form__row">
307
+ <div className="form__group">
308
+ <div className="form__region-header">
309
+ <h5 className="form__region-heading">{translations.SUMMARY.REMARKS}</h5>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ <div className="form__row">
314
+ <div className="form__group">
315
+ <textarea className="form__input" value={remarks} onChange={(event) => onRemarksChange?.(event.target.value)} />
316
+ </div>
317
+ </div>
318
+ </div>
319
+
320
+ <div className="form__region">
321
+ <div className="form__row">
322
+ <div className="form__group">
323
+ <div className="info-message">
324
+ <Icon name="ui-tooltip" className="icon--secondary-color" />
325
+ <div className="info-message__copy">
326
+ <h5>{translations.SUMMARY.VALIDATE_TITLE}</h5>
327
+ {customValidateText ? (
328
+ <div dangerouslySetInnerHTML={{ __html: customValidateText }} />
329
+ ) : (
330
+ <>
331
+ <p>{isOffer ? translations.SUMMARY.VALIDATE_TEXT_OFFER : translations.SUMMARY.VALIDATE_TEXT_BOOKING}</p>
332
+ {allowOption && <p>{translations.SUMMARY.VALIDATE_TEXT_OPTION}</p>}
333
+ </>
334
+ )}
335
+ {localCheckboxes?.map((checkbox) => (
336
+ <div className="checkbox" key={checkbox.id}>
337
+ <label className="checkbox__label">
338
+ <input
339
+ type="checkbox"
340
+ className="checkbox__input"
341
+ checked={checkbox.isSelected}
342
+ onChange={() => handleCheckboxChange(checkbox.id)}
343
+ />
344
+ <span className="checkbox__label-text" dangerouslySetInnerHTML={{ __html: checkbox.text }} />
345
+ </label>
346
+ </div>
347
+ ))}
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </div>
353
+ </div>
354
+
355
+ <div className="booking__navigator">
356
+ {renderPreviousButton()}
357
+ {allowOption && (
358
+ <button
359
+ title={translations.STEPS.SUBMIT_OPTION}
360
+ className={buildClassName(['cta', !userValidated && 'cta--disabled'])}
361
+ disabled={!userValidated}
362
+ name="option">
363
+ {translations.STEPS.SUBMIT_OPTION}
364
+ </button>
365
+ )}
366
+ <button
367
+ title={isOffer ? translations.STEPS.SUBMIT_OFFER : translations.STEPS.SUBMIT_BOOKING}
368
+ className={buildClassName(['cta', !userValidated && 'cta--disabled'])}
369
+ disabled={!userValidated}
370
+ name="default">
371
+ {isOffer ? translations.STEPS.SUBMIT_OFFER : translations.STEPS.SUBMIT_BOOKING}
372
+ </button>
373
+ </div>
374
+ </form>
375
+ )}
376
+ </>
377
+ );
378
+ };
379
+
380
+ export default SharedSummary;