@qite/tide-booking-component 1.4.33 → 1.4.34

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,218 +1,215 @@
1
- import React, { useContext, useEffect, useMemo } from 'react';
2
- import { useDispatch, useSelector } from 'react-redux';
3
- import { QSMRootState } from '../../store/qsm-store';
4
- import QSMConfigurationContext from '../../qsm-configuration-context';
5
- import useMediaQuery from '../../../shared/utils/use-media-query-util';
6
- import MobileFilterModal from '../mobile-filter-modal';
7
- import SearchInputGroup from '../search-input-group';
8
- import DoubleSearchInputGroup from '../double-search-input-group';
9
- import Dates from '../../../booking-product/components/dates';
10
- import TravelInputGroup from '../travel-input-group';
11
- import TravelClassPicker from '../travel-class-picker';
12
- import TravelTypePicker from '../travel-type-picker';
13
- import Icon from '../icon';
14
- import TravelNationalityPicker from '../travel-nationality-picker';
15
- import { addDays, addMonths } from 'date-fns';
16
- import { DateRange } from '../../../booking-product/types';
17
- import { QSMState, setFromDate, setToDate } from '../../store/qsm-slice';
18
- import { FieldConfig } from '../../types';
19
-
20
- const QSMContainer: React.FC = () => {
21
- const dispatch = useDispatch();
22
- const isMobile = useMediaQuery('(max-width: 768px)');
23
- const qsmState = useSelector((state: QSMRootState) => state.qsm);
24
- const { mobileFilterType, fromDate, toDate } = qsmState;
25
- const { searchFields, askTravelers, submitIcon, submitLabel, onSubmit } = useContext(QSMConfigurationContext);
26
-
27
- useEffect(() => {
28
- if (fromDate || toDate) return;
29
-
30
- const startDate = addMonths(new Date(), 1);
31
- const endDate = addDays(startDate, 7);
32
-
33
- dispatch(setFromDate(startDate.toISOString()));
34
- dispatch(setToDate(endDate.toISOString()));
35
- }, [fromDate, toDate, dispatch]);
36
-
37
- const dateRange = useMemo<DateRange | undefined>(() => {
38
- if (!fromDate || !toDate) return undefined;
39
-
40
- return {
41
- fromDate: new Date(fromDate),
42
- toDate: new Date(toDate)
43
- };
44
- }, [fromDate, toDate]);
45
-
46
- const handleDateChange = (value: DateRange) => {
47
- dispatch(setFromDate(value.fromDate?.toISOString()));
48
- dispatch(setToDate(value.toDate?.toISOString()));
49
- };
50
-
51
- const handleSubmit = () => {
52
- if (!onSubmit) return;
53
-
54
- const { fromDate, toDate, travelers, selectedTravelClass, selectedTravelType, selectedNationality, adults, kids, babies, rooms, selectedFlexRange } =
55
- qsmState;
56
-
57
- const payload = {
58
- fromDate,
59
- toDate,
60
- travelers,
61
- travelClass: selectedTravelClass,
62
- travelType: selectedTravelType,
63
- nationality: selectedNationality,
64
- adults,
65
- kids,
66
- babies,
67
- rooms,
68
- dateFlexibility: selectedFlexRange
69
- };
70
-
71
- addSearchFieldsToPayload(payload, searchFields, qsmState);
72
- onSubmit(payload);
73
- };
74
-
75
- const addSearchFieldsToPayload = (payload: any, fields: FieldConfig[], state: QSMState) => {
76
- fields.forEach((field) => {
77
- if (field.type === 'single') {
78
- const key = field.fieldKey;
79
- const option = field.options.find((opt) => opt.value === state[key]);
80
- payload[key] = option ?? state[key];
81
- }
82
-
83
- if (field.type === 'double' && field.fields) {
84
- // recursively add each nested field
85
- field.fields.forEach((nestedField) => {
86
- const key = nestedField.fieldKey;
87
- const option = nestedField.options.find((opt) => opt.value === state[key]);
88
- payload[key] = option ?? state[key];
89
- });
90
- }
91
- });
92
- };
93
-
94
- return (
95
- <div className="qsm">
96
- <div className="qsm__content">
97
- <div className="qsm__tabs">
98
- <button type="button" className="qsm__tab">
99
- <span className="qsm__tab__icons">
100
- <Icon name="ui-location" height={16} />
101
- </span>
102
- Multidestination
103
- </button>
104
- <button type="button" className="qsm__tab">
105
- <span className="qsm__tab__icons">
106
- <Icon name="ui-suitcase" height={16} />
107
- </span>
108
- Packages
109
- </button>
110
- <button type="button" className="qsm__tab qsm__tab--active">
111
- <span className="qsm__tab__icons">
112
- <Icon name="ui-backforward" height={14} />
113
- +
114
- <Icon name="ui-bed" height={14} />
115
- </span>
116
- Transport + hotel
117
- </button>
118
- <button type="button" className="qsm__tab">
119
- <span className="qsm__tab__icons">
120
- <Icon name="ui-bed" height={16} />
121
- </span>
122
- Accommodation
123
- </button>
124
- <button type="button" className="qsm__tab">
125
- <span className="qsm__tab__icons">
126
- <Icon name="ui-flight" height={16} />
127
- </span>
128
- Transports
129
- </button>
130
- <button type="button" className="qsm__tab">
131
- <span className="qsm__tab__icons">
132
- <Icon name="ui-ticket" height={16} />
133
- </span>
134
- Ticket Only
135
- </button>
136
- <button type="button" className="qsm__tab">
137
- <span className="qsm__tab__icons">
138
- <Icon name="ui-car" height={16} />
139
- </span>
140
- Rent a car
141
- </button>
142
- <button type="button" className="qsm__tab">
143
- <span className="qsm__tab__icons">
144
- <Icon name="ui-backforward" height={16} />
145
- </span>
146
- Transfers
147
- </button>
148
- <button type="button" className="qsm__tab">
149
- <span className="qsm__tab__icons">
150
- <Icon name="ui-ship" height={16} />
151
- </span>
152
- Cruises
153
- </button>
154
- </div>
155
- <div className="qsm__filter">
156
- <div className="radiobutton-group qsm__filter__inputgroup">
157
- <div className="radiobutton">
158
- <label className="radiobutton__label">
159
- <input
160
- type="radio"
161
- name="mainBookerId"
162
- // onChange={handleMainBookerChange}
163
- // onBlur={formik.handleBlur}
164
- value=""
165
- // checked={formik.values.mainBookerId === travelerValues.id}
166
- checked={true}
167
- readOnly
168
- className="radiobutton__input"
169
- />
170
- <span>One accommodation</span>
171
- </label>
172
- </div>
173
- <div className="radiobutton">
174
- <label className="radiobutton__label">
175
- <input
176
- type="radio"
177
- name="mainBookerId"
178
- // onChange={handleMainBookerChange}
179
- // onBlur={formik.handleBlur}
180
- value=""
181
- // checked={formik.values.mainBookerId === travelerValues.id}
182
- className="radiobutton__input"
183
- disabled={true}
184
- />
185
- <span>Multiple accommodations</span>
186
- </label>
187
- </div>
188
- </div>
189
- <div className="qsm__filter__classgroup">
190
- <TravelClassPicker />
191
- <TravelTypePicker />
192
- <TravelNationalityPicker />
193
- </div>
194
- </div>
195
- <div className="qsm__input-group">
196
- {searchFields.map((field, idx) => {
197
- if (field.type === 'double') {
198
- return <DoubleSearchInputGroup key={idx} fieldKey={field.fieldKey} showReverse={field.showReverse} />;
199
- }
200
- return <SearchInputGroup key={idx} fieldKey={field.fieldKey} />;
201
- })}
202
-
203
- <Dates value={dateRange} onChange={handleDateChange} />
204
-
205
- {askTravelers && <TravelInputGroup />}
206
-
207
- <button type="button" className="cta" onClick={handleSubmit}>
208
- {submitIcon && submitIcon.toString().length > 0 && <span>{submitIcon}</span>}
209
- <span>{submitLabel}</span>
210
- </button>
211
- </div>
212
- </div>
213
- {isMobile && mobileFilterType && <MobileFilterModal />}
214
- </div>
215
- );
216
- };
217
-
218
- export default QSMContainer;
1
+ import React, { useContext, useEffect, useMemo } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { QSMRootState } from '../../store/qsm-store';
4
+ import QSMConfigurationContext from '../../qsm-configuration-context';
5
+ import useMediaQuery from '../../../shared/utils/use-media-query-util';
6
+ import MobileFilterModal from '../mobile-filter-modal';
7
+ import SearchInputGroup from '../search-input-group';
8
+ import DoubleSearchInputGroup from '../double-search-input-group';
9
+ import Dates from '../../../booking-product/components/dates';
10
+ import TravelInputGroup from '../travel-input-group';
11
+ import TravelClassPicker from '../travel-class-picker';
12
+ import TravelTypePicker from '../travel-type-picker';
13
+ import Icon from '../icon';
14
+ import TravelNationalityPicker from '../travel-nationality-picker';
15
+ import { addDays, addMonths } from 'date-fns';
16
+ import { DateRange } from '../../../booking-product/types';
17
+ import { QSMState, setFromDate, setToDate } from '../../store/qsm-slice';
18
+ import { FieldConfig } from '../../types';
19
+
20
+ const QSMContainer: React.FC = () => {
21
+ const dispatch = useDispatch();
22
+ const isMobile = useMediaQuery('(max-width: 768px)');
23
+ const qsmState = useSelector((state: QSMRootState) => state.qsm);
24
+ const { mobileFilterType, fromDate, toDate } = qsmState;
25
+ const { searchFields, askTravelers, submitIcon, submitLabel, onSubmit, travelTypes } = useContext(QSMConfigurationContext);
26
+
27
+ useEffect(() => {
28
+ if (fromDate || toDate) return;
29
+
30
+ const startDate = addMonths(new Date(), 1);
31
+ const endDate = addDays(startDate, 7);
32
+
33
+ dispatch(setFromDate(startDate.toISOString()));
34
+ dispatch(setToDate(endDate.toISOString()));
35
+ }, [fromDate, toDate, dispatch]);
36
+
37
+ const dateRange = useMemo<DateRange | undefined>(() => {
38
+ if (!fromDate || !toDate) return undefined;
39
+
40
+ return {
41
+ fromDate: new Date(fromDate),
42
+ toDate: new Date(toDate)
43
+ };
44
+ }, [fromDate, toDate]);
45
+
46
+ const handleDateChange = (value: DateRange) => {
47
+ dispatch(setFromDate(value.fromDate?.toISOString()));
48
+ dispatch(setToDate(value.toDate?.toISOString()));
49
+ };
50
+
51
+ const handleSubmit = () => {
52
+ if (!onSubmit) return;
53
+
54
+ const { fromDate, toDate, travelers, selectedTravelClass, selectedTravelType, selectedNationality, adults, kids, babies, rooms, selectedFlexRange } =
55
+ qsmState;
56
+
57
+ const selectedTravelTypeValue = travelTypes.find((t) => t.label === selectedTravelType);
58
+
59
+ const payload = {
60
+ fromDate,
61
+ toDate,
62
+ travelClass: selectedTravelClass,
63
+ travelType: selectedTravelTypeValue,
64
+ nationality: selectedNationality,
65
+ rooms
66
+ };
67
+
68
+ addSearchFieldsToPayload(payload, searchFields, qsmState);
69
+ onSubmit(payload);
70
+ };
71
+
72
+ const addSearchFieldsToPayload = (payload: any, fields: FieldConfig[], state: QSMState) => {
73
+ fields.forEach((field) => {
74
+ if (field.type === 'single') {
75
+ const key = field.fieldKey;
76
+ const option = field.options.find((opt) => opt.value === state[key]);
77
+ payload[key] = option ?? state[key];
78
+ }
79
+
80
+ if (field.type === 'double' && field.fields) {
81
+ // recursively add each nested field
82
+ field.fields.forEach((nestedField) => {
83
+ const key = nestedField.fieldKey;
84
+ const option = nestedField.options.find((opt) => opt.value === state[key]);
85
+ payload[key] = option ?? state[key];
86
+ });
87
+ }
88
+ });
89
+ };
90
+
91
+ return (
92
+ <div className="qsm">
93
+ <div className="qsm__content">
94
+ <div className="qsm__tabs">
95
+ <button type="button" className="qsm__tab">
96
+ <span className="qsm__tab__icons">
97
+ <Icon name="ui-location" height={16} />
98
+ </span>
99
+ Multidestination
100
+ </button>
101
+ <button type="button" className="qsm__tab">
102
+ <span className="qsm__tab__icons">
103
+ <Icon name="ui-suitcase" height={16} />
104
+ </span>
105
+ Packages
106
+ </button>
107
+ <button type="button" className="qsm__tab qsm__tab--active">
108
+ <span className="qsm__tab__icons">
109
+ <Icon name="ui-backforward" height={14} />
110
+ +
111
+ <Icon name="ui-bed" height={14} />
112
+ </span>
113
+ Transport + hotel
114
+ </button>
115
+ <button type="button" className="qsm__tab">
116
+ <span className="qsm__tab__icons">
117
+ <Icon name="ui-bed" height={16} />
118
+ </span>
119
+ Accommodation
120
+ </button>
121
+ <button type="button" className="qsm__tab">
122
+ <span className="qsm__tab__icons">
123
+ <Icon name="ui-flight" height={16} />
124
+ </span>
125
+ Transports
126
+ </button>
127
+ <button type="button" className="qsm__tab">
128
+ <span className="qsm__tab__icons">
129
+ <Icon name="ui-ticket" height={16} />
130
+ </span>
131
+ Ticket Only
132
+ </button>
133
+ <button type="button" className="qsm__tab">
134
+ <span className="qsm__tab__icons">
135
+ <Icon name="ui-car" height={16} />
136
+ </span>
137
+ Rent a car
138
+ </button>
139
+ <button type="button" className="qsm__tab">
140
+ <span className="qsm__tab__icons">
141
+ <Icon name="ui-backforward" height={16} />
142
+ </span>
143
+ Transfers
144
+ </button>
145
+ <button type="button" className="qsm__tab">
146
+ <span className="qsm__tab__icons">
147
+ <Icon name="ui-ship" height={16} />
148
+ </span>
149
+ Cruises
150
+ </button>
151
+ </div>
152
+ <div className="qsm__filter">
153
+ <div className="radiobutton-group qsm__filter__inputgroup">
154
+ <div className="radiobutton">
155
+ <label className="radiobutton__label">
156
+ <input
157
+ type="radio"
158
+ name="mainBookerId"
159
+ // onChange={handleMainBookerChange}
160
+ // onBlur={formik.handleBlur}
161
+ value=""
162
+ // checked={formik.values.mainBookerId === travelerValues.id}
163
+ checked={true}
164
+ readOnly
165
+ className="radiobutton__input"
166
+ />
167
+ <span>One accommodation</span>
168
+ </label>
169
+ </div>
170
+ <div className="radiobutton">
171
+ <label className="radiobutton__label">
172
+ <input
173
+ type="radio"
174
+ name="mainBookerId"
175
+ // onChange={handleMainBookerChange}
176
+ // onBlur={formik.handleBlur}
177
+ value=""
178
+ // checked={formik.values.mainBookerId === travelerValues.id}
179
+ className="radiobutton__input"
180
+ disabled={true}
181
+ />
182
+ <span>Multiple accommodations</span>
183
+ </label>
184
+ </div>
185
+ </div>
186
+ <div className="qsm__filter__classgroup">
187
+ <TravelClassPicker />
188
+ <TravelTypePicker />
189
+ <TravelNationalityPicker />
190
+ </div>
191
+ </div>
192
+ <div className="qsm__input-group">
193
+ {searchFields.map((field, idx) => {
194
+ if (field.type === 'double') {
195
+ return <DoubleSearchInputGroup key={idx} fieldKey={field.fieldKey} showReverse={field.showReverse} />;
196
+ }
197
+ return <SearchInputGroup key={idx} fieldKey={field.fieldKey} />;
198
+ })}
199
+
200
+ <Dates value={dateRange} onChange={handleDateChange} />
201
+
202
+ {askTravelers && <TravelInputGroup />}
203
+
204
+ <button type="button" className="cta" onClick={handleSubmit}>
205
+ {submitIcon && submitIcon.toString().length > 0 && <span>{submitIcon}</span>}
206
+ <span>{submitLabel}</span>
207
+ </button>
208
+ </div>
209
+ </div>
210
+ {isMobile && mobileFilterType && <MobileFilterModal />}
211
+ </div>
212
+ );
213
+ };
214
+
215
+ export default QSMContainer;