@qite/tide-booking-component 0.0.2-preview.58 → 0.0.2-preview.60

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.
@@ -1,650 +1,659 @@
1
- import React, { useContext, useEffect, useState } from "react";
2
- import { compact, get, sortBy } from "lodash";
3
- import {
4
- selectAdultIds,
5
- selectChildIds,
6
- selectTravelersFormValues,
7
- setFormValues,
8
- } from "./travelers-form-slice";
9
- import { useSelector } from "react-redux";
10
-
11
- import LabeledInput from "../../components/labeled-input";
12
- import LabeledSelect from "../../components/labeled-select";
13
- import SettingsContext from "../../settings-context";
14
- import { TravelersFormValues } from "../../types";
15
- import { buildClassName } from "../../utils/class-util";
16
- import { fetchPriceDetails } from "../price-details/price-details-slice";
17
- import flat from "flat";
18
- import { navigate } from "@reach/router";
19
- import produce from "immer";
20
- import {
21
- selectAgents,
22
- selectBookingQueryString,
23
- selectStartDate,
24
- } from "../booking/selectors";
25
- import translations from "../../translations/translations.json";
26
- import { useFormik } from "formik";
27
- import validateForm from "./validate-form";
28
- import { format, parse } from "date-fns";
29
- import { useAppDispatch } from "../../store";
30
- import TypeAheadInput from "./type-ahead-input";
31
-
32
- interface TravelersFormProps {}
33
-
34
- function createTraveler(id: number) {
35
- return { id, firstName: "", lastName: "", birthDate: "", gender: "" };
36
- }
37
-
38
- function createInitialValues(
39
- adultIds: number[],
40
- childIds: number[],
41
- startDate?: string
42
- ) {
43
- const initialValues = {
44
- startDate: startDate,
45
- adults: adultIds.map((id) => createTraveler(id)),
46
- children: childIds.map((id) => createTraveler(id)),
47
- mainBookerId: -1,
48
- street: "",
49
- houseNumber: "",
50
- box: "",
51
- zipCode: "",
52
- place: "",
53
- country: "",
54
- phone: "",
55
- email: "",
56
- emailConfirmation: "",
57
- travelAgentId: 0,
58
- travelAgentName: "",
59
- };
60
-
61
- if (initialValues.adults && initialValues.adults.length) {
62
- initialValues.mainBookerId = initialValues.adults[0].id;
63
- }
64
-
65
- return initialValues;
66
- }
67
-
68
- const TravelersForm: React.FC<TravelersFormProps> = () => {
69
- const dispatch = useAppDispatch();
70
-
71
- const settings = useContext(SettingsContext);
72
- const bookingQueryString = useSelector(selectBookingQueryString);
73
- const startDate = useSelector(selectStartDate);
74
- const adultIds = useSelector(selectAdultIds);
75
- const childIds = useSelector(selectChildIds);
76
- const agents = useSelector(selectAgents);
77
-
78
- const initialValues =
79
- useSelector(selectTravelersFormValues) ??
80
- createInitialValues(adultIds, childIds, startDate);
81
-
82
- const [showAgents, setShowAgents] = useState<boolean>(
83
- initialValues.travelAgentId > 0
84
- );
85
-
86
- const typeaheadAgents =
87
- sortBy(
88
- agents?.map((x) => ({
89
- key: `${x.id}`,
90
- value: `${x.name} (${x.postalCode} ${x.location})`,
91
- text: `${x.name} (${x.postalCode} ${x.location})`,
92
- })),
93
- "value"
94
- ) ?? [];
95
-
96
- const [filteredAgents, setFilteredAgents] =
97
- useState<{ key: string; value: string; text: string }[]>(typeaheadAgents);
98
-
99
- const formik = useFormik<TravelersFormValues>({
100
- initialValues,
101
- validate: validateForm,
102
- onSubmit: (values) => {
103
- dispatch(setFormValues(values));
104
- dispatch(fetchPriceDetails());
105
-
106
- navigate(
107
- `${settings.basePath}${settings.options.pathSuffix}?${bookingQueryString}`
108
- );
109
- },
110
- });
111
-
112
- useEffect(() => {
113
- const formValues = produce(formik.values, (values) => {
114
- values.adults = adultIds.map(
115
- (id) => values.adults[id] ?? createTraveler(id)
116
- );
117
-
118
- if (
119
- values.adults.findIndex(
120
- (traveler) => traveler.id === values.mainBookerId
121
- ) === -1 &&
122
- values.adults.length
123
- ) {
124
- values.mainBookerId = values.adults[0].id;
125
- }
126
- });
127
-
128
- formik.setValues(formValues, false);
129
- dispatch(setFormValues(formValues));
130
- }, [adultIds]);
131
-
132
- useEffect(() => {
133
- const formValues = produce(formik.values, (values) => {
134
- values.children = childIds.map(
135
- (id) => values.children[id] ?? createTraveler(id)
136
- );
137
- });
138
- formik.setValues(formValues, false);
139
- dispatch(setFormValues(formValues));
140
- }, [childIds]);
141
-
142
- useEffect(() => {
143
- dispatch(fetchPriceDetails());
144
- }, []);
145
-
146
- const handleMainBookerChange: React.FormEventHandler<HTMLInputElement> = (
147
- e
148
- ) => {
149
- const id = parseInt(e.currentTarget.value);
150
-
151
- formik.setFieldValue("mainBookerId", id);
152
- };
153
-
154
- const mainBooker = formik.values.adults.find(
155
- (traveler) => traveler.id === formik.values.mainBookerId
156
- );
157
-
158
- const handleAgentChange = (value: string) => {
159
- const filteredAgents = typeaheadAgents.filter(
160
- (x) => x.value.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
161
- );
162
-
163
- setFilteredAgents(filteredAgents);
164
- formik.setFieldValue("travelAgentName", value);
165
- };
166
-
167
- const handleAgentSelect = (key: string) => {
168
- const agent = typeaheadAgents.find((x) => x.key === key);
169
-
170
- formik.setFieldValue("travelAgentId", Number(agent?.key));
171
- formik.setFieldValue("travelAgentName", agent?.value);
172
- };
173
-
174
- const handleAgentClear = () => {
175
- formik.setFieldValue("travelAgentId", 0);
176
- formik.setFieldValue("travelAgentName", "");
177
- };
178
-
179
- const toggleAgent = (value: boolean) => {
180
- setShowAgents(value);
181
-
182
- if (!value) {
183
- handleAgentClear();
184
- setFilteredAgents([]);
185
- }
186
- };
187
-
188
- const flatErrors: Record<string, string> = flat(formik.errors);
189
- const hasVisibleError = (key: string) =>
190
- get(formik.errors, key) && get(formik.touched, key);
191
-
192
- return (
193
- <form
194
- className="form"
195
- noValidate
196
- onSubmit={formik.handleSubmit}
197
- onReset={formik.handleReset}
198
- >
199
- <div className="form__region">
200
- <div className="form__region-header">
201
- <h5 className="form__region-heading">
202
- {translations.TRAVELERS_FORM.PERSONS}
203
- </h5>
204
- <p className="form__region-label">
205
- {compact([
206
- adultIds.length,
207
- adultIds.length === 1 && ` ${translations.TRAVELERS_FORM.ADULT}`,
208
- adultIds.length > 1 && ` ${translations.TRAVELERS_FORM.ADULTS}`,
209
- adultIds &&
210
- adultIds.length &&
211
- childIds &&
212
- childIds.length &&
213
- ", ",
214
- childIds.length,
215
- childIds.length === 1 && ` ${translations.TRAVELERS_FORM.CHILD}`,
216
- childIds.length > 1 && ` ${translations.TRAVELERS_FORM.CHILDREN}`,
217
- ]).join("")}
218
- </p>
219
- </div>
220
- </div>
221
- {formik.values.adults.map((travelerValues, index) => (
222
- <div className="form__region" key={travelerValues.id}>
223
- <div className="form__region-header">
224
- <h5 className="form__region-heading">
225
- {translations.TRAVELERS_FORM.TRAVELER} {index + 1}
226
- </h5>
227
- <p className="form__region-label">
228
- {translations.TRAVELERS_FORM.ADULT}
229
- </p>
230
-
231
- <div className="radiobutton">
232
- <label className="radiobutton__label">
233
- <input
234
- type="radio"
235
- name="mainBookerId"
236
- onChange={handleMainBookerChange}
237
- onBlur={formik.handleBlur}
238
- value={travelerValues.id}
239
- checked={formik.values.mainBookerId === travelerValues.id}
240
- className="radiobutton__input"
241
- />
242
- {translations.TRAVELERS_FORM.MAIN_BOOKER}
243
- </label>
244
- </div>
245
- </div>
246
- <div className="form__row">
247
- <div
248
- className={buildClassName([
249
- "form__group",
250
- hasVisibleError(`adults[${index}].gender`) &&
251
- "form__group--error",
252
- ])}
253
- >
254
- <label className="form__label">
255
- {translations.TRAVELERS_FORM.GENDER} *
256
- </label>
257
- <div className="radiobutton-group">
258
- <div className="radiobutton">
259
- <label className="radiobutton__label">
260
- <input
261
- type="radio"
262
- className="radiobutton__input"
263
- name={`adults[${index}].gender`}
264
- onChange={formik.handleChange}
265
- onBlur={formik.handleBlur}
266
- value="m"
267
- checked={travelerValues.gender === "m"}
268
- />
269
- {translations.TRAVELERS_FORM.MALE}
270
- </label>
271
- </div>
272
-
273
- <div className="radiobutton">
274
- <label className="radiobutton__label">
275
- <input
276
- type="radio"
277
- className="radiobutton__input"
278
- name={`adults[${index}].gender`}
279
- onChange={formik.handleChange}
280
- onBlur={formik.handleBlur}
281
- value="f"
282
- checked={travelerValues.gender === "f"}
283
- />
284
- {translations.TRAVELERS_FORM.FEMALE}
285
- </label>
286
- </div>
287
-
288
- <div className="radiobutton">
289
- <label className="radiobutton__label">
290
- <input
291
- type="radio"
292
- className="radiobutton__input"
293
- name={`adults[${index}].gender`}
294
- onChange={formik.handleChange}
295
- onBlur={formik.handleBlur}
296
- value="x"
297
- checked={travelerValues.gender === "x"}
298
- />
299
- {translations.TRAVELERS_FORM.OTHER}
300
- </label>
301
- </div>
302
- </div>
303
- </div>
304
- </div>
305
- <div className="form__row">
306
- <LabeledInput
307
- hasError={hasVisibleError(`adults[${index}].firstName`)}
308
- extraClassName="form__group--md-33"
309
- label={translations.TRAVELERS_FORM.FIRST_NAME}
310
- required
311
- name={`adults[${index}].firstName`}
312
- onChange={formik.handleChange}
313
- onBlur={formik.handleBlur}
314
- value={travelerValues.firstName}
315
- />
316
- <LabeledInput
317
- hasError={hasVisibleError(`adults[${index}].lastName`)}
318
- extraClassName="form__group--md-33"
319
- label={translations.TRAVELERS_FORM.LAST_NAME}
320
- required
321
- name={`adults[${index}].lastName`}
322
- onChange={formik.handleChange}
323
- onBlur={formik.handleBlur}
324
- value={travelerValues.lastName}
325
- />
326
- <LabeledInput
327
- type="date"
328
- hasError={hasVisibleError(`adults[${index}].birthDate`)}
329
- extraClassName="form__group--md-33"
330
- label={translations.TRAVELERS_FORM.BIRTHDATE}
331
- required
332
- name={`adults[${index}].birthDate`}
333
- onChange={formik.handleChange}
334
- onBlur={formik.handleBlur}
335
- value={travelerValues.birthDate}
336
- />
337
- </div>
338
- </div>
339
- ))}
340
- {formik.values.children.map((travelerValues, index) => (
341
- <div className="form__region" key={travelerValues.id}>
342
- <div className="form__region-header">
343
- <h5 className="form__region-heading">
344
- {translations.TRAVELERS_FORM.TRAVELER}{" "}
345
- {formik.values.adults.length + index + 1}
346
- </h5>
347
- <p className="form__region-label">
348
- {translations.TRAVELERS_FORM.CHILD}
349
- </p>
350
- </div>
351
- <div className="form__row">
352
- <div
353
- className={buildClassName([
354
- "form__group",
355
- hasVisibleError(`children[${index}].gender`) &&
356
- "form__group--error",
357
- ])}
358
- >
359
- <label className="form__label">
360
- {translations.TRAVELERS_FORM.GENDER} *
361
- </label>
362
- <div className="radiobutton-group">
363
- <div className="radiobutton">
364
- <label className="radiobutton__label">
365
- <input
366
- type="radio"
367
- className="radiobutton__input"
368
- name={`children[${index}].gender`}
369
- onChange={formik.handleChange}
370
- onBlur={formik.handleBlur}
371
- value="m"
372
- checked={travelerValues.gender === "m"}
373
- />
374
- {translations.TRAVELERS_FORM.MALE}
375
- </label>
376
- </div>
377
-
378
- <div className="radiobutton">
379
- <label className="radiobutton__label">
380
- <input
381
- type="radio"
382
- className="radiobutton__input"
383
- name={`children[${index}].gender`}
384
- onChange={formik.handleChange}
385
- onBlur={formik.handleBlur}
386
- value="f"
387
- checked={travelerValues.gender === "f"}
388
- />
389
- {translations.TRAVELERS_FORM.FEMALE}
390
- </label>
391
- </div>
392
-
393
- <div className="radiobutton">
394
- <label className="radiobutton__label">
395
- <input
396
- type="radio"
397
- className="radiobutton__input"
398
- name={`children[${index}].gender`}
399
- onChange={formik.handleChange}
400
- onBlur={formik.handleBlur}
401
- value="x"
402
- checked={travelerValues.gender === "x"}
403
- />
404
- {translations.TRAVELERS_FORM.OTHER}
405
- </label>
406
- </div>
407
- </div>
408
- </div>
409
- </div>
410
- <div className="form__row">
411
- <LabeledInput
412
- hasError={hasVisibleError(`children[${index}].firstName`)}
413
- extraClassName="form__group--md-33"
414
- label={translations.TRAVELERS_FORM.FIRST_NAME}
415
- required
416
- name={`children[${index}].firstName`}
417
- onChange={formik.handleChange}
418
- onBlur={formik.handleBlur}
419
- value={travelerValues.firstName}
420
- />
421
- <LabeledInput
422
- hasError={hasVisibleError(`children[${index}].lastName`)}
423
- extraClassName="form__group--md-33"
424
- label={translations.TRAVELERS_FORM.LAST_NAME}
425
- required
426
- name={`children[${index}].lastName`}
427
- onChange={formik.handleChange}
428
- onBlur={formik.handleBlur}
429
- value={travelerValues.lastName}
430
- />
431
- <LabeledInput
432
- type="date"
433
- hasError={hasVisibleError(`children[${index}].birthDate`)}
434
- extraClassName="form__group--md-33"
435
- label={translations.TRAVELERS_FORM.BIRTHDATE}
436
- required
437
- name={`children[${index}].birthDate`}
438
- onChange={formik.handleChange}
439
- onBlur={formik.handleBlur}
440
- value={travelerValues.birthDate}
441
- />
442
- </div>
443
- </div>
444
- ))}
445
- <div className="form__region">
446
- <div className="form__region-header">
447
- <h5 className="form__region-heading">
448
- {translations.TRAVELERS_FORM.MAIN_BOOKER}
449
- </h5>
450
- <p className="form__region-label">
451
- {compact([
452
- compact([mainBooker?.firstName, mainBooker?.lastName]).join(" "),
453
- mainBooker?.birthDate &&
454
- format(
455
- parse(mainBooker.birthDate, "yyyy-MM-dd", new Date()),
456
- "dd-MM-yyyy"
457
- ),
458
- ]).join(", ")}
459
- </p>
460
- </div>
461
-
462
- <div className="form__twocolumn">
463
- <div className="form__twocolumn-column">
464
- <div className="form__row">
465
- <LabeledInput
466
- hasError={hasVisibleError("street")}
467
- extraClassName="form__group--50 form__group--sm-60"
468
- label={translations.TRAVELERS_FORM.STREET}
469
- required
470
- name="street"
471
- onChange={formik.handleChange}
472
- onBlur={formik.handleBlur}
473
- placeholder={translations.TRAVELERS_FORM.STREET_PLACEHOLDER}
474
- value={formik.values.street}
475
- />
476
- <LabeledInput
477
- hasError={hasVisibleError("houseNumber")}
478
- extraClassName="form__group--30 form__group--sm-20"
479
- label={translations.TRAVELERS_FORM.HOUSE_NUMBER}
480
- required
481
- name="houseNumber"
482
- onChange={formik.handleChange}
483
- onBlur={formik.handleBlur}
484
- value={formik.values.houseNumber}
485
- />
486
- <LabeledInput
487
- hasError={hasVisibleError("box")}
488
- extraClassName="form__group--20"
489
- label={translations.TRAVELERS_FORM.POST_BOX}
490
- name="box"
491
- onChange={formik.handleChange}
492
- onBlur={formik.handleBlur}
493
- value={formik.values.box}
494
- />
495
- </div>
496
- </div>
497
-
498
- <div className="form__twocolumn-column">
499
- <div className="form__row">
500
- <LabeledInput
501
- hasError={hasVisibleError("zipCode")}
502
- extraClassName="form__group--40 form__group--sm-20"
503
- label={translations.TRAVELERS_FORM.ZIPCODE}
504
- required
505
- name="zipCode"
506
- onChange={formik.handleChange}
507
- onBlur={formik.handleBlur}
508
- value={formik.values.zipCode}
509
- />
510
- <LabeledInput
511
- hasError={hasVisibleError("place")}
512
- extraClassName="form__group--60 form__group--sm-40"
513
- label={translations.TRAVELERS_FORM.CITY}
514
- required
515
- name="place"
516
- placeholder={translations.TRAVELERS_FORM.CITY_PLACEHOLDER}
517
- onChange={formik.handleChange}
518
- onBlur={formik.handleBlur}
519
- value={formik.values.place}
520
- />
521
- <LabeledSelect
522
- hasError={hasVisibleError("country")}
523
- extraClassName="form__group--sm-40"
524
- label={translations.TRAVELERS_FORM.COUNTRY}
525
- required
526
- name="country"
527
- onChange={formik.handleChange}
528
- onBlur={formik.handleBlur}
529
- value={formik.values.country}
530
- options={[
531
- {
532
- key: "empty",
533
- label: translations.TRAVELERS_FORM.SELECT_COUNTRY,
534
- value: undefined,
535
- },
536
- {
537
- key: "be",
538
- value: "be",
539
- label: "België",
540
- },
541
- {
542
- key: "nl",
543
- value: "nl",
544
- label: "Nederland",
545
- },
546
- {
547
- key: "fr",
548
- value: "fr",
549
- label: "Frankrijk",
550
- },
551
- ]}
552
- />
553
- </div>
554
- </div>
555
- </div>
556
- <div className="form__row">
557
- <LabeledInput
558
- hasError={hasVisibleError("phone")}
559
- extraClassName="form__group--md-33"
560
- label={translations.TRAVELERS_FORM.PHONE}
561
- required
562
- name="phone"
563
- onChange={formik.handleChange}
564
- onBlur={formik.handleBlur}
565
- value={formik.values.phone}
566
- />
567
- <LabeledInput
568
- type="email"
569
- hasError={hasVisibleError("email")}
570
- extraClassName="form__group--md-33"
571
- label={translations.TRAVELERS_FORM.EMAIL}
572
- required
573
- name="email"
574
- onChange={formik.handleChange}
575
- onBlur={formik.handleBlur}
576
- value={formik.values.email}
577
- />
578
- <LabeledInput
579
- type="email"
580
- hasError={hasVisibleError("emailConfirmation")}
581
- extraClassName="form__group--md-33"
582
- label={translations.TRAVELERS_FORM.REPEAT_EMAIL}
583
- required
584
- name="emailConfirmation"
585
- onChange={formik.handleChange}
586
- onBlur={formik.handleBlur}
587
- value={formik.values.emailConfirmation}
588
- />
589
- </div>
590
- </div>
591
- <div className="form__region">
592
- <div className="form__region-header">
593
- <h5 className="form__region-heading">
594
- Ik wens te boeken bij mijn lokale reisagent
595
- </h5>
596
- <div className="checkbox" id="cbxChooseOffice">
597
- <label className="checkbox__label">
598
- <input
599
- type="checkbox"
600
- name="booking--mainbooker"
601
- defaultChecked={showAgents}
602
- onClick={() => toggleAgent(!showAgents)}
603
- className="checkbox__input"
604
- />
605
- Ik kies een kantoor
606
- </label>
607
- </div>
608
- </div>
609
- {showAgents && (
610
- <div className="form__row form__row--choose-office">
611
- <div className="form__group form__group--icon">
612
- <TypeAheadInput
613
- value={formik.values.travelAgentName}
614
- options={filteredAgents}
615
- onChange={handleAgentChange}
616
- onSelect={handleAgentSelect}
617
- onClear={handleAgentClear}
618
- name="travelAgentName"
619
- placeholder="Kies uw reisagent"
620
- />
621
- </div>
622
- </div>
623
- )}
624
- </div>
625
- {Object.keys(flatErrors).length > 0 && (
626
- <div className="form__region form__region--errors">
627
- <div className="form__row">
628
- <div className="form__group">
629
- <p className="form__error-heading">
630
- {translations.TRAVELERS_FORM.VALIDATION_MESSAGE}:
631
- </p>
632
- <ul className="list">
633
- {Object.keys(flatErrors).map((key) => (
634
- <li key={key}>{get(flatErrors, key)}</li>
635
- ))}
636
- </ul>
637
- </div>
638
- </div>
639
- </div>
640
- )}
641
- <div className="booking__navigator">
642
- <button type="submit" title={translations.STEPS.NEXT} className="cta">
643
- {translations.STEPS.NEXT}
644
- </button>
645
- </div>
646
- </form>
647
- );
648
- };
649
-
650
- export default TravelersForm;
1
+ import React, { useContext, useEffect, useState } from "react";
2
+ import { compact, get, sortBy } from "lodash";
3
+ import {
4
+ selectAdultIds,
5
+ selectChildIds,
6
+ selectTravelersFormValues,
7
+ setFormValues,
8
+ } from "./travelers-form-slice";
9
+ import { useSelector } from "react-redux";
10
+
11
+ import LabeledInput from "../../components/labeled-input";
12
+ import LabeledSelect from "../../components/labeled-select";
13
+ import SettingsContext from "../../settings-context";
14
+ import { TravelersFormValues } from "../../types";
15
+ import { buildClassName } from "../../utils/class-util";
16
+ import { fetchPriceDetails } from "../price-details/price-details-slice";
17
+ import flat from "flat";
18
+ import { navigate } from "@reach/router";
19
+ import produce from "immer";
20
+ import {
21
+ selectAgentAdressId,
22
+ selectAgents,
23
+ selectBookingQueryString,
24
+ selectStartDate,
25
+ } from "../booking/selectors";
26
+ import translations from "../../translations/translations.json";
27
+ import { useFormik } from "formik";
28
+ import validateForm from "./validate-form";
29
+ import { format, parse } from "date-fns";
30
+ import { useAppDispatch } from "../../store";
31
+ import TypeAheadInput from "./type-ahead-input";
32
+
33
+ interface TravelersFormProps {}
34
+
35
+ function createTraveler(id: number) {
36
+ return { id, firstName: "", lastName: "", birthDate: "", gender: "" };
37
+ }
38
+
39
+ function createInitialValues(
40
+ adultIds: number[],
41
+ childIds: number[],
42
+ startDate?: string
43
+ ) {
44
+ const initialValues = {
45
+ startDate: startDate,
46
+ adults: adultIds.map((id) => createTraveler(id)),
47
+ children: childIds.map((id) => createTraveler(id)),
48
+ mainBookerId: -1,
49
+ street: "",
50
+ houseNumber: "",
51
+ box: "",
52
+ zipCode: "",
53
+ place: "",
54
+ country: "",
55
+ phone: "",
56
+ email: "",
57
+ emailConfirmation: "",
58
+ travelAgentId: 0,
59
+ travelAgentName: "",
60
+ };
61
+
62
+ if (initialValues.adults && initialValues.adults.length) {
63
+ initialValues.mainBookerId = initialValues.adults[0].id;
64
+ }
65
+
66
+ return initialValues;
67
+ }
68
+
69
+ const TravelersForm: React.FC<TravelersFormProps> = () => {
70
+ const dispatch = useAppDispatch();
71
+
72
+ const settings = useContext(SettingsContext);
73
+ const bookingQueryString = useSelector(selectBookingQueryString);
74
+ const startDate = useSelector(selectStartDate);
75
+ const adultIds = useSelector(selectAdultIds);
76
+ const childIds = useSelector(selectChildIds);
77
+ const agents = useSelector(selectAgents);
78
+ const agentAdressId = useSelector(selectAgentAdressId);
79
+
80
+ console.log("agentAdressId", agentAdressId);
81
+ useEffect(() => {
82
+ if (agentAdressId) {
83
+ formik.setFieldValue("travelAgentId", agentAdressId);
84
+ }
85
+ }, [agentAdressId]);
86
+
87
+ const initialValues =
88
+ useSelector(selectTravelersFormValues) ??
89
+ createInitialValues(adultIds, childIds, startDate);
90
+
91
+ const [showAgents, setShowAgents] = useState<boolean>(
92
+ initialValues.travelAgentId > 0 || agentAdressId != undefined
93
+ );
94
+
95
+ const typeaheadAgents =
96
+ sortBy(
97
+ agents?.map((x) => ({
98
+ key: `${x.id}`,
99
+ value: `${x.name} (${x.postalCode} ${x.location})`,
100
+ text: `${x.name} (${x.postalCode} ${x.location})`,
101
+ })),
102
+ "value"
103
+ ) ?? [];
104
+
105
+ const [filteredAgents, setFilteredAgents] =
106
+ useState<{ key: string; value: string; text: string }[]>(typeaheadAgents);
107
+
108
+ const formik = useFormik<TravelersFormValues>({
109
+ initialValues,
110
+ validate: validateForm,
111
+ onSubmit: (values) => {
112
+ dispatch(setFormValues(values));
113
+ dispatch(fetchPriceDetails());
114
+
115
+ navigate(
116
+ `${settings.basePath}${settings.options.pathSuffix}?${bookingQueryString}`
117
+ );
118
+ },
119
+ });
120
+
121
+ useEffect(() => {
122
+ const formValues = produce(formik.values, (values) => {
123
+ values.adults = adultIds.map(
124
+ (id) => values.adults[id] ?? createTraveler(id)
125
+ );
126
+
127
+ if (
128
+ values.adults.findIndex(
129
+ (traveler) => traveler.id === values.mainBookerId
130
+ ) === -1 &&
131
+ values.adults.length
132
+ ) {
133
+ values.mainBookerId = values.adults[0].id;
134
+ }
135
+ });
136
+
137
+ formik.setValues(formValues, false);
138
+ dispatch(setFormValues(formValues));
139
+ }, [adultIds]);
140
+
141
+ useEffect(() => {
142
+ const formValues = produce(formik.values, (values) => {
143
+ values.children = childIds.map(
144
+ (id) => values.children[id] ?? createTraveler(id)
145
+ );
146
+ });
147
+ formik.setValues(formValues, false);
148
+ dispatch(setFormValues(formValues));
149
+ }, [childIds]);
150
+
151
+ useEffect(() => {
152
+ dispatch(fetchPriceDetails());
153
+ }, []);
154
+
155
+ const handleMainBookerChange: React.FormEventHandler<HTMLInputElement> = (
156
+ e
157
+ ) => {
158
+ const id = parseInt(e.currentTarget.value);
159
+
160
+ formik.setFieldValue("mainBookerId", id);
161
+ };
162
+
163
+ const mainBooker = formik.values.adults.find(
164
+ (traveler) => traveler.id === formik.values.mainBookerId
165
+ );
166
+
167
+ const handleAgentChange = (value: string) => {
168
+ const filteredAgents = typeaheadAgents.filter(
169
+ (x) => x.value.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
170
+ );
171
+
172
+ setFilteredAgents(filteredAgents);
173
+ formik.setFieldValue("travelAgentName", value);
174
+ };
175
+
176
+ const handleAgentSelect = (key: string) => {
177
+ const agent = typeaheadAgents.find((x) => x.key === key);
178
+
179
+ formik.setFieldValue("travelAgentId", Number(agent?.key));
180
+ formik.setFieldValue("travelAgentName", agent?.value);
181
+ };
182
+
183
+ const handleAgentClear = () => {
184
+ formik.setFieldValue("travelAgentId", 0);
185
+ formik.setFieldValue("travelAgentName", "");
186
+ };
187
+
188
+ const toggleAgent = (value: boolean) => {
189
+ setShowAgents(value);
190
+
191
+ if (!value) {
192
+ handleAgentClear();
193
+ setFilteredAgents([]);
194
+ }
195
+ };
196
+
197
+ const flatErrors: Record<string, string> = flat(formik.errors);
198
+ const hasVisibleError = (key: string) =>
199
+ get(formik.errors, key) && get(formik.touched, key);
200
+
201
+ return (
202
+ <form
203
+ className="form"
204
+ noValidate
205
+ onSubmit={formik.handleSubmit}
206
+ onReset={formik.handleReset}
207
+ >
208
+ <div className="form__region">
209
+ <div className="form__region-header">
210
+ <h5 className="form__region-heading">
211
+ {translations.TRAVELERS_FORM.PERSONS}
212
+ </h5>
213
+ <p className="form__region-label">
214
+ {compact([
215
+ adultIds.length,
216
+ adultIds.length === 1 && ` ${translations.TRAVELERS_FORM.ADULT}`,
217
+ adultIds.length > 1 && ` ${translations.TRAVELERS_FORM.ADULTS}`,
218
+ adultIds &&
219
+ adultIds.length &&
220
+ childIds &&
221
+ childIds.length &&
222
+ ", ",
223
+ childIds.length,
224
+ childIds.length === 1 && ` ${translations.TRAVELERS_FORM.CHILD}`,
225
+ childIds.length > 1 && ` ${translations.TRAVELERS_FORM.CHILDREN}`,
226
+ ]).join("")}
227
+ </p>
228
+ </div>
229
+ </div>
230
+ {formik.values.adults.map((travelerValues, index) => (
231
+ <div className="form__region" key={travelerValues.id}>
232
+ <div className="form__region-header">
233
+ <h5 className="form__region-heading">
234
+ {translations.TRAVELERS_FORM.TRAVELER} {index + 1}
235
+ </h5>
236
+ <p className="form__region-label">
237
+ {translations.TRAVELERS_FORM.ADULT}
238
+ </p>
239
+
240
+ <div className="radiobutton">
241
+ <label className="radiobutton__label">
242
+ <input
243
+ type="radio"
244
+ name="mainBookerId"
245
+ onChange={handleMainBookerChange}
246
+ onBlur={formik.handleBlur}
247
+ value={travelerValues.id}
248
+ checked={formik.values.mainBookerId === travelerValues.id}
249
+ className="radiobutton__input"
250
+ />
251
+ {translations.TRAVELERS_FORM.MAIN_BOOKER}
252
+ </label>
253
+ </div>
254
+ </div>
255
+ <div className="form__row">
256
+ <div
257
+ className={buildClassName([
258
+ "form__group",
259
+ hasVisibleError(`adults[${index}].gender`) &&
260
+ "form__group--error",
261
+ ])}
262
+ >
263
+ <label className="form__label">
264
+ {translations.TRAVELERS_FORM.GENDER} *
265
+ </label>
266
+ <div className="radiobutton-group">
267
+ <div className="radiobutton">
268
+ <label className="radiobutton__label">
269
+ <input
270
+ type="radio"
271
+ className="radiobutton__input"
272
+ name={`adults[${index}].gender`}
273
+ onChange={formik.handleChange}
274
+ onBlur={formik.handleBlur}
275
+ value="m"
276
+ checked={travelerValues.gender === "m"}
277
+ />
278
+ {translations.TRAVELERS_FORM.MALE}
279
+ </label>
280
+ </div>
281
+
282
+ <div className="radiobutton">
283
+ <label className="radiobutton__label">
284
+ <input
285
+ type="radio"
286
+ className="radiobutton__input"
287
+ name={`adults[${index}].gender`}
288
+ onChange={formik.handleChange}
289
+ onBlur={formik.handleBlur}
290
+ value="f"
291
+ checked={travelerValues.gender === "f"}
292
+ />
293
+ {translations.TRAVELERS_FORM.FEMALE}
294
+ </label>
295
+ </div>
296
+
297
+ <div className="radiobutton">
298
+ <label className="radiobutton__label">
299
+ <input
300
+ type="radio"
301
+ className="radiobutton__input"
302
+ name={`adults[${index}].gender`}
303
+ onChange={formik.handleChange}
304
+ onBlur={formik.handleBlur}
305
+ value="x"
306
+ checked={travelerValues.gender === "x"}
307
+ />
308
+ {translations.TRAVELERS_FORM.OTHER}
309
+ </label>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ <div className="form__row">
315
+ <LabeledInput
316
+ hasError={hasVisibleError(`adults[${index}].firstName`)}
317
+ extraClassName="form__group--md-33"
318
+ label={translations.TRAVELERS_FORM.FIRST_NAME}
319
+ required
320
+ name={`adults[${index}].firstName`}
321
+ onChange={formik.handleChange}
322
+ onBlur={formik.handleBlur}
323
+ value={travelerValues.firstName}
324
+ />
325
+ <LabeledInput
326
+ hasError={hasVisibleError(`adults[${index}].lastName`)}
327
+ extraClassName="form__group--md-33"
328
+ label={translations.TRAVELERS_FORM.LAST_NAME}
329
+ required
330
+ name={`adults[${index}].lastName`}
331
+ onChange={formik.handleChange}
332
+ onBlur={formik.handleBlur}
333
+ value={travelerValues.lastName}
334
+ />
335
+ <LabeledInput
336
+ type="date"
337
+ hasError={hasVisibleError(`adults[${index}].birthDate`)}
338
+ extraClassName="form__group--md-33"
339
+ label={translations.TRAVELERS_FORM.BIRTHDATE}
340
+ required
341
+ name={`adults[${index}].birthDate`}
342
+ onChange={formik.handleChange}
343
+ onBlur={formik.handleBlur}
344
+ value={travelerValues.birthDate}
345
+ />
346
+ </div>
347
+ </div>
348
+ ))}
349
+ {formik.values.children.map((travelerValues, index) => (
350
+ <div className="form__region" key={travelerValues.id}>
351
+ <div className="form__region-header">
352
+ <h5 className="form__region-heading">
353
+ {translations.TRAVELERS_FORM.TRAVELER}{" "}
354
+ {formik.values.adults.length + index + 1}
355
+ </h5>
356
+ <p className="form__region-label">
357
+ {translations.TRAVELERS_FORM.CHILD}
358
+ </p>
359
+ </div>
360
+ <div className="form__row">
361
+ <div
362
+ className={buildClassName([
363
+ "form__group",
364
+ hasVisibleError(`children[${index}].gender`) &&
365
+ "form__group--error",
366
+ ])}
367
+ >
368
+ <label className="form__label">
369
+ {translations.TRAVELERS_FORM.GENDER} *
370
+ </label>
371
+ <div className="radiobutton-group">
372
+ <div className="radiobutton">
373
+ <label className="radiobutton__label">
374
+ <input
375
+ type="radio"
376
+ className="radiobutton__input"
377
+ name={`children[${index}].gender`}
378
+ onChange={formik.handleChange}
379
+ onBlur={formik.handleBlur}
380
+ value="m"
381
+ checked={travelerValues.gender === "m"}
382
+ />
383
+ {translations.TRAVELERS_FORM.MALE}
384
+ </label>
385
+ </div>
386
+
387
+ <div className="radiobutton">
388
+ <label className="radiobutton__label">
389
+ <input
390
+ type="radio"
391
+ className="radiobutton__input"
392
+ name={`children[${index}].gender`}
393
+ onChange={formik.handleChange}
394
+ onBlur={formik.handleBlur}
395
+ value="f"
396
+ checked={travelerValues.gender === "f"}
397
+ />
398
+ {translations.TRAVELERS_FORM.FEMALE}
399
+ </label>
400
+ </div>
401
+
402
+ <div className="radiobutton">
403
+ <label className="radiobutton__label">
404
+ <input
405
+ type="radio"
406
+ className="radiobutton__input"
407
+ name={`children[${index}].gender`}
408
+ onChange={formik.handleChange}
409
+ onBlur={formik.handleBlur}
410
+ value="x"
411
+ checked={travelerValues.gender === "x"}
412
+ />
413
+ {translations.TRAVELERS_FORM.OTHER}
414
+ </label>
415
+ </div>
416
+ </div>
417
+ </div>
418
+ </div>
419
+ <div className="form__row">
420
+ <LabeledInput
421
+ hasError={hasVisibleError(`children[${index}].firstName`)}
422
+ extraClassName="form__group--md-33"
423
+ label={translations.TRAVELERS_FORM.FIRST_NAME}
424
+ required
425
+ name={`children[${index}].firstName`}
426
+ onChange={formik.handleChange}
427
+ onBlur={formik.handleBlur}
428
+ value={travelerValues.firstName}
429
+ />
430
+ <LabeledInput
431
+ hasError={hasVisibleError(`children[${index}].lastName`)}
432
+ extraClassName="form__group--md-33"
433
+ label={translations.TRAVELERS_FORM.LAST_NAME}
434
+ required
435
+ name={`children[${index}].lastName`}
436
+ onChange={formik.handleChange}
437
+ onBlur={formik.handleBlur}
438
+ value={travelerValues.lastName}
439
+ />
440
+ <LabeledInput
441
+ type="date"
442
+ hasError={hasVisibleError(`children[${index}].birthDate`)}
443
+ extraClassName="form__group--md-33"
444
+ label={translations.TRAVELERS_FORM.BIRTHDATE}
445
+ required
446
+ name={`children[${index}].birthDate`}
447
+ onChange={formik.handleChange}
448
+ onBlur={formik.handleBlur}
449
+ value={travelerValues.birthDate}
450
+ />
451
+ </div>
452
+ </div>
453
+ ))}
454
+ <div className="form__region">
455
+ <div className="form__region-header">
456
+ <h5 className="form__region-heading">
457
+ {translations.TRAVELERS_FORM.MAIN_BOOKER}
458
+ </h5>
459
+ <p className="form__region-label">
460
+ {compact([
461
+ compact([mainBooker?.firstName, mainBooker?.lastName]).join(" "),
462
+ mainBooker?.birthDate &&
463
+ format(
464
+ parse(mainBooker.birthDate, "yyyy-MM-dd", new Date()),
465
+ "dd-MM-yyyy"
466
+ ),
467
+ ]).join(", ")}
468
+ </p>
469
+ </div>
470
+
471
+ <div className="form__twocolumn">
472
+ <div className="form__twocolumn-column">
473
+ <div className="form__row">
474
+ <LabeledInput
475
+ hasError={hasVisibleError("street")}
476
+ extraClassName="form__group--50 form__group--sm-60"
477
+ label={translations.TRAVELERS_FORM.STREET}
478
+ required
479
+ name="street"
480
+ onChange={formik.handleChange}
481
+ onBlur={formik.handleBlur}
482
+ placeholder={translations.TRAVELERS_FORM.STREET_PLACEHOLDER}
483
+ value={formik.values.street}
484
+ />
485
+ <LabeledInput
486
+ hasError={hasVisibleError("houseNumber")}
487
+ extraClassName="form__group--30 form__group--sm-20"
488
+ label={translations.TRAVELERS_FORM.HOUSE_NUMBER}
489
+ required
490
+ name="houseNumber"
491
+ onChange={formik.handleChange}
492
+ onBlur={formik.handleBlur}
493
+ value={formik.values.houseNumber}
494
+ />
495
+ <LabeledInput
496
+ hasError={hasVisibleError("box")}
497
+ extraClassName="form__group--20"
498
+ label={translations.TRAVELERS_FORM.POST_BOX}
499
+ name="box"
500
+ onChange={formik.handleChange}
501
+ onBlur={formik.handleBlur}
502
+ value={formik.values.box}
503
+ />
504
+ </div>
505
+ </div>
506
+
507
+ <div className="form__twocolumn-column">
508
+ <div className="form__row">
509
+ <LabeledInput
510
+ hasError={hasVisibleError("zipCode")}
511
+ extraClassName="form__group--40 form__group--sm-20"
512
+ label={translations.TRAVELERS_FORM.ZIPCODE}
513
+ required
514
+ name="zipCode"
515
+ onChange={formik.handleChange}
516
+ onBlur={formik.handleBlur}
517
+ value={formik.values.zipCode}
518
+ />
519
+ <LabeledInput
520
+ hasError={hasVisibleError("place")}
521
+ extraClassName="form__group--60 form__group--sm-40"
522
+ label={translations.TRAVELERS_FORM.CITY}
523
+ required
524
+ name="place"
525
+ placeholder={translations.TRAVELERS_FORM.CITY_PLACEHOLDER}
526
+ onChange={formik.handleChange}
527
+ onBlur={formik.handleBlur}
528
+ value={formik.values.place}
529
+ />
530
+ <LabeledSelect
531
+ hasError={hasVisibleError("country")}
532
+ extraClassName="form__group--sm-40"
533
+ label={translations.TRAVELERS_FORM.COUNTRY}
534
+ required
535
+ name="country"
536
+ onChange={formik.handleChange}
537
+ onBlur={formik.handleBlur}
538
+ value={formik.values.country}
539
+ options={[
540
+ {
541
+ key: "empty",
542
+ label: translations.TRAVELERS_FORM.SELECT_COUNTRY,
543
+ value: undefined,
544
+ },
545
+ {
546
+ key: "be",
547
+ value: "be",
548
+ label: "België",
549
+ },
550
+ {
551
+ key: "nl",
552
+ value: "nl",
553
+ label: "Nederland",
554
+ },
555
+ {
556
+ key: "fr",
557
+ value: "fr",
558
+ label: "Frankrijk",
559
+ },
560
+ ]}
561
+ />
562
+ </div>
563
+ </div>
564
+ </div>
565
+ <div className="form__row">
566
+ <LabeledInput
567
+ hasError={hasVisibleError("phone")}
568
+ extraClassName="form__group--md-33"
569
+ label={translations.TRAVELERS_FORM.PHONE}
570
+ required
571
+ name="phone"
572
+ onChange={formik.handleChange}
573
+ onBlur={formik.handleBlur}
574
+ value={formik.values.phone}
575
+ />
576
+ <LabeledInput
577
+ type="email"
578
+ hasError={hasVisibleError("email")}
579
+ extraClassName="form__group--md-33"
580
+ label={translations.TRAVELERS_FORM.EMAIL}
581
+ required
582
+ name="email"
583
+ onChange={formik.handleChange}
584
+ onBlur={formik.handleBlur}
585
+ value={formik.values.email}
586
+ />
587
+ <LabeledInput
588
+ type="email"
589
+ hasError={hasVisibleError("emailConfirmation")}
590
+ extraClassName="form__group--md-33"
591
+ label={translations.TRAVELERS_FORM.REPEAT_EMAIL}
592
+ required
593
+ name="emailConfirmation"
594
+ onChange={formik.handleChange}
595
+ onBlur={formik.handleBlur}
596
+ value={formik.values.emailConfirmation}
597
+ />
598
+ </div>
599
+ </div>
600
+ <div className="form__region">
601
+ <div className="form__region-header">
602
+ <h5 className="form__region-heading">
603
+ Ik wens te boeken bij mijn lokale reisagent
604
+ </h5>
605
+ <div className="checkbox" id="cbxChooseOffice">
606
+ <label className="checkbox__label">
607
+ <input
608
+ type="checkbox"
609
+ name="booking--mainbooker"
610
+ defaultChecked={showAgents}
611
+ onClick={() => toggleAgent(!showAgents)}
612
+ className="checkbox__input"
613
+ />
614
+ Ik kies een kantoor
615
+ </label>
616
+ </div>
617
+ </div>
618
+ {showAgents && (
619
+ <div className="form__row form__row--choose-office">
620
+ <div className="form__group form__group--icon">
621
+ <TypeAheadInput
622
+ value={formik.values.travelAgentName}
623
+ options={filteredAgents}
624
+ onChange={handleAgentChange}
625
+ onSelect={handleAgentSelect}
626
+ onClear={handleAgentClear}
627
+ name="travelAgentName"
628
+ placeholder="Kies uw reisagent"
629
+ />
630
+ </div>
631
+ </div>
632
+ )}
633
+ </div>
634
+ {Object.keys(flatErrors).length > 0 && (
635
+ <div className="form__region form__region--errors">
636
+ <div className="form__row">
637
+ <div className="form__group">
638
+ <p className="form__error-heading">
639
+ {translations.TRAVELERS_FORM.VALIDATION_MESSAGE}:
640
+ </p>
641
+ <ul className="list">
642
+ {Object.keys(flatErrors).map((key) => (
643
+ <li key={key}>{get(flatErrors, key)}</li>
644
+ ))}
645
+ </ul>
646
+ </div>
647
+ </div>
648
+ </div>
649
+ )}
650
+ <div className="booking__navigator">
651
+ <button type="submit" title={translations.STEPS.NEXT} className="cta">
652
+ {translations.STEPS.NEXT}
653
+ </button>
654
+ </div>
655
+ </form>
656
+ );
657
+ };
658
+
659
+ export default TravelersForm;