@qite/tide-booking-component 1.3.4 → 1.3.5

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 (33) hide show
  1. package/build/build-cjs/booking-wizard/features/sidebar/sidebar-util.d.ts +3 -3
  2. package/build/build-cjs/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +5 -5
  3. package/build/build-cjs/booking-wizard/types.d.ts +6 -0
  4. package/build/build-cjs/index.js +223 -147
  5. package/build/build-cjs/shared/utils/localization-util.d.ts +2 -0
  6. package/build/build-esm/booking-wizard/features/sidebar/sidebar-util.d.ts +3 -3
  7. package/build/build-esm/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +5 -5
  8. package/build/build-esm/booking-wizard/types.d.ts +6 -0
  9. package/build/build-esm/index.js +223 -147
  10. package/build/build-esm/shared/utils/localization-util.d.ts +2 -0
  11. package/package.json +75 -75
  12. package/src/booking-wizard/components/print-offer-button.tsx +63 -63
  13. package/src/booking-wizard/features/booking/booking-self-contained.tsx +389 -389
  14. package/src/booking-wizard/features/booking/booking-slice.ts +663 -663
  15. package/src/booking-wizard/features/booking/booking.tsx +361 -361
  16. package/src/booking-wizard/features/flight-options/flight-utils.ts +522 -522
  17. package/src/booking-wizard/features/product-options/options-form.tsx +481 -481
  18. package/src/booking-wizard/features/sidebar/sidebar-util.ts +177 -177
  19. package/src/booking-wizard/features/summary/summary-booking-option-pax.tsx +25 -25
  20. package/src/booking-wizard/features/summary/summary.tsx +674 -674
  21. package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +164 -164
  22. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +888 -754
  23. package/src/booking-wizard/settings-context.ts +62 -62
  24. package/src/booking-wizard/types.ts +286 -279
  25. package/src/booking-wizard/use-offer-printer.ts +117 -117
  26. package/src/shared/translations/en-GB.json +239 -237
  27. package/src/shared/translations/fr-BE.json +239 -238
  28. package/src/shared/translations/nl-BE.json +239 -237
  29. package/src/shared/utils/tide-api-utils.ts +36 -36
  30. package/styles/booking-wizard-variables.scss +873 -873
  31. package/styles/components/_booking.scss +879 -879
  32. package/styles/components/_dropdown.scss +72 -72
  33. package/styles/components/_form.scss +1583 -1583
@@ -1,754 +1,888 @@
1
- import { compact, get, sortBy } from "lodash";
2
- import React, { useContext, useEffect, useState } from "react";
3
- import { useSelector } from "react-redux";
4
- import { setCurrentStep } from "../booking/booking-slice";
5
- import {
6
- selectFormRooms,
7
- selectTravelersFormValues,
8
- setFormValues,
9
- } from "./travelers-form-slice";
10
-
11
- import { Link, navigate } from "@reach/router";
12
- import { format, parse } from "date-fns";
13
- import flat from "flat";
14
- import { useFormik } from "formik";
15
- import produce from "immer";
16
- import { buildClassName } from "../../../shared/utils/class-util";
17
- import LabeledInput from "../../components/labeled-input";
18
- import LabeledSelect from "../../components/labeled-select";
19
- import SettingsContext from "../../settings-context";
20
- import { useAppDispatch } from "../../store";
21
- import { TravelersFormValues } from "../../types";
22
- import { setBookingType } from "../booking/booking-slice";
23
- import { OPTIONS_FORM_STEP, SUMMARY_STEP } from "../booking/constants";
24
- import {
25
- selectAgentAdressId,
26
- selectAgents,
27
- selectBookingQueryString,
28
- selectBookingType,
29
- selectStartDate,
30
- selectTranslations,
31
- } from "../booking/selectors";
32
- import { fetchPriceDetails } from "../price-details/price-details-slice";
33
- import TypeAheadInput from "./type-ahead-input";
34
- import validateForm from "./validate-form";
35
-
36
- interface TravelersFormProps {}
37
-
38
- function createTraveler(id: number) {
39
- return { id, firstName: "", lastName: "", birthDate: "", gender: "" };
40
- }
41
-
42
- function createInitialValues(
43
- formRooms: { adults: number[]; children: number[] }[],
44
- startDate?: string,
45
- agentAdressId?: number
46
- ) {
47
- const initialValues = {
48
- startDate: startDate,
49
- rooms: formRooms.map((r) => ({
50
- adults: r.adults.map((id) => createTraveler(id)),
51
- children: r.children.map((id) => createTraveler(id)),
52
- })),
53
- mainBookerId: -1,
54
- street: "",
55
- houseNumber: "",
56
- box: "",
57
- zipCode: "",
58
- place: "",
59
- country: "",
60
- phone: "",
61
- email: "",
62
- emailConfirmation: "",
63
- travelAgentId: agentAdressId ?? 0,
64
- travelAgentName: "",
65
- };
66
-
67
- if (
68
- initialValues.rooms &&
69
- initialValues.rooms.length &&
70
- initialValues.rooms[0].adults &&
71
- initialValues.rooms[0].adults.length
72
- ) {
73
- initialValues.mainBookerId = initialValues.rooms[0].adults[0].id;
74
- }
75
-
76
- return initialValues;
77
- }
78
-
79
- const TravelersForm: React.FC<TravelersFormProps> = () => {
80
- const dispatch = useAppDispatch();
81
-
82
- const settings = useContext(SettingsContext);
83
- const bookingQueryString = useSelector(selectBookingQueryString);
84
- const startDate = useSelector(selectStartDate);
85
- const formRooms = useSelector(selectFormRooms);
86
- const bookingType = useSelector(selectBookingType);
87
- const agents = useSelector(selectAgents);
88
- const agentAdressId = useSelector(selectAgentAdressId);
89
- const translations = useSelector(selectTranslations);
90
-
91
- const initialValues =
92
- useSelector(selectTravelersFormValues) ??
93
- createInitialValues(formRooms, startDate, agentAdressId);
94
-
95
- const [showAgents, setShowAgents] = useState<boolean>(
96
- settings.agentRequired ?? false
97
- );
98
- const [showAgentSelection, setShowAgentSelection] = useState<boolean>(
99
- !settings.agentAdressId && !settings.hideAgentSelection
100
- );
101
-
102
- const typeaheadAgents =
103
- sortBy(
104
- agents?.map((x) => ({
105
- key: `${x.id}`,
106
- value: `${x.name} (${x.postalCode} ${x.location})`,
107
- text: `${x.name} (${x.postalCode} ${x.location})`,
108
- })),
109
- "value"
110
- ) ?? [];
111
-
112
- const [filteredAgents, setFilteredAgents] =
113
- useState<{ key: string; value: string; text: string }[]>(typeaheadAgents);
114
-
115
- const formik = useFormik<TravelersFormValues>({
116
- initialValues,
117
- validate: (values) =>
118
- validateForm(values, settings.agentRequired, bookingType, translations),
119
- onSubmit: (values) => {
120
- dispatch(setFormValues(values));
121
- dispatch(fetchPriceDetails());
122
-
123
- if (settings.skipRouter) {
124
- dispatch(setCurrentStep(SUMMARY_STEP));
125
- } else {
126
- navigate(
127
- `${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`
128
- );
129
- }
130
- },
131
- });
132
-
133
- useEffect(() => {
134
- dispatch(fetchPriceDetails());
135
- }, []);
136
-
137
- useEffect(() => {
138
- if (agents && settings.affiliateSlug) {
139
- const agent = agents.find(
140
- (x) => x.affiliateSlug && x.affiliateSlug === settings.affiliateSlug
141
- );
142
- if (!agent) return;
143
-
144
- const formValues = produce(formik.values, (values) => {
145
- (values.travelAgentId = Number(agent.id)),
146
- (values.travelAgentName = agent.name);
147
- });
148
- formik.setValues(formValues, false);
149
- dispatch(setFormValues(formValues));
150
- setShowAgentSelection(false);
151
- }
152
- }, [agents, settings.affiliateSlug]);
153
-
154
- const handleMainBookerChange: React.FormEventHandler<HTMLInputElement> = (
155
- e
156
- ) => {
157
- const id = parseInt(e.currentTarget.value);
158
-
159
- formik.setFieldValue("mainBookerId", id);
160
- };
161
-
162
- const mainBooker = formik.values.rooms
163
- .find((r) =>
164
- r.adults.find((traveler) => traveler.id === formik.values.mainBookerId)
165
- )
166
- ?.adults.find((traveler) => traveler.id === formik.values.mainBookerId);
167
-
168
- const handleAgentChange = (value: string) => {
169
- const filteredAgents = typeaheadAgents.filter(
170
- (x) => x.value.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
171
- );
172
-
173
- setFilteredAgents(filteredAgents);
174
- formik.setFieldValue("travelAgentName", value);
175
- };
176
-
177
- const handleAgentSelect = (key: string) => {
178
- const agent = typeaheadAgents.find((x) => x.key === key);
179
-
180
- formik.setValues({
181
- ...formik.values,
182
- travelAgentId: Number(agent?.key),
183
- travelAgentName: agent?.value ?? "",
184
- });
185
-
186
- let bookingType = "b2b2c";
187
- if (agentAdressId && agentAdressId != 0) {
188
- bookingType = "b2b";
189
- }
190
- dispatch(setBookingType(bookingType));
191
- };
192
-
193
- const handleAgentClear = () => {
194
- formik.setValues({
195
- ...formik.values,
196
- travelAgentId: 0,
197
- travelAgentName: "",
198
- });
199
-
200
- dispatch(setBookingType("b2c"));
201
- };
202
-
203
- const toggleAgent = (value: boolean) => {
204
- setShowAgents(value);
205
-
206
- if (!value) {
207
- handleAgentClear();
208
- setFilteredAgents([]);
209
- }
210
- };
211
-
212
- const goPrevious = () => {
213
- dispatch(setCurrentStep(OPTIONS_FORM_STEP));
214
- };
215
-
216
- const flatErrors: Record<string, string> = flat(formik.errors);
217
- const hasVisibleError = (key: string) =>
218
- get(formik.errors, key) && get(formik.touched, key);
219
-
220
- return (
221
- <form
222
- className="form form__travelers"
223
- name="booking--travellers"
224
- id="booking--travellers"
225
- noValidate
226
- onSubmit={formik.handleSubmit}
227
- onReset={formik.handleReset}
228
- >
229
- <div className="form__travelers__wrapper">
230
- {formik.values.rooms.map((room, rIndex) => (
231
- <div key={rIndex}>
232
- <div className="form__region">
233
- <div className="form__region-header">
234
- <h5 className="form__region-heading">
235
- {translations.SHARED.ROOM} {rIndex + 1}
236
- </h5>
237
- <p className="form__region-label">
238
- {compact([
239
- room.adults.length,
240
- room.adults.length === 1 &&
241
- ` ${translations.TRAVELERS_FORM.ADULT}`,
242
- room.adults.length > 1 &&
243
- ` ${translations.TRAVELERS_FORM.ADULTS}`,
244
- room.adults &&
245
- room.adults.length &&
246
- room.children &&
247
- room.children.length &&
248
- ", ",
249
- room.children.length,
250
- room.children.length === 1 &&
251
- ` ${translations.TRAVELERS_FORM.CHILD}`,
252
- room.children.length > 1 &&
253
- ` ${translations.TRAVELERS_FORM.CHILDREN}`,
254
- ]).join("")}
255
- </p>
256
- </div>
257
- </div>
258
- {room.adults.map((travelerValues, index) => (
259
- <div className="form__region" key={travelerValues.id}>
260
- <div className="form__region-header">
261
- <h5 className="form__region-heading">
262
- {translations.TRAVELERS_FORM.TRAVELER} {index + 1}
263
- </h5>
264
- <p className="form__region-label">
265
- {translations.TRAVELERS_FORM.ADULT}
266
- </p>
267
-
268
- <div className="radiobutton">
269
- <label className="radiobutton__label">
270
- <input
271
- type="radio"
272
- name="mainBookerId"
273
- onChange={handleMainBookerChange}
274
- onBlur={formik.handleBlur}
275
- value={travelerValues.id}
276
- checked={
277
- formik.values.mainBookerId === travelerValues.id
278
- }
279
- className="radiobutton__input"
280
- />
281
- {translations.TRAVELERS_FORM.MAIN_BOOKER}
282
- </label>
283
- </div>
284
- </div>
285
- <div className="form__row">
286
- <div
287
- className={buildClassName([
288
- "form__group",
289
- hasVisibleError(
290
- `rooms[${rIndex}].adults[${index}].gender`
291
- ) && "form__group--error",
292
- ])}
293
- >
294
- <label className="form__label">
295
- {translations.TRAVELERS_FORM.GENDER} *
296
- </label>
297
- <div className="radiobutton-group">
298
- <div className="radiobutton">
299
- <label className="radiobutton__label">
300
- <input
301
- type="radio"
302
- className="radiobutton__input"
303
- name={`rooms[${rIndex}].adults[${index}].gender`}
304
- onChange={formik.handleChange}
305
- onBlur={formik.handleBlur}
306
- value="m"
307
- checked={travelerValues.gender === "m"}
308
- />
309
- {translations.TRAVELERS_FORM.MALE}
310
- </label>
311
- </div>
312
-
313
- <div className="radiobutton">
314
- <label className="radiobutton__label">
315
- <input
316
- type="radio"
317
- className="radiobutton__input"
318
- name={`rooms[${rIndex}].adults[${index}].gender`}
319
- onChange={formik.handleChange}
320
- onBlur={formik.handleBlur}
321
- value="f"
322
- checked={travelerValues.gender === "f"}
323
- />
324
- {translations.TRAVELERS_FORM.FEMALE}
325
- </label>
326
- </div>
327
-
328
- <div className="radiobutton">
329
- <label className="radiobutton__label">
330
- <input
331
- type="radio"
332
- className="radiobutton__input"
333
- name={`rooms[${rIndex}].adults[${index}].gender`}
334
- onChange={formik.handleChange}
335
- onBlur={formik.handleBlur}
336
- value="x"
337
- checked={travelerValues.gender === "x"}
338
- />
339
- {translations.TRAVELERS_FORM.OTHER}
340
- </label>
341
- </div>
342
- </div>
343
- </div>
344
- </div>
345
- <div className="form__row">
346
- <LabeledInput
347
- hasError={hasVisibleError(
348
- `rooms[${rIndex}].adults[${index}].firstName`
349
- )}
350
- extraClassName="form__group--md-33"
351
- label={translations.TRAVELERS_FORM.FIRST_NAME}
352
- required
353
- name={`rooms[${rIndex}].adults[${index}].firstName`}
354
- onChange={formik.handleChange}
355
- onBlur={formik.handleBlur}
356
- value={travelerValues.firstName}
357
- />
358
- <LabeledInput
359
- hasError={hasVisibleError(
360
- `rooms[${rIndex}].adults[${index}].lastName`
361
- )}
362
- extraClassName="form__group--md-33"
363
- label={translations.TRAVELERS_FORM.LAST_NAME}
364
- required
365
- name={`rooms[${rIndex}].adults[${index}].lastName`}
366
- onChange={formik.handleChange}
367
- onBlur={formik.handleBlur}
368
- value={travelerValues.lastName}
369
- />
370
- <LabeledInput
371
- type="date"
372
- hasError={hasVisibleError(
373
- `rooms[${rIndex}].adults[${index}].birthDate`
374
- )}
375
- extraClassName="form__group--md-33"
376
- label={translations.TRAVELERS_FORM.BIRTHDATE}
377
- required
378
- name={`rooms[${rIndex}].adults[${index}].birthDate`}
379
- onChange={formik.handleChange}
380
- onBlur={formik.handleBlur}
381
- value={travelerValues.birthDate}
382
- />
383
- </div>
384
- </div>
385
- ))}
386
- {room.children.map((travelerValues, index) => (
387
- <div className="form__region" key={travelerValues.id}>
388
- <div className="form__region-header">
389
- <h5 className="form__region-heading">
390
- {translations.TRAVELERS_FORM.TRAVELER}{" "}
391
- {room.adults.length + index + 1}
392
- </h5>
393
- <p className="form__region-label">
394
- {translations.TRAVELERS_FORM.CHILD}
395
- </p>
396
- </div>
397
- <div className="form__row">
398
- <div
399
- className={buildClassName([
400
- "form__group",
401
- hasVisibleError(
402
- `rooms[${rIndex}].children[${index}].gender`
403
- ) && "form__group--error",
404
- ])}
405
- >
406
- <label className="form__label">
407
- {translations.TRAVELERS_FORM.GENDER} *
408
- </label>
409
- <div className="radiobutton-group">
410
- <div className="radiobutton">
411
- <label className="radiobutton__label">
412
- <input
413
- type="radio"
414
- className="radiobutton__input"
415
- name={`rooms[${rIndex}].children[${index}].gender`}
416
- onChange={formik.handleChange}
417
- onBlur={formik.handleBlur}
418
- value="m"
419
- checked={travelerValues.gender === "m"}
420
- />
421
- {translations.TRAVELERS_FORM.MALE}
422
- </label>
423
- </div>
424
-
425
- <div className="radiobutton">
426
- <label className="radiobutton__label">
427
- <input
428
- type="radio"
429
- className="radiobutton__input"
430
- name={`rooms[${rIndex}].children[${index}].gender`}
431
- onChange={formik.handleChange}
432
- onBlur={formik.handleBlur}
433
- value="f"
434
- checked={travelerValues.gender === "f"}
435
- />
436
- {translations.TRAVELERS_FORM.FEMALE}
437
- </label>
438
- </div>
439
-
440
- <div className="radiobutton">
441
- <label className="radiobutton__label">
442
- <input
443
- type="radio"
444
- className="radiobutton__input"
445
- name={`rooms[${rIndex}].children[${index}].gender`}
446
- onChange={formik.handleChange}
447
- onBlur={formik.handleBlur}
448
- value="x"
449
- checked={travelerValues.gender === "x"}
450
- />
451
- {translations.TRAVELERS_FORM.OTHER}
452
- </label>
453
- </div>
454
- </div>
455
- </div>
456
- </div>
457
- <div className="form__row">
458
- <LabeledInput
459
- hasError={hasVisibleError(
460
- `rooms[${rIndex}].children[${index}].firstName`
461
- )}
462
- extraClassName="form__group--md-33"
463
- label={translations.TRAVELERS_FORM.FIRST_NAME}
464
- required
465
- name={`rooms[${rIndex}].children[${index}].firstName`}
466
- onChange={formik.handleChange}
467
- onBlur={formik.handleBlur}
468
- value={travelerValues.firstName}
469
- />
470
- <LabeledInput
471
- hasError={hasVisibleError(
472
- `rooms[${rIndex}].children[${index}].lastName`
473
- )}
474
- extraClassName="form__group--md-33"
475
- label={translations.TRAVELERS_FORM.LAST_NAME}
476
- required
477
- name={`rooms[${rIndex}].children[${index}].lastName`}
478
- onChange={formik.handleChange}
479
- onBlur={formik.handleBlur}
480
- value={travelerValues.lastName}
481
- />
482
- <LabeledInput
483
- type="date"
484
- hasError={hasVisibleError(
485
- `rooms[${rIndex}].children[${index}].birthDate`
486
- )}
487
- extraClassName="form__group--md-33"
488
- label={translations.TRAVELERS_FORM.BIRTHDATE}
489
- required
490
- name={`rooms[${rIndex}].children[${index}].birthDate`}
491
- onChange={formik.handleChange}
492
- onBlur={formik.handleBlur}
493
- value={travelerValues.birthDate}
494
- />
495
- </div>
496
- </div>
497
- ))}
498
- </div>
499
- ))}
500
-
501
- {bookingType != "b2b" ? (
502
- <div className="form__region">
503
- <div className="form__region-header">
504
- <h5 className="form__region-heading">
505
- {translations.TRAVELERS_FORM.MAIN_BOOKER}
506
- </h5>
507
- <p className="form__region-label">
508
- {compact([
509
- compact([mainBooker?.firstName, mainBooker?.lastName]).join(
510
- " "
511
- ),
512
- mainBooker?.birthDate &&
513
- format(
514
- parse(mainBooker.birthDate, "yyyy-MM-dd", new Date()),
515
- "dd-MM-yyyy"
516
- ),
517
- ]).join(", ")}
518
- </p>
519
- </div>
520
- <>
521
- <div className="form__twocolumn">
522
- <div className="form__twocolumn-column">
523
- <div className="form__row">
524
- <LabeledInput
525
- hasError={hasVisibleError("street")}
526
- extraClassName="form__group--50 form__group--sm-60"
527
- label={translations.TRAVELERS_FORM.STREET}
528
- required
529
- name="street"
530
- onChange={formik.handleChange}
531
- onBlur={formik.handleBlur}
532
- value={formik.values.street}
533
- />
534
- <LabeledInput
535
- hasError={hasVisibleError("houseNumber")}
536
- extraClassName="form__group--30 form__group--sm-20"
537
- label={translations.TRAVELERS_FORM.HOUSE_NUMBER}
538
- required
539
- name="houseNumber"
540
- onChange={formik.handleChange}
541
- onBlur={formik.handleBlur}
542
- value={formik.values.houseNumber}
543
- />
544
- <LabeledInput
545
- hasError={hasVisibleError("box")}
546
- extraClassName="form__group--20"
547
- label={translations.TRAVELERS_FORM.POST_BOX}
548
- name="box"
549
- onChange={formik.handleChange}
550
- onBlur={formik.handleBlur}
551
- value={formik.values.box}
552
- />
553
- </div>
554
- </div>
555
- <div className="form__twocolumn-column">
556
- <div className="form__row">
557
- <LabeledInput
558
- hasError={hasVisibleError("zipCode")}
559
- extraClassName="form__group--40 form__group--sm-20"
560
- label={translations.TRAVELERS_FORM.ZIPCODE}
561
- required
562
- name="zipCode"
563
- onChange={formik.handleChange}
564
- onBlur={formik.handleBlur}
565
- value={formik.values.zipCode}
566
- />
567
- <LabeledInput
568
- hasError={hasVisibleError("place")}
569
- extraClassName="form__group--60 form__group--sm-40"
570
- label={translations.TRAVELERS_FORM.CITY}
571
- required
572
- name="place"
573
- onChange={formik.handleChange}
574
- onBlur={formik.handleBlur}
575
- value={formik.values.place}
576
- />
577
- <LabeledSelect
578
- hasError={hasVisibleError("country")}
579
- extraClassName="form__group--sm-40"
580
- label={translations.TRAVELERS_FORM.COUNTRY}
581
- required
582
- name="country"
583
- onChange={formik.handleChange}
584
- onBlur={formik.handleBlur}
585
- value={formik.values.country}
586
- options={[
587
- {
588
- key: "empty",
589
- label: translations.TRAVELERS_FORM.SELECT_COUNTRY,
590
- value: undefined,
591
- },
592
- {
593
- key: "be",
594
- value: "be",
595
- label: translations.TRAVELERS_FORM.COUNTRIES.BELGIUM,
596
- },
597
- {
598
- key: "nl",
599
- value: "nl",
600
- label:
601
- translations.TRAVELERS_FORM.COUNTRIES.NETHERLANDS,
602
- },
603
- {
604
- key: "fr",
605
- value: "fr",
606
- label: translations.TRAVELERS_FORM.COUNTRIES.FRANCE,
607
- },
608
- ]}
609
- />
610
- </div>
611
- </div>
612
- </div>
613
- <div className="form__row">
614
- <LabeledInput
615
- hasError={hasVisibleError("phone")}
616
- extraClassName="form__group--md-33"
617
- label={translations.TRAVELERS_FORM.PHONE}
618
- required
619
- name="phone"
620
- onChange={formik.handleChange}
621
- onBlur={formik.handleBlur}
622
- value={formik.values.phone}
623
- />
624
- <LabeledInput
625
- type="email"
626
- hasError={hasVisibleError("email")}
627
- extraClassName="form__group--md-33"
628
- label={translations.TRAVELERS_FORM.EMAIL}
629
- required
630
- name="email"
631
- onChange={formik.handleChange}
632
- onBlur={formik.handleBlur}
633
- value={formik.values.email}
634
- />
635
- <LabeledInput
636
- type="email"
637
- hasError={hasVisibleError("emailConfirmation")}
638
- extraClassName="form__group--md-33"
639
- label={translations.TRAVELERS_FORM.REPEAT_EMAIL}
640
- required
641
- name="emailConfirmation"
642
- onChange={formik.handleChange}
643
- onBlur={formik.handleBlur}
644
- value={formik.values.emailConfirmation}
645
- />
646
- </div>
647
- </>
648
- </div>
649
- ) : (
650
- <div className="form__region">
651
- <div className="form__row">
652
- <LabeledInput
653
- hasError={hasVisibleError("phone")}
654
- extraClassName="form__group--md-33"
655
- label={translations.TRAVELERS_FORM.PHONE}
656
- required
657
- name="phone"
658
- onChange={formik.handleChange}
659
- onBlur={formik.handleBlur}
660
- value={formik.values.phone}
661
- />
662
- </div>
663
- </div>
664
- )}
665
-
666
- {showAgentSelection && (
667
- <div className="form__region">
668
- <div className="form__region-header">
669
- <h5 className="form__region-heading">
670
- {translations.TRAVELERS_FORM.BOOK_WITH_AGENT}
671
- </h5>
672
- <div className="checkbox" id="cbxChooseOffice">
673
- <label className="checkbox__label">
674
- <input
675
- type="checkbox"
676
- name="booking--mainbooker"
677
- defaultChecked={showAgents}
678
- onClick={() => toggleAgent(!showAgents)}
679
- className="checkbox__input"
680
- />
681
- {translations.TRAVELERS_FORM.CHOOSE_OFFICE}
682
- </label>
683
- </div>
684
- </div>
685
- {showAgents && (
686
- <div className="form__row form__row--choose-office">
687
- <div
688
- className={buildClassName([
689
- "form__group",
690
- "form__group--icon",
691
- hasVisibleError("travelAgentId") && "form__group--error",
692
- ])}
693
- >
694
- <TypeAheadInput
695
- value={formik.values.travelAgentName}
696
- options={filteredAgents}
697
- onChange={handleAgentChange}
698
- onSelect={handleAgentSelect}
699
- onClear={handleAgentClear}
700
- name="travelAgentName"
701
- placeholder={
702
- translations.TRAVELERS_FORM.CHOOSE_AGENT_PLACEHOLDER
703
- }
704
- />
705
- </div>
706
- </div>
707
- )}
708
- </div>
709
- )}
710
- </div>
711
- {Object.keys(flatErrors).length > 0 && (
712
- <div className="form__region form__region--errors">
713
- <div className="form__row">
714
- <div className="form__group">
715
- <p className="form__error-heading">
716
- {translations.TRAVELERS_FORM.VALIDATION_MESSAGE}:
717
- </p>
718
- <ul className="list">
719
- {Object.keys(flatErrors).map((key) => (
720
- <li key={key}>{get(flatErrors, key)}</li>
721
- ))}
722
- </ul>
723
- </div>
724
- </div>
725
- </div>
726
- )}
727
- <div className="booking__navigator">
728
- {settings.skipRouter ? (
729
- <button
730
- type="button"
731
- title={translations.STEPS.PREVIOUS}
732
- onClick={() => goPrevious()}
733
- className="cta cta--secondary"
734
- >
735
- {translations.STEPS.PREVIOUS}
736
- </button>
737
- ) : (
738
- <Link
739
- to={`${settings.basePath}${settings.options.pathSuffix}?${bookingQueryString}`}
740
- title={translations.STEPS.PREVIOUS}
741
- className="cta cta--secondary"
742
- >
743
- {translations.STEPS.PREVIOUS}
744
- </Link>
745
- )}
746
- <button type="submit" title={translations.STEPS.NEXT} className="cta">
747
- {translations.STEPS.NEXT}
748
- </button>
749
- </div>
750
- </form>
751
- );
752
- };
753
-
754
- export default TravelersForm;
1
+ import { compact, get, sortBy } from "lodash";
2
+ import React, { useContext, useEffect, useState } from "react";
3
+ import { useSelector } from "react-redux";
4
+ import { setCurrentStep } from "../booking/booking-slice";
5
+ import {
6
+ selectFormRooms,
7
+ selectTravelersFormValues,
8
+ setFormValues,
9
+ } from "./travelers-form-slice";
10
+
11
+ import { Link, navigate } from "@reach/router";
12
+ import { format, parse } from "date-fns";
13
+ import flat from "flat";
14
+ import { useFormik } from "formik";
15
+ import produce from "immer";
16
+ import { buildClassName } from "../../../shared/utils/class-util";
17
+ import LabeledInput from "../../components/labeled-input";
18
+ import LabeledSelect from "../../components/labeled-select";
19
+ import SettingsContext from "../../settings-context";
20
+ import { useAppDispatch } from "../../store";
21
+ import { RoomTraveler, Traveler, TravelersFormValues } from "../../types";
22
+ import { setBookingType } from "../booking/booking-slice";
23
+ import { OPTIONS_FORM_STEP, SUMMARY_STEP } from "../booking/constants";
24
+ import {
25
+ selectAgentAdressId,
26
+ selectAgents,
27
+ selectBookingQueryString,
28
+ selectBookingType,
29
+ selectStartDate,
30
+ selectTranslations,
31
+ } from "../booking/selectors";
32
+ import { fetchPriceDetails } from "../price-details/price-details-slice";
33
+ import TypeAheadInput from "./type-ahead-input";
34
+ import validateForm from "./validate-form";
35
+
36
+ interface TravelersFormProps { }
37
+
38
+ function createTraveler(traveler: RoomTraveler, followNumber: { number: number} , personTranslation?: string, isCompact?: boolean,) {
39
+ if (isCompact) {
40
+ return { id: traveler.id, firstName: personTranslation, lastName: `${followNumber.number++}`, birthDate: "", gender: "", age: traveler.age } as Traveler;
41
+ } else {
42
+ return { id: traveler.id, firstName: "", lastName: "", birthDate: "", gender: "" } as Traveler;
43
+ }
44
+ }
45
+
46
+ function createInitialValues(
47
+ formRooms: { adults: RoomTraveler[]; children: RoomTraveler[] }[],
48
+ startDate?: string,
49
+ agentAdressId?: number,
50
+ personTranslation?: string,
51
+ isCompact?: boolean
52
+ ) {
53
+ let followNumber = { number: 0 };
54
+
55
+ const initialValues = {
56
+ startDate: startDate,
57
+ rooms: formRooms.map((r) => ({
58
+ adults: r.adults.map((x) => createTraveler(x, followNumber, personTranslation, isCompact)),
59
+ children: r.children.map((x) => createTraveler(x, followNumber, personTranslation, isCompact)),
60
+ })),
61
+ mainBookerId: -1,
62
+ street: "",
63
+ houseNumber: "",
64
+ box: "",
65
+ zipCode: "",
66
+ place: "",
67
+ country: "",
68
+ phone: "",
69
+ email: "",
70
+ emailConfirmation: "",
71
+ travelAgentId: agentAdressId ?? 0,
72
+ travelAgentName: "",
73
+ };
74
+
75
+ if (
76
+ initialValues.rooms &&
77
+ initialValues.rooms.length &&
78
+ initialValues.rooms[0].adults &&
79
+ initialValues.rooms[0].adults.length
80
+ ) {
81
+ initialValues.mainBookerId = initialValues.rooms[0].adults[0].id;
82
+ }
83
+
84
+ return initialValues;
85
+ }
86
+
87
+ const TravelersForm: React.FC<TravelersFormProps> = () => {
88
+ const dispatch = useAppDispatch();
89
+
90
+ const settings = useContext(SettingsContext);
91
+ const bookingQueryString = useSelector(selectBookingQueryString);
92
+ const startDate = useSelector(selectStartDate);
93
+ const formRooms = useSelector(selectFormRooms);
94
+ const bookingType = useSelector(selectBookingType);
95
+ const agents = useSelector(selectAgents);
96
+ const agentAdressId = useSelector(selectAgentAdressId);
97
+ const translations = useSelector(selectTranslations);
98
+ const useCompactForm = !!settings.travellers.compactForm && !!settings.agentAdressId;
99
+
100
+ const initialValues = useSelector(selectTravelersFormValues) ??
101
+ createInitialValues(formRooms, startDate, agentAdressId, translations.TRAVELERS_FORM.PERSON, useCompactForm);
102
+
103
+ const [showAgents, setShowAgents] = useState<boolean>(
104
+ settings.agentRequired ?? false
105
+ );
106
+ const [showAgentSelection, setShowAgentSelection] = useState<boolean>(
107
+ !settings.agentAdressId && !settings.hideAgentSelection
108
+ );
109
+
110
+ const typeaheadAgents =
111
+ sortBy(
112
+ agents?.map((x) => ({
113
+ key: `${x.id}`,
114
+ value: `${x.name} (${x.postalCode} ${x.location})`,
115
+ text: `${x.name} (${x.postalCode} ${x.location})`,
116
+ })),
117
+ "value"
118
+ ) ?? [];
119
+
120
+ const [filteredAgents, setFilteredAgents] =
121
+ useState<{ key: string; value: string; text: string }[]>(typeaheadAgents);
122
+
123
+ const formik = useCompactForm
124
+ ? useFormik<TravelersFormValues>({
125
+ initialValues,
126
+ validate: (values) => { },
127
+ onSubmit: (values) => {
128
+ dispatch(setFormValues(values));
129
+ dispatch(fetchPriceDetails());
130
+
131
+ if (settings.skipRouter) {
132
+ dispatch(setCurrentStep(SUMMARY_STEP));
133
+ } else {
134
+ navigate(
135
+ `${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`
136
+ );
137
+ }
138
+ },
139
+ })
140
+ : useFormik<TravelersFormValues>({
141
+ initialValues,
142
+ validate: (values) =>
143
+ validateForm(values, settings.agentRequired, bookingType, translations),
144
+ onSubmit: (values) => {
145
+ dispatch(setFormValues(values));
146
+ dispatch(fetchPriceDetails());
147
+
148
+ if (settings.skipRouter) {
149
+ dispatch(setCurrentStep(SUMMARY_STEP));
150
+ } else {
151
+ navigate(
152
+ `${settings.basePath}${settings.summary.pathSuffix}?${bookingQueryString}`
153
+ );
154
+ }
155
+ },
156
+ });
157
+
158
+ useEffect(() => {
159
+ dispatch(fetchPriceDetails());
160
+ }, []);
161
+
162
+ useEffect(() => {
163
+ if (agents && settings.affiliateSlug) {
164
+ const agent = agents.find(
165
+ (x) => x.affiliateSlug && x.affiliateSlug === settings.affiliateSlug
166
+ );
167
+ if (!agent) return;
168
+
169
+ const formValues = produce(formik.values, (values) => {
170
+ (values.travelAgentId = Number(agent.id)),
171
+ (values.travelAgentName = agent.name);
172
+ });
173
+ formik.setValues(formValues, false);
174
+ dispatch(setFormValues(formValues));
175
+ setShowAgentSelection(false);
176
+ }
177
+ }, [agents, settings.affiliateSlug]);
178
+
179
+ const handleMainBookerChange: React.FormEventHandler<HTMLInputElement> = (
180
+ e
181
+ ) => {
182
+ const id = parseInt(e.currentTarget.value);
183
+
184
+ formik.setFieldValue("mainBookerId", id);
185
+ };
186
+
187
+ const mainBooker = formik.values.rooms
188
+ .find((r) =>
189
+ r.adults.find((traveler) => traveler.id === formik.values.mainBookerId)
190
+ )
191
+ ?.adults.find((traveler) => traveler.id === formik.values.mainBookerId);
192
+
193
+ const handleAgentChange = (value: string) => {
194
+ const filteredAgents = typeaheadAgents.filter(
195
+ (x) => x.value.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
196
+ );
197
+
198
+ setFilteredAgents(filteredAgents);
199
+ formik.setFieldValue("travelAgentName", value);
200
+ };
201
+
202
+ const handleAgentSelect = (key: string) => {
203
+ const agent = typeaheadAgents.find((x) => x.key === key);
204
+
205
+ formik.setValues({
206
+ ...formik.values,
207
+ travelAgentId: Number(agent?.key),
208
+ travelAgentName: agent?.value ?? "",
209
+ });
210
+
211
+ let bookingType = "b2b2c";
212
+ if (agentAdressId && agentAdressId != 0) {
213
+ bookingType = "b2b";
214
+ }
215
+ dispatch(setBookingType(bookingType));
216
+ };
217
+
218
+ const handleAgentClear = () => {
219
+ formik.setValues({
220
+ ...formik.values,
221
+ travelAgentId: 0,
222
+ travelAgentName: "",
223
+ });
224
+
225
+ dispatch(setBookingType("b2c"));
226
+ };
227
+
228
+ const toggleAgent = (value: boolean) => {
229
+ setShowAgents(value);
230
+
231
+ if (!value) {
232
+ handleAgentClear();
233
+ setFilteredAgents([]);
234
+ }
235
+ };
236
+
237
+ const goPrevious = () => {
238
+ dispatch(setCurrentStep(OPTIONS_FORM_STEP));
239
+ };
240
+
241
+ const flatErrors: Record<string, string> = flat(formik.errors);
242
+ const hasVisibleError = (key: string) =>
243
+ get(formik.errors, key) && get(formik.touched, key);
244
+
245
+ return (
246
+ <form
247
+ className="form form__travelers"
248
+ name="booking--travellers"
249
+ id="booking--travellers"
250
+ noValidate
251
+ onSubmit={formik.handleSubmit}
252
+ onReset={formik.handleReset}
253
+ >
254
+ {useCompactForm
255
+ ? (
256
+ <div className="form__travelers__wrapper">
257
+ {formik.values.rooms.map((room, rIndex) => (
258
+ <div key={rIndex}>
259
+ <div className="form__region">
260
+ <div className="form__region-header">
261
+ <h5 className="form__region-heading">
262
+ {translations.SHARED.ROOM} {rIndex + 1}
263
+ </h5>
264
+ <p className="form__region-label">
265
+ {compact([
266
+ room.adults.length,
267
+ room.adults.length === 1 &&
268
+ ` ${translations.TRAVELERS_FORM.ADULT}`,
269
+ room.adults.length > 1 &&
270
+ ` ${translations.TRAVELERS_FORM.ADULTS}`,
271
+ room.adults &&
272
+ room.adults.length &&
273
+ room.children &&
274
+ room.children.length &&
275
+ ", ",
276
+ room.children.length,
277
+ room.children.length === 1 &&
278
+ ` ${translations.TRAVELERS_FORM.CHILD}`,
279
+ room.children.length > 1 &&
280
+ ` ${translations.TRAVELERS_FORM.CHILDREN}`,
281
+ ]).join("")}
282
+ </p>
283
+ </div>
284
+ </div>
285
+ {room.adults.map((travelerValues, index) => (
286
+ <div className="form__region" key={travelerValues.id}>
287
+ <div className="form__region-header">
288
+ <h5 className="form__region-heading">
289
+ {translations.TRAVELERS_FORM.TRAVELER} {index + 1}
290
+ </h5>
291
+ <p className="form__region-label">
292
+ {translations.TRAVELERS_FORM.ADULT}
293
+ </p>
294
+
295
+ <div className="radiobutton">
296
+ <label className="radiobutton__label">
297
+ <input
298
+ type="radio"
299
+ name="mainBookerId"
300
+ onChange={handleMainBookerChange}
301
+ onBlur={formik.handleBlur}
302
+ value={travelerValues.id}
303
+ checked={
304
+ formik.values.mainBookerId === travelerValues.id
305
+ }
306
+ className="radiobutton__input"
307
+ />
308
+ {translations.TRAVELERS_FORM.MAIN_BOOKER}
309
+ </label>
310
+ </div>
311
+ </div>
312
+ <div className="form__row">
313
+ <LabeledInput
314
+ hasError={hasVisibleError(
315
+ `rooms[${rIndex}].children[${index}].age`
316
+ )}
317
+ extraClassName="form__group--md-33"
318
+ label={translations.TRAVELERS_FORM.AGE}
319
+ required
320
+ name={`rooms[${rIndex}].children[${index}].age`}
321
+ onChange={formik.handleChange}
322
+ onBlur={formik.handleBlur}
323
+ value={travelerValues.age}
324
+ />
325
+ </div>
326
+ </div>
327
+ ))}
328
+ {room.children.map((travelerValues, index) => (
329
+ <div className="form__region" key={travelerValues.id}>
330
+ <div className="form__region-header">
331
+ <h5 className="form__region-heading">
332
+ {translations.TRAVELERS_FORM.TRAVELER}{" "}
333
+ {room.adults.length + index + 1}
334
+ </h5>
335
+ <p className="form__region-label">
336
+ {translations.TRAVELERS_FORM.CHILD}
337
+ </p>
338
+ </div>
339
+ <div className="form__row">
340
+ <LabeledInput
341
+ hasError={hasVisibleError(
342
+ `rooms[${rIndex}].children[${index}].age`
343
+ )}
344
+ extraClassName="form__group--md-33"
345
+ label={translations.TRAVELERS_FORM.AGE}
346
+ required
347
+ name={`rooms[${rIndex}].children[${index}].age`}
348
+ onChange={formik.handleChange}
349
+ onBlur={formik.handleBlur}
350
+ value={travelerValues.age}
351
+ />
352
+ </div>
353
+ </div>
354
+ ))}
355
+ </div>
356
+ ))}
357
+ </div>
358
+ )
359
+ : (
360
+ <>
361
+ <div className="form__travelers__wrapper">
362
+ {formik.values.rooms.map((room, rIndex) => (
363
+ <div key={rIndex}>
364
+ <div className="form__region">
365
+ <div className="form__region-header">
366
+ <h5 className="form__region-heading">
367
+ {translations.SHARED.ROOM} {rIndex + 1}
368
+ </h5>
369
+ <p className="form__region-label">
370
+ {compact([
371
+ room.adults.length,
372
+ room.adults.length === 1 &&
373
+ ` ${translations.TRAVELERS_FORM.ADULT}`,
374
+ room.adults.length > 1 &&
375
+ ` ${translations.TRAVELERS_FORM.ADULTS}`,
376
+ room.adults &&
377
+ room.adults.length &&
378
+ room.children &&
379
+ room.children.length &&
380
+ ", ",
381
+ room.children.length,
382
+ room.children.length === 1 &&
383
+ ` ${translations.TRAVELERS_FORM.CHILD}`,
384
+ room.children.length > 1 &&
385
+ ` ${translations.TRAVELERS_FORM.CHILDREN}`,
386
+ ]).join("")}
387
+ </p>
388
+ </div>
389
+ </div>
390
+ {room.adults.map((travelerValues, index) => (
391
+ <div className="form__region" key={travelerValues.id}>
392
+ <div className="form__region-header">
393
+ <h5 className="form__region-heading">
394
+ {translations.TRAVELERS_FORM.TRAVELER} {index + 1}
395
+ </h5>
396
+ <p className="form__region-label">
397
+ {translations.TRAVELERS_FORM.ADULT}
398
+ </p>
399
+
400
+ <div className="radiobutton">
401
+ <label className="radiobutton__label">
402
+ <input
403
+ type="radio"
404
+ name="mainBookerId"
405
+ onChange={handleMainBookerChange}
406
+ onBlur={formik.handleBlur}
407
+ value={travelerValues.id}
408
+ checked={
409
+ formik.values.mainBookerId === travelerValues.id
410
+ }
411
+ className="radiobutton__input"
412
+ />
413
+ {translations.TRAVELERS_FORM.MAIN_BOOKER}
414
+ </label>
415
+ </div>
416
+ </div>
417
+ <div className="form__row">
418
+ <div
419
+ className={buildClassName([
420
+ "form__group",
421
+ hasVisibleError(
422
+ `rooms[${rIndex}].adults[${index}].gender`
423
+ ) && "form__group--error",
424
+ ])}
425
+ >
426
+ <label className="form__label">
427
+ {translations.TRAVELERS_FORM.GENDER} *
428
+ </label>
429
+ <div className="radiobutton-group">
430
+ <div className="radiobutton">
431
+ <label className="radiobutton__label">
432
+ <input
433
+ type="radio"
434
+ className="radiobutton__input"
435
+ name={`rooms[${rIndex}].adults[${index}].gender`}
436
+ onChange={formik.handleChange}
437
+ onBlur={formik.handleBlur}
438
+ value="m"
439
+ checked={travelerValues.gender === "m"}
440
+ />
441
+ {translations.TRAVELERS_FORM.MALE}
442
+ </label>
443
+ </div>
444
+
445
+ <div className="radiobutton">
446
+ <label className="radiobutton__label">
447
+ <input
448
+ type="radio"
449
+ className="radiobutton__input"
450
+ name={`rooms[${rIndex}].adults[${index}].gender`}
451
+ onChange={formik.handleChange}
452
+ onBlur={formik.handleBlur}
453
+ value="f"
454
+ checked={travelerValues.gender === "f"}
455
+ />
456
+ {translations.TRAVELERS_FORM.FEMALE}
457
+ </label>
458
+ </div>
459
+
460
+ <div className="radiobutton">
461
+ <label className="radiobutton__label">
462
+ <input
463
+ type="radio"
464
+ className="radiobutton__input"
465
+ name={`rooms[${rIndex}].adults[${index}].gender`}
466
+ onChange={formik.handleChange}
467
+ onBlur={formik.handleBlur}
468
+ value="x"
469
+ checked={travelerValues.gender === "x"}
470
+ />
471
+ {translations.TRAVELERS_FORM.OTHER}
472
+ </label>
473
+ </div>
474
+ </div>
475
+ </div>
476
+ </div>
477
+ <div className="form__row">
478
+ <LabeledInput
479
+ hasError={hasVisibleError(
480
+ `rooms[${rIndex}].adults[${index}].firstName`
481
+ )}
482
+ extraClassName="form__group--md-33"
483
+ label={translations.TRAVELERS_FORM.FIRST_NAME}
484
+ required
485
+ name={`rooms[${rIndex}].adults[${index}].firstName`}
486
+ onChange={formik.handleChange}
487
+ onBlur={formik.handleBlur}
488
+ value={travelerValues.firstName}
489
+ />
490
+ <LabeledInput
491
+ hasError={hasVisibleError(
492
+ `rooms[${rIndex}].adults[${index}].lastName`
493
+ )}
494
+ extraClassName="form__group--md-33"
495
+ label={translations.TRAVELERS_FORM.LAST_NAME}
496
+ required
497
+ name={`rooms[${rIndex}].adults[${index}].lastName`}
498
+ onChange={formik.handleChange}
499
+ onBlur={formik.handleBlur}
500
+ value={travelerValues.lastName}
501
+ />
502
+ <LabeledInput
503
+ type="date"
504
+ hasError={hasVisibleError(
505
+ `rooms[${rIndex}].adults[${index}].birthDate`
506
+ )}
507
+ extraClassName="form__group--md-33"
508
+ label={translations.TRAVELERS_FORM.BIRTHDATE}
509
+ required
510
+ name={`rooms[${rIndex}].adults[${index}].birthDate`}
511
+ onChange={formik.handleChange}
512
+ onBlur={formik.handleBlur}
513
+ value={travelerValues.birthDate}
514
+ />
515
+ </div>
516
+ </div>
517
+ ))}
518
+ {room.children.map((travelerValues, index) => (
519
+ <div className="form__region" key={travelerValues.id}>
520
+ <div className="form__region-header">
521
+ <h5 className="form__region-heading">
522
+ {translations.TRAVELERS_FORM.TRAVELER}{" "}
523
+ {room.adults.length + index + 1}
524
+ </h5>
525
+ <p className="form__region-label">
526
+ {translations.TRAVELERS_FORM.CHILD}
527
+ </p>
528
+ </div>
529
+ <div className="form__row">
530
+ <div
531
+ className={buildClassName([
532
+ "form__group",
533
+ hasVisibleError(
534
+ `rooms[${rIndex}].children[${index}].gender`
535
+ ) && "form__group--error",
536
+ ])}
537
+ >
538
+ <label className="form__label">
539
+ {translations.TRAVELERS_FORM.GENDER} *
540
+ </label>
541
+ <div className="radiobutton-group">
542
+ <div className="radiobutton">
543
+ <label className="radiobutton__label">
544
+ <input
545
+ type="radio"
546
+ className="radiobutton__input"
547
+ name={`rooms[${rIndex}].children[${index}].gender`}
548
+ onChange={formik.handleChange}
549
+ onBlur={formik.handleBlur}
550
+ value="m"
551
+ checked={travelerValues.gender === "m"}
552
+ />
553
+ {translations.TRAVELERS_FORM.MALE}
554
+ </label>
555
+ </div>
556
+
557
+ <div className="radiobutton">
558
+ <label className="radiobutton__label">
559
+ <input
560
+ type="radio"
561
+ className="radiobutton__input"
562
+ name={`rooms[${rIndex}].children[${index}].gender`}
563
+ onChange={formik.handleChange}
564
+ onBlur={formik.handleBlur}
565
+ value="f"
566
+ checked={travelerValues.gender === "f"}
567
+ />
568
+ {translations.TRAVELERS_FORM.FEMALE}
569
+ </label>
570
+ </div>
571
+
572
+ <div className="radiobutton">
573
+ <label className="radiobutton__label">
574
+ <input
575
+ type="radio"
576
+ className="radiobutton__input"
577
+ name={`rooms[${rIndex}].children[${index}].gender`}
578
+ onChange={formik.handleChange}
579
+ onBlur={formik.handleBlur}
580
+ value="x"
581
+ checked={travelerValues.gender === "x"}
582
+ />
583
+ {translations.TRAVELERS_FORM.OTHER}
584
+ </label>
585
+ </div>
586
+ </div>
587
+ </div>
588
+ </div>
589
+ <div className="form__row">
590
+ <LabeledInput
591
+ hasError={hasVisibleError(
592
+ `rooms[${rIndex}].children[${index}].firstName`
593
+ )}
594
+ extraClassName="form__group--md-33"
595
+ label={translations.TRAVELERS_FORM.FIRST_NAME}
596
+ required
597
+ name={`rooms[${rIndex}].children[${index}].firstName`}
598
+ onChange={formik.handleChange}
599
+ onBlur={formik.handleBlur}
600
+ value={travelerValues.firstName}
601
+ />
602
+ <LabeledInput
603
+ hasError={hasVisibleError(
604
+ `rooms[${rIndex}].children[${index}].lastName`
605
+ )}
606
+ extraClassName="form__group--md-33"
607
+ label={translations.TRAVELERS_FORM.LAST_NAME}
608
+ required
609
+ name={`rooms[${rIndex}].children[${index}].lastName`}
610
+ onChange={formik.handleChange}
611
+ onBlur={formik.handleBlur}
612
+ value={travelerValues.lastName}
613
+ />
614
+ <LabeledInput
615
+ type="date"
616
+ hasError={hasVisibleError(
617
+ `rooms[${rIndex}].children[${index}].birthDate`
618
+ )}
619
+ extraClassName="form__group--md-33"
620
+ label={translations.TRAVELERS_FORM.BIRTHDATE}
621
+ required
622
+ name={`rooms[${rIndex}].children[${index}].birthDate`}
623
+ onChange={formik.handleChange}
624
+ onBlur={formik.handleBlur}
625
+ value={travelerValues.birthDate}
626
+ />
627
+ </div>
628
+ </div>
629
+ ))}
630
+ </div>
631
+ ))}
632
+
633
+ {bookingType != "b2b" ? (
634
+ <div className="form__region">
635
+ <div className="form__region-header">
636
+ <h5 className="form__region-heading">
637
+ {translations.TRAVELERS_FORM.MAIN_BOOKER}
638
+ </h5>
639
+ <p className="form__region-label">
640
+ {compact([
641
+ compact([mainBooker?.firstName, mainBooker?.lastName]).join(
642
+ " "
643
+ ),
644
+ mainBooker?.birthDate &&
645
+ format(
646
+ parse(mainBooker.birthDate, "yyyy-MM-dd", new Date()),
647
+ "dd-MM-yyyy"
648
+ ),
649
+ ]).join(", ")}
650
+ </p>
651
+ </div>
652
+ <>
653
+ <div className="form__twocolumn">
654
+ <div className="form__twocolumn-column">
655
+ <div className="form__row">
656
+ <LabeledInput
657
+ hasError={hasVisibleError("street")}
658
+ extraClassName="form__group--50 form__group--sm-60"
659
+ label={translations.TRAVELERS_FORM.STREET}
660
+ required
661
+ name="street"
662
+ onChange={formik.handleChange}
663
+ onBlur={formik.handleBlur}
664
+ value={formik.values.street}
665
+ />
666
+ <LabeledInput
667
+ hasError={hasVisibleError("houseNumber")}
668
+ extraClassName="form__group--30 form__group--sm-20"
669
+ label={translations.TRAVELERS_FORM.HOUSE_NUMBER}
670
+ required
671
+ name="houseNumber"
672
+ onChange={formik.handleChange}
673
+ onBlur={formik.handleBlur}
674
+ value={formik.values.houseNumber}
675
+ />
676
+ <LabeledInput
677
+ hasError={hasVisibleError("box")}
678
+ extraClassName="form__group--20"
679
+ label={translations.TRAVELERS_FORM.POST_BOX}
680
+ name="box"
681
+ onChange={formik.handleChange}
682
+ onBlur={formik.handleBlur}
683
+ value={formik.values.box}
684
+ />
685
+ </div>
686
+ </div>
687
+ <div className="form__twocolumn-column">
688
+ <div className="form__row">
689
+ <LabeledInput
690
+ hasError={hasVisibleError("zipCode")}
691
+ extraClassName="form__group--40 form__group--sm-20"
692
+ label={translations.TRAVELERS_FORM.ZIPCODE}
693
+ required
694
+ name="zipCode"
695
+ onChange={formik.handleChange}
696
+ onBlur={formik.handleBlur}
697
+ value={formik.values.zipCode}
698
+ />
699
+ <LabeledInput
700
+ hasError={hasVisibleError("place")}
701
+ extraClassName="form__group--60 form__group--sm-40"
702
+ label={translations.TRAVELERS_FORM.CITY}
703
+ required
704
+ name="place"
705
+ onChange={formik.handleChange}
706
+ onBlur={formik.handleBlur}
707
+ value={formik.values.place}
708
+ />
709
+ <LabeledSelect
710
+ hasError={hasVisibleError("country")}
711
+ extraClassName="form__group--sm-40"
712
+ label={translations.TRAVELERS_FORM.COUNTRY}
713
+ required
714
+ name="country"
715
+ onChange={formik.handleChange}
716
+ onBlur={formik.handleBlur}
717
+ value={formik.values.country}
718
+ options={[
719
+ {
720
+ key: "empty",
721
+ label: translations.TRAVELERS_FORM.SELECT_COUNTRY,
722
+ value: undefined,
723
+ },
724
+ {
725
+ key: "be",
726
+ value: "be",
727
+ label: translations.TRAVELERS_FORM.COUNTRIES.BELGIUM,
728
+ },
729
+ {
730
+ key: "nl",
731
+ value: "nl",
732
+ label:
733
+ translations.TRAVELERS_FORM.COUNTRIES.NETHERLANDS,
734
+ },
735
+ {
736
+ key: "fr",
737
+ value: "fr",
738
+ label: translations.TRAVELERS_FORM.COUNTRIES.FRANCE,
739
+ },
740
+ ]}
741
+ />
742
+ </div>
743
+ </div>
744
+ </div>
745
+ <div className="form__row">
746
+ <LabeledInput
747
+ hasError={hasVisibleError("phone")}
748
+ extraClassName="form__group--md-33"
749
+ label={translations.TRAVELERS_FORM.PHONE}
750
+ required
751
+ name="phone"
752
+ onChange={formik.handleChange}
753
+ onBlur={formik.handleBlur}
754
+ value={formik.values.phone}
755
+ />
756
+ <LabeledInput
757
+ type="email"
758
+ hasError={hasVisibleError("email")}
759
+ extraClassName="form__group--md-33"
760
+ label={translations.TRAVELERS_FORM.EMAIL}
761
+ required
762
+ name="email"
763
+ onChange={formik.handleChange}
764
+ onBlur={formik.handleBlur}
765
+ value={formik.values.email}
766
+ />
767
+ <LabeledInput
768
+ type="email"
769
+ hasError={hasVisibleError("emailConfirmation")}
770
+ extraClassName="form__group--md-33"
771
+ label={translations.TRAVELERS_FORM.REPEAT_EMAIL}
772
+ required
773
+ name="emailConfirmation"
774
+ onChange={formik.handleChange}
775
+ onBlur={formik.handleBlur}
776
+ value={formik.values.emailConfirmation}
777
+ />
778
+ </div>
779
+ </>
780
+ </div>
781
+ ) : (
782
+ <div className="form__region">
783
+ <div className="form__row">
784
+ <LabeledInput
785
+ hasError={hasVisibleError("phone")}
786
+ extraClassName="form__group--md-33"
787
+ label={translations.TRAVELERS_FORM.PHONE}
788
+ required
789
+ name="phone"
790
+ onChange={formik.handleChange}
791
+ onBlur={formik.handleBlur}
792
+ value={formik.values.phone}
793
+ />
794
+ </div>
795
+ </div>
796
+ )}
797
+
798
+ {showAgentSelection && (
799
+ <div className="form__region">
800
+ <div className="form__region-header">
801
+ <h5 className="form__region-heading">
802
+ {translations.TRAVELERS_FORM.BOOK_WITH_AGENT}
803
+ </h5>
804
+ <div className="checkbox" id="cbxChooseOffice">
805
+ <label className="checkbox__label">
806
+ <input
807
+ type="checkbox"
808
+ name="booking--mainbooker"
809
+ defaultChecked={showAgents}
810
+ onClick={() => toggleAgent(!showAgents)}
811
+ className="checkbox__input"
812
+ />
813
+ {translations.TRAVELERS_FORM.CHOOSE_OFFICE}
814
+ </label>
815
+ </div>
816
+ </div>
817
+ {showAgents && (
818
+ <div className="form__row form__row--choose-office">
819
+ <div
820
+ className={buildClassName([
821
+ "form__group",
822
+ "form__group--icon",
823
+ hasVisibleError("travelAgentId") && "form__group--error",
824
+ ])}
825
+ >
826
+ <TypeAheadInput
827
+ value={formik.values.travelAgentName}
828
+ options={filteredAgents}
829
+ onChange={handleAgentChange}
830
+ onSelect={handleAgentSelect}
831
+ onClear={handleAgentClear}
832
+ name="travelAgentName"
833
+ placeholder={
834
+ translations.TRAVELERS_FORM.CHOOSE_AGENT_PLACEHOLDER
835
+ }
836
+ />
837
+ </div>
838
+ </div>
839
+ )}
840
+ </div>
841
+ )}
842
+ </div>
843
+ {Object.keys(flatErrors).length > 0 && (
844
+ <div className="form__region form__region--errors">
845
+ <div className="form__row">
846
+ <div className="form__group">
847
+ <p className="form__error-heading">
848
+ {translations.TRAVELERS_FORM.VALIDATION_MESSAGE}:
849
+ </p>
850
+ <ul className="list">
851
+ {Object.keys(flatErrors).map((key) => (
852
+ <li key={key}>{get(flatErrors, key)}</li>
853
+ ))}
854
+ </ul>
855
+ </div>
856
+ </div>
857
+ </div>
858
+ )}
859
+ </>
860
+ )}
861
+ <div className="booking__navigator">
862
+ {settings.skipRouter ? (
863
+ <button
864
+ type="button"
865
+ title={translations.STEPS.PREVIOUS}
866
+ onClick={() => goPrevious()}
867
+ className="cta cta--secondary"
868
+ >
869
+ {translations.STEPS.PREVIOUS}
870
+ </button>
871
+ ) : (
872
+ <Link
873
+ to={`${settings.basePath}${settings.options.pathSuffix}?${bookingQueryString}`}
874
+ title={translations.STEPS.PREVIOUS}
875
+ className="cta cta--secondary"
876
+ >
877
+ {translations.STEPS.PREVIOUS}
878
+ </Link>
879
+ )}
880
+ <button type="submit" title={translations.STEPS.NEXT} className="cta">
881
+ {translations.STEPS.NEXT}
882
+ </button>
883
+ </div>
884
+ </form>
885
+ );
886
+ };
887
+
888
+ export default TravelersForm;