@qite/tide-booking-component 1.4.110 → 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 (53) hide show
  1. package/build/build-cjs/index.js +2316 -1555
  2. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  3. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  4. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -1
  5. package/build/build-cjs/src/search-results/types.d.ts +3 -0
  6. package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
  7. package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
  8. package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
  9. package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
  10. package/build/build-cjs/src/shared/utils/localization-util.d.ts +6 -0
  11. package/build/build-esm/index.js +2213 -1453
  12. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  13. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  14. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -1
  15. package/build/build-esm/src/search-results/types.d.ts +3 -0
  16. package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
  17. package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
  18. package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
  19. package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
  20. package/build/build-esm/src/shared/utils/localization-util.d.ts +6 -0
  21. package/package.json +2 -2
  22. package/src/booking-wizard/components/step-indicator.tsx +1 -1
  23. package/src/booking-wizard/components/step-route.tsx +1 -1
  24. package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
  25. package/src/booking-wizard/features/sidebar/index.tsx +1 -1
  26. package/src/booking-wizard/features/summary/summary.tsx +1 -1
  27. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
  28. package/src/search-results/components/book-packaging-entry/index.tsx +192 -11
  29. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +1 -4
  30. package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
  31. package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
  32. package/src/search-results/components/search-results-container/search-results-container.tsx +42 -14
  33. package/src/search-results/store/search-results-slice.ts +8 -2
  34. package/src/search-results/types.ts +4 -0
  35. package/src/shared/booking/shared-confirmation.tsx +105 -0
  36. package/src/shared/booking/summary.tsx +380 -0
  37. package/src/shared/booking/travelers-form.tsx +870 -0
  38. package/src/shared/components/flyin/flyin.tsx +8 -9
  39. package/src/shared/components/flyin/packaging-flights-flyin.tsx +4 -4
  40. package/src/shared/utils/booking-summary.tsx +46 -0
  41. package/src/shared/utils/tide-api-utils.ts +2 -2
  42. package/styles/components/_dropdown.scss +5 -0
  43. package/styles/components/_flyin.scss +43 -0
  44. package/styles/components/_search.scss +5 -0
  45. /package/build/build-cjs/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  46. /package/build/build-cjs/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  47. /package/build/build-cjs/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  48. /package/build/build-esm/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  49. /package/build/build-esm/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  50. /package/build/build-esm/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  51. /package/src/shared/booking/{BookingPanel.tsx → booking-panel.tsx} +0 -0
  52. /package/src/shared/booking/{Sidebar.tsx → shared-sidebar.tsx} +0 -0
  53. /package/src/shared/booking/{StepIndicators.tsx → step-indicators.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;