@qite/tide-booking-component 1.4.93 → 1.4.95
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.
- package/.prettierrc +9 -9
- package/.vs/ProjectSettings.json +3 -3
- package/.vs/VSWorkspaceState.json +5 -5
- package/build/build-cjs/index.js +81 -27
- package/build/build-cjs/src/booking-wizard/features/booking/booking-slice.d.ts +2 -1
- package/build/build-cjs/src/booking-wizard/features/booking/selectors.d.ts +4 -3
- package/build/build-cjs/src/booking-wizard/features/price-details/price-details-slice.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/price-details/selectors.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/types.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +81 -27
- package/build/build-esm/src/booking-wizard/features/booking/booking-slice.d.ts +2 -1
- package/build/build-esm/src/booking-wizard/features/booking/selectors.d.ts +4 -3
- package/build/build-esm/src/booking-wizard/features/price-details/price-details-slice.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/price-details/selectors.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/types.d.ts +1 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +83 -83
- package/src/booking-product/components/age-select.tsx +35 -35
- package/src/booking-product/components/amount-input.tsx +51 -51
- package/src/booking-product/components/date-range-picker/calendar.tsx +155 -155
- package/src/booking-product/components/footer.tsx +54 -54
- package/src/booking-product/components/header.tsx +57 -57
- package/src/booking-product/components/icon.tsx +200 -200
- package/src/booking-product/components/list-view.tsx +54 -54
- package/src/booking-product/components/rating.tsx +21 -21
- package/src/booking-product/components/rooms.tsx +171 -171
- package/src/booking-product/constants.ts +1 -1
- package/src/booking-product/index.tsx +21 -21
- package/src/booking-product/settings-context.ts +16 -16
- package/src/booking-product/types.ts +30 -30
- package/src/booking-product/utils/api.ts +26 -26
- package/src/booking-product/utils/price.ts +28 -28
- package/src/booking-wizard/api-settings-slice.ts +24 -24
- package/src/booking-wizard/components/icon.tsx +398 -398
- package/src/booking-wizard/components/labeled-input.tsx +56 -56
- package/src/booking-wizard/components/labeled-select.tsx +54 -54
- package/src/booking-wizard/components/message.tsx +21 -21
- package/src/booking-wizard/components/multi-range-filter.tsx +99 -99
- package/src/booking-wizard/components/phone-input.tsx +146 -146
- package/src/booking-wizard/components/print-offer-button.tsx +53 -53
- package/src/booking-wizard/components/product-card.tsx +23 -23
- package/src/booking-wizard/declarations.d.ts +4 -4
- package/src/booking-wizard/features/booking/booking-self-contained.tsx +16 -1
- package/src/booking-wizard/features/booking/booking-slice.ts +9 -1
- package/src/booking-wizard/features/booking/booking.tsx +16 -1
- package/src/booking-wizard/features/booking/selectors.ts +5 -0
- package/src/booking-wizard/features/flight-options/flight-filter.tsx +371 -371
- package/src/booking-wizard/features/flight-options/flight-option-flight.tsx +354 -354
- package/src/booking-wizard/features/flight-options/flight-option-modal.tsx +211 -211
- package/src/booking-wizard/features/flight-options/flight-option.tsx +57 -57
- package/src/booking-wizard/features/flight-options/flight-utils.ts +423 -423
- package/src/booking-wizard/features/price-details/price-details-api.ts +20 -20
- package/src/booking-wizard/features/price-details/price-details-slice.ts +2 -0
- package/src/booking-wizard/features/price-details/selectors.ts +1 -0
- package/src/booking-wizard/features/price-details/util.ts +115 -115
- package/src/booking-wizard/features/product-options/no-options.tsx +18 -18
- package/src/booking-wizard/features/product-options/none-option.tsx +73 -73
- package/src/booking-wizard/features/product-options/option-booking-airline-group.tsx +53 -53
- package/src/booking-wizard/features/product-options/option-booking-group.tsx +152 -152
- package/src/booking-wizard/features/product-options/option-item.tsx +236 -236
- package/src/booking-wizard/features/product-options/option-pax-card.tsx +159 -159
- package/src/booking-wizard/features/product-options/option-pax-group.tsx +122 -122
- package/src/booking-wizard/features/product-options/option-room.tsx +226 -226
- package/src/booking-wizard/features/product-options/option-unit-group.tsx +138 -138
- package/src/booking-wizard/features/room-options/room-utils.ts +154 -154
- package/src/booking-wizard/features/room-options/room.tsx +123 -123
- package/src/booking-wizard/features/room-options/traveler-rooms.tsx +64 -64
- package/src/booking-wizard/features/sidebar/index.tsx +2 -0
- package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +66 -66
- package/src/booking-wizard/features/sidebar/sidebar.tsx +17 -1
- package/src/booking-wizard/features/summary/summary-booking-option-pax.tsx +23 -23
- package/src/booking-wizard/features/summary/summary-booking-option-unit.tsx +23 -23
- package/src/booking-wizard/features/summary/summary-flight.tsx +36 -36
- package/src/booking-wizard/features/summary/summary-per-booking-option-group.tsx +60 -60
- package/src/booking-wizard/features/summary/summary-per-pax-option-group.tsx +56 -56
- package/src/booking-wizard/features/summary/summary-per-unit-option-group.tsx +58 -58
- package/src/booking-wizard/features/summary/summary-slice.ts +27 -27
- package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +157 -157
- package/src/booking-wizard/features/travelers-form/travelers-form-util.ts +10 -10
- package/src/booking-wizard/features/travelers-form/type-ahead-input.tsx +85 -85
- package/src/booking-wizard/features/travelers-form/validate-form.ts +178 -178
- package/src/booking-wizard/index.tsx +27 -27
- package/src/booking-wizard/store.ts +26 -26
- package/src/booking-wizard/types.ts +1 -0
- package/src/booking-wizard/use-offer-printer.ts +108 -108
- package/src/content/components/LanguageSwitcher.tsx +158 -158
- package/src/content/components/accordion.tsx +30 -30
- package/src/content/components/contact.tsx +211 -211
- package/src/content/components/personal-contact-form.tsx +809 -809
- package/src/content/header/index.tsx +43 -43
- package/src/content/header/types.ts +26 -26
- package/src/qsm/components/date-picker/index.tsx +152 -152
- package/src/qsm/components/date-range-picker/calendar-day.tsx +49 -49
- package/src/qsm/components/date-range-picker/calendar.tsx +211 -211
- package/src/qsm/components/date-range-picker/index.tsx +404 -404
- package/src/qsm/index.tsx +26 -26
- package/src/qsm/store/qsm-store.ts +13 -13
- package/src/search-results/components/flight/flight-card.tsx +38 -38
- package/src/search-results/components/flight/flight-leg.tsx +61 -61
- package/src/search-results/components/flight/flight-path.tsx +23 -23
- package/src/search-results/components/multi-range-filter.tsx +104 -104
- package/src/search-results/components/search-results-container/search-results-container.tsx +2 -2
- package/src/search-results/index.tsx +24 -24
- package/src/search-results/search-results-configuration-context.ts +6 -6
- package/src/search-results/store/search-results-store.ts +13 -13
- package/src/shared/components/loader.tsx +16 -16
- package/src/shared/translations/ar-SA.json +2 -1
- package/src/shared/translations/da-DK.json +2 -1
- package/src/shared/translations/de-DE.json +2 -1
- package/src/shared/translations/en-GB.json +2 -1
- package/src/shared/translations/es-ES.json +2 -1
- package/src/shared/translations/fr-BE.json +2 -1
- package/src/shared/translations/fr-FR.json +2 -1
- package/src/shared/translations/is-IS.json +2 -1
- package/src/shared/translations/it-IT.json +2 -1
- package/src/shared/translations/ja-JP.json +2 -1
- package/src/shared/translations/nl-BE.json +2 -1
- package/src/shared/translations/nl-NL.json +2 -1
- package/src/shared/translations/no-NO.json +2 -1
- package/src/shared/translations/pl-PL.json +2 -1
- package/src/shared/translations/pt-PT.json +2 -1
- package/src/shared/translations/sv-SE.json +2 -1
- package/src/shared/utils/class-util.ts +7 -7
- package/src/shared/utils/query-string-util.ts +91 -91
- package/src/shared/utils/tide-api-utils.ts +34 -34
- package/src/shared/utils/use-media-query-util.ts +19 -19
- package/styles/abstracts/_mixins.scss +74 -74
- package/styles/abstracts/_variables.scss +57 -57
- package/styles/base/_fonts.scss +2 -2
- package/styles/base/_normalize.scss +227 -227
- package/styles/base/_typography.scss +35 -35
- package/styles/booking-joker-variables.scss +596 -596
- package/styles/booking-product-variables.scss +330 -330
- package/styles/booking-product.scss +438 -438
- package/styles/booking-qsm-variables.scss +501 -501
- package/styles/booking-qsm.scss +52 -52
- package/styles/booking-wizard-variables.scss +603 -603
- package/styles/booking-wizard.scss +61 -61
- package/styles/components/_accordion.scss +67 -67
- package/styles/components/_animations.scss +39 -39
- package/styles/components/_base.scss +107 -107
- package/styles/components/_breadcrumb.scss +92 -92
- package/styles/components/_button.scss +238 -238
- package/styles/components/_checkbox.scss +230 -230
- package/styles/components/_contact.scss +239 -239
- package/styles/components/_cta.scss +238 -238
- package/styles/components/_date-list.scss +41 -41
- package/styles/components/_date-range-picker.scss +223 -223
- package/styles/components/_decrement-increment.scss +35 -35
- package/styles/components/_dropdown.scss +72 -72
- package/styles/components/_faq.scss +27 -27
- package/styles/components/_flight-option.scss +1419 -1419
- package/styles/components/_gallery.scss +314 -314
- package/styles/components/_header.scss +113 -113
- package/styles/components/_img-slider.scss +175 -175
- package/styles/components/_info-message.scss +75 -75
- package/styles/components/_input.scss +35 -35
- package/styles/components/_list.scss +185 -185
- package/styles/components/_loader.scss +70 -70
- package/styles/components/_mixins.scss +579 -579
- package/styles/components/_passenger-picker.scss +306 -306
- package/styles/components/_phone-input.scss +8 -8
- package/styles/components/_placeholders.scss +165 -165
- package/styles/components/_qsm.scss +17 -17
- package/styles/components/_radiobutton.scss +170 -170
- package/styles/components/_select-wrapper.scss +76 -76
- package/styles/components/_slider.scss +128 -128
- package/styles/components/_spinner.scss +29 -29
- package/styles/components/_step-indicators.scss +161 -161
- package/styles/components/_table.scss +81 -81
- package/styles/components/_typeahead.scss +275 -275
- package/styles/components/_variables.scss +89 -89
- package/styles/content-blocks-variables.scss +507 -507
- package/styles/font.scss +2 -2
- package/styles/qsm/_calendar.scss +274 -274
- package/styles/qsm/_qsm.scss +1094 -1094
- package/styles/search.scss +1200 -1200
- package/tsconfig.json +24 -24
|
@@ -1,146 +1,146 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { buildClassName } from '../../shared/utils/class-util';
|
|
3
|
-
import { compact } from 'lodash';
|
|
4
|
-
import { Country } from '../types';
|
|
5
|
-
|
|
6
|
-
interface PhoneInputProps {
|
|
7
|
-
name: string;
|
|
8
|
-
required?: boolean;
|
|
9
|
-
value?: string | number;
|
|
10
|
-
label?: string;
|
|
11
|
-
placeholder?: string;
|
|
12
|
-
hasError?: boolean;
|
|
13
|
-
extraClassName?: string;
|
|
14
|
-
countries: Country[];
|
|
15
|
-
countryIso2?: string; // Initial country selection (ISO2 code)
|
|
16
|
-
onChange?: React.ChangeEventHandler<HTMLElement>;
|
|
17
|
-
onBlur?: React.FocusEventHandler<HTMLElement>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const normalize = (v?: string | number) => (v == null ? '' : String(v));
|
|
21
|
-
|
|
22
|
-
const parseCombined = (raw: string, countries: Country[]): { prefix: string; number: string } => {
|
|
23
|
-
const value = normalize(raw).trim();
|
|
24
|
-
if (!value) return { prefix: '', number: '' };
|
|
25
|
-
|
|
26
|
-
// Try to match a known prefix at the start. Prefer longest match.
|
|
27
|
-
const sorted = [...countries].sort((a, b) => b.phonePrefix.length - a.phonePrefix.length);
|
|
28
|
-
const hit = sorted.find((c) => value.startsWith(c.phonePrefix));
|
|
29
|
-
if (hit) {
|
|
30
|
-
const rest = value
|
|
31
|
-
.slice(hit.phonePrefix.length)
|
|
32
|
-
.trim()
|
|
33
|
-
.replace(/^[-\s]+/, '');
|
|
34
|
-
return { prefix: hit.phonePrefix, number: rest };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Fallback: split on first space if it looks like a +prefix number
|
|
38
|
-
const m = value.match(/^(\+\d{1,4})[\s-]*(.*)$/);
|
|
39
|
-
if (m) return { prefix: m[1], number: m[2] };
|
|
40
|
-
|
|
41
|
-
return { prefix: '', number: value };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const PhoneInput: React.FC<PhoneInputProps> = ({
|
|
45
|
-
name,
|
|
46
|
-
required,
|
|
47
|
-
value,
|
|
48
|
-
label,
|
|
49
|
-
placeholder,
|
|
50
|
-
extraClassName,
|
|
51
|
-
hasError,
|
|
52
|
-
countries,
|
|
53
|
-
countryIso2,
|
|
54
|
-
onChange,
|
|
55
|
-
onBlur
|
|
56
|
-
}) => {
|
|
57
|
-
// Derive initial state from `value`
|
|
58
|
-
const initialCountry = countries.find((c) => c.iso2 === countryIso2);
|
|
59
|
-
|
|
60
|
-
const initial = useMemo(() => parseCombined(normalize(value), countries), [value, countries]);
|
|
61
|
-
const [prefix, setPrefix] = useState<string>(initial.prefix);
|
|
62
|
-
const [number, setNumber] = useState<string>(initial.number);
|
|
63
|
-
|
|
64
|
-
// Keep state in sync if the parent changes `value`
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const parsed = !prefix && !number && initialCountry ? { prefix: initialCountry.phonePrefix, number: '' } : parseCombined(normalize(value), countries);
|
|
67
|
-
if (parsed.prefix) setPrefix(parsed.prefix);
|
|
68
|
-
if (parsed.number) setNumber(parsed.number);
|
|
69
|
-
}, [value, countries, countryIso2]);
|
|
70
|
-
|
|
71
|
-
const emitCombinedChange = (e: React.ChangeEvent<HTMLElement>, nextPrefix: string, nextNumber: string) => {
|
|
72
|
-
// Combine with a space, unless the number already starts with a dash or space
|
|
73
|
-
const combined = nextPrefix && nextNumber ? compact([nextPrefix, nextNumber]).join(' ') : null;
|
|
74
|
-
onChange?.({
|
|
75
|
-
...e,
|
|
76
|
-
type: 'change',
|
|
77
|
-
target: { name, value: combined },
|
|
78
|
-
currentTarget: { name, value: combined }
|
|
79
|
-
} as any);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const handleCountryBlur = (e: React.FocusEvent<HTMLSelectElement>) => {
|
|
83
|
-
onBlur?.(e);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const onCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
87
|
-
const next = e.target.value;
|
|
88
|
-
setPrefix(next);
|
|
89
|
-
emitCombinedChange(e, next, number);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const onPhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
93
|
-
const cleaned = e.target.value.replace(/[^\d\s-]/g, '');
|
|
94
|
-
setNumber(cleaned);
|
|
95
|
-
emitCombinedChange(e, prefix, cleaned);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const handleNumberBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
99
|
-
onBlur?.(e);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// Ensure the select shows a reasonable default when prefix is empty
|
|
103
|
-
const selectValue = prefix || '';
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<label className={buildClassName(['form__group', extraClassName, hasError && 'form__group--error'])}>
|
|
107
|
-
{label && <span className="form__label">{compact([label, required && '*']).join(' ')}</span>}
|
|
108
|
-
|
|
109
|
-
<div className="phone-input">
|
|
110
|
-
<div className="dropdown">
|
|
111
|
-
<select
|
|
112
|
-
aria-label={label ? `${label} – country code` : 'Country calling code'}
|
|
113
|
-
name={`${name}__country`}
|
|
114
|
-
value={selectValue}
|
|
115
|
-
onBlur={handleCountryBlur}
|
|
116
|
-
onChange={onCountryChange}>
|
|
117
|
-
<option value="" disabled></option>
|
|
118
|
-
{countries?.map((option) => (
|
|
119
|
-
<option key={option.iso2} value={option.phonePrefix}>
|
|
120
|
-
{option.name} ({option.phonePrefix})
|
|
121
|
-
</option>
|
|
122
|
-
))}
|
|
123
|
-
</select>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<input
|
|
127
|
-
aria-label={label ? `${label} – number` : 'Phone number'}
|
|
128
|
-
name={`${name}__number`}
|
|
129
|
-
type="tel"
|
|
130
|
-
inputMode="tel"
|
|
131
|
-
required={required}
|
|
132
|
-
className="form__input"
|
|
133
|
-
placeholder={placeholder}
|
|
134
|
-
onChange={onPhoneChange}
|
|
135
|
-
onBlur={handleNumberBlur}
|
|
136
|
-
value={number}
|
|
137
|
-
/>
|
|
138
|
-
|
|
139
|
-
{/* Hidden merged value to integrate with forms that expect a single field */}
|
|
140
|
-
<input type="hidden" name={name} value={compact([prefix, number]).join(' ')} />
|
|
141
|
-
</div>
|
|
142
|
-
</label>
|
|
143
|
-
);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export default PhoneInput;
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { buildClassName } from '../../shared/utils/class-util';
|
|
3
|
+
import { compact } from 'lodash';
|
|
4
|
+
import { Country } from '../types';
|
|
5
|
+
|
|
6
|
+
interface PhoneInputProps {
|
|
7
|
+
name: string;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
value?: string | number;
|
|
10
|
+
label?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
hasError?: boolean;
|
|
13
|
+
extraClassName?: string;
|
|
14
|
+
countries: Country[];
|
|
15
|
+
countryIso2?: string; // Initial country selection (ISO2 code)
|
|
16
|
+
onChange?: React.ChangeEventHandler<HTMLElement>;
|
|
17
|
+
onBlur?: React.FocusEventHandler<HTMLElement>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalize = (v?: string | number) => (v == null ? '' : String(v));
|
|
21
|
+
|
|
22
|
+
const parseCombined = (raw: string, countries: Country[]): { prefix: string; number: string } => {
|
|
23
|
+
const value = normalize(raw).trim();
|
|
24
|
+
if (!value) return { prefix: '', number: '' };
|
|
25
|
+
|
|
26
|
+
// Try to match a known prefix at the start. Prefer longest match.
|
|
27
|
+
const sorted = [...countries].sort((a, b) => b.phonePrefix.length - a.phonePrefix.length);
|
|
28
|
+
const hit = sorted.find((c) => value.startsWith(c.phonePrefix));
|
|
29
|
+
if (hit) {
|
|
30
|
+
const rest = value
|
|
31
|
+
.slice(hit.phonePrefix.length)
|
|
32
|
+
.trim()
|
|
33
|
+
.replace(/^[-\s]+/, '');
|
|
34
|
+
return { prefix: hit.phonePrefix, number: rest };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fallback: split on first space if it looks like a +prefix number
|
|
38
|
+
const m = value.match(/^(\+\d{1,4})[\s-]*(.*)$/);
|
|
39
|
+
if (m) return { prefix: m[1], number: m[2] };
|
|
40
|
+
|
|
41
|
+
return { prefix: '', number: value };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const PhoneInput: React.FC<PhoneInputProps> = ({
|
|
45
|
+
name,
|
|
46
|
+
required,
|
|
47
|
+
value,
|
|
48
|
+
label,
|
|
49
|
+
placeholder,
|
|
50
|
+
extraClassName,
|
|
51
|
+
hasError,
|
|
52
|
+
countries,
|
|
53
|
+
countryIso2,
|
|
54
|
+
onChange,
|
|
55
|
+
onBlur
|
|
56
|
+
}) => {
|
|
57
|
+
// Derive initial state from `value`
|
|
58
|
+
const initialCountry = countries.find((c) => c.iso2 === countryIso2);
|
|
59
|
+
|
|
60
|
+
const initial = useMemo(() => parseCombined(normalize(value), countries), [value, countries]);
|
|
61
|
+
const [prefix, setPrefix] = useState<string>(initial.prefix);
|
|
62
|
+
const [number, setNumber] = useState<string>(initial.number);
|
|
63
|
+
|
|
64
|
+
// Keep state in sync if the parent changes `value`
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const parsed = !prefix && !number && initialCountry ? { prefix: initialCountry.phonePrefix, number: '' } : parseCombined(normalize(value), countries);
|
|
67
|
+
if (parsed.prefix) setPrefix(parsed.prefix);
|
|
68
|
+
if (parsed.number) setNumber(parsed.number);
|
|
69
|
+
}, [value, countries, countryIso2]);
|
|
70
|
+
|
|
71
|
+
const emitCombinedChange = (e: React.ChangeEvent<HTMLElement>, nextPrefix: string, nextNumber: string) => {
|
|
72
|
+
// Combine with a space, unless the number already starts with a dash or space
|
|
73
|
+
const combined = nextPrefix && nextNumber ? compact([nextPrefix, nextNumber]).join(' ') : null;
|
|
74
|
+
onChange?.({
|
|
75
|
+
...e,
|
|
76
|
+
type: 'change',
|
|
77
|
+
target: { name, value: combined },
|
|
78
|
+
currentTarget: { name, value: combined }
|
|
79
|
+
} as any);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleCountryBlur = (e: React.FocusEvent<HTMLSelectElement>) => {
|
|
83
|
+
onBlur?.(e);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const onCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
87
|
+
const next = e.target.value;
|
|
88
|
+
setPrefix(next);
|
|
89
|
+
emitCombinedChange(e, next, number);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onPhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
93
|
+
const cleaned = e.target.value.replace(/[^\d\s-]/g, '');
|
|
94
|
+
setNumber(cleaned);
|
|
95
|
+
emitCombinedChange(e, prefix, cleaned);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleNumberBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
99
|
+
onBlur?.(e);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Ensure the select shows a reasonable default when prefix is empty
|
|
103
|
+
const selectValue = prefix || '';
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<label className={buildClassName(['form__group', extraClassName, hasError && 'form__group--error'])}>
|
|
107
|
+
{label && <span className="form__label">{compact([label, required && '*']).join(' ')}</span>}
|
|
108
|
+
|
|
109
|
+
<div className="phone-input">
|
|
110
|
+
<div className="dropdown">
|
|
111
|
+
<select
|
|
112
|
+
aria-label={label ? `${label} – country code` : 'Country calling code'}
|
|
113
|
+
name={`${name}__country`}
|
|
114
|
+
value={selectValue}
|
|
115
|
+
onBlur={handleCountryBlur}
|
|
116
|
+
onChange={onCountryChange}>
|
|
117
|
+
<option value="" disabled></option>
|
|
118
|
+
{countries?.map((option) => (
|
|
119
|
+
<option key={option.iso2} value={option.phonePrefix}>
|
|
120
|
+
{option.name} ({option.phonePrefix})
|
|
121
|
+
</option>
|
|
122
|
+
))}
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<input
|
|
127
|
+
aria-label={label ? `${label} – number` : 'Phone number'}
|
|
128
|
+
name={`${name}__number`}
|
|
129
|
+
type="tel"
|
|
130
|
+
inputMode="tel"
|
|
131
|
+
required={required}
|
|
132
|
+
className="form__input"
|
|
133
|
+
placeholder={placeholder}
|
|
134
|
+
onChange={onPhoneChange}
|
|
135
|
+
onBlur={handleNumberBlur}
|
|
136
|
+
value={number}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
{/* Hidden merged value to integrate with forms that expect a single field */}
|
|
140
|
+
<input type="hidden" name={name} value={compact([prefix, number]).join(' ')} />
|
|
141
|
+
</div>
|
|
142
|
+
</label>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default PhoneInput;
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { BookingPackage, Pax } from '@qite/tide-client/build/types';
|
|
3
|
-
import { useOfferPrinter } from '../use-offer-printer';
|
|
4
|
-
|
|
5
|
-
interface PrintOfferButtonProps {
|
|
6
|
-
bookingPackage?: BookingPackage;
|
|
7
|
-
|
|
8
|
-
getPax: () => Pax[] | undefined;
|
|
9
|
-
|
|
10
|
-
tagIds?: number[];
|
|
11
|
-
|
|
12
|
-
printActionId?: number | null;
|
|
13
|
-
|
|
14
|
-
onPrinted?: (pdfUrl: string) => void;
|
|
15
|
-
|
|
16
|
-
labelIdle?: string;
|
|
17
|
-
labelCreating?: string;
|
|
18
|
-
labelPrinting?: string;
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
className?: string;
|
|
21
|
-
loader?: React.ReactNode;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const PrintOfferButton: React.FC<PrintOfferButtonProps> = ({
|
|
25
|
-
bookingPackage,
|
|
26
|
-
getPax,
|
|
27
|
-
tagIds,
|
|
28
|
-
printActionId = null,
|
|
29
|
-
onPrinted,
|
|
30
|
-
labelIdle = 'Print offer',
|
|
31
|
-
labelCreating = 'Generating offer…',
|
|
32
|
-
labelPrinting = 'Generating PDF…',
|
|
33
|
-
disabled = false,
|
|
34
|
-
className
|
|
35
|
-
}) => {
|
|
36
|
-
const { handlePrint, stage, loading } = useOfferPrinter({
|
|
37
|
-
bookingPackage,
|
|
38
|
-
getPax,
|
|
39
|
-
tagIds,
|
|
40
|
-
onPrinted,
|
|
41
|
-
printActionId
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const label = stage === 'creating' ? labelCreating : stage === 'printing' ? labelPrinting : labelIdle;
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<button type="button" onClick={handlePrint} disabled={disabled || loading} className={className}>
|
|
48
|
-
{label}
|
|
49
|
-
</button>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export default PrintOfferButton;
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BookingPackage, Pax } from '@qite/tide-client/build/types';
|
|
3
|
+
import { useOfferPrinter } from '../use-offer-printer';
|
|
4
|
+
|
|
5
|
+
interface PrintOfferButtonProps {
|
|
6
|
+
bookingPackage?: BookingPackage;
|
|
7
|
+
|
|
8
|
+
getPax: () => Pax[] | undefined;
|
|
9
|
+
|
|
10
|
+
tagIds?: number[];
|
|
11
|
+
|
|
12
|
+
printActionId?: number | null;
|
|
13
|
+
|
|
14
|
+
onPrinted?: (pdfUrl: string) => void;
|
|
15
|
+
|
|
16
|
+
labelIdle?: string;
|
|
17
|
+
labelCreating?: string;
|
|
18
|
+
labelPrinting?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
loader?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PrintOfferButton: React.FC<PrintOfferButtonProps> = ({
|
|
25
|
+
bookingPackage,
|
|
26
|
+
getPax,
|
|
27
|
+
tagIds,
|
|
28
|
+
printActionId = null,
|
|
29
|
+
onPrinted,
|
|
30
|
+
labelIdle = 'Print offer',
|
|
31
|
+
labelCreating = 'Generating offer…',
|
|
32
|
+
labelPrinting = 'Generating PDF…',
|
|
33
|
+
disabled = false,
|
|
34
|
+
className
|
|
35
|
+
}) => {
|
|
36
|
+
const { handlePrint, stage, loading } = useOfferPrinter({
|
|
37
|
+
bookingPackage,
|
|
38
|
+
getPax,
|
|
39
|
+
tagIds,
|
|
40
|
+
onPrinted,
|
|
41
|
+
printActionId
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const label = stage === 'creating' ? labelCreating : stage === 'printing' ? labelPrinting : labelIdle;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button type="button" onClick={handlePrint} disabled={disabled || loading} className={className}>
|
|
48
|
+
{label}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default PrintOfferButton;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
interface ProductCardProps {
|
|
4
|
-
productName: string;
|
|
5
|
-
thumbnailUrl?: string;
|
|
6
|
-
handleToggleClick: () => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const ProductCard: React.FC<ProductCardProps> = ({ productName, thumbnailUrl, handleToggleClick }) => {
|
|
10
|
-
return (
|
|
11
|
-
<div className="booking__product">
|
|
12
|
-
<div className="booking__product-image">
|
|
13
|
-
{thumbnailUrl && <img src={`${thumbnailUrl}?height=400&width=400`} alt={productName} className="mediacontent" />}
|
|
14
|
-
</div>
|
|
15
|
-
<div className="booking__product-text">
|
|
16
|
-
<h3 className="booking__product-heading">{productName}</h3>
|
|
17
|
-
</div>
|
|
18
|
-
<button type="button" className="booking__product-toggle" onClick={handleToggleClick}></button>
|
|
19
|
-
</div>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default ProductCard;
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ProductCardProps {
|
|
4
|
+
productName: string;
|
|
5
|
+
thumbnailUrl?: string;
|
|
6
|
+
handleToggleClick: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ProductCard: React.FC<ProductCardProps> = ({ productName, thumbnailUrl, handleToggleClick }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="booking__product">
|
|
12
|
+
<div className="booking__product-image">
|
|
13
|
+
{thumbnailUrl && <img src={`${thumbnailUrl}?height=400&width=400`} alt={productName} className="mediacontent" />}
|
|
14
|
+
</div>
|
|
15
|
+
<div className="booking__product-text">
|
|
16
|
+
<h3 className="booking__product-heading">{productName}</h3>
|
|
17
|
+
</div>
|
|
18
|
+
<button type="button" className="booking__product-toggle" onClick={handleToggleClick}></button>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default ProductCard;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare module '*.svg' {
|
|
2
|
-
const content: string;
|
|
3
|
-
export default content;
|
|
4
|
-
}
|
|
1
|
+
declare module '*.svg' {
|
|
2
|
+
const content: string;
|
|
3
|
+
export default content;
|
|
4
|
+
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
setLanguageCode,
|
|
23
23
|
setOfficeId,
|
|
24
24
|
setProductAttributes,
|
|
25
|
+
setShowCommission,
|
|
25
26
|
setSkipPayment,
|
|
26
27
|
setTagIds,
|
|
27
28
|
setTranslations
|
|
@@ -74,6 +75,7 @@ const BookingSelfContained: React.FC<BookingProps> = ({ productCode, productName
|
|
|
74
75
|
officeId,
|
|
75
76
|
bookingOptions,
|
|
76
77
|
showSidebarDeposit,
|
|
78
|
+
showCommission,
|
|
77
79
|
includeFlights,
|
|
78
80
|
skipPaymentWithAgent,
|
|
79
81
|
generatePaymentUrl,
|
|
@@ -199,6 +201,7 @@ const BookingSelfContained: React.FC<BookingProps> = ({ productCode, productName
|
|
|
199
201
|
dispatch(setLanguageCode(language));
|
|
200
202
|
dispatch(setBookingOptions(bookingOptions));
|
|
201
203
|
dispatch(setCalculateDeposit(showSidebarDeposit));
|
|
204
|
+
dispatch(setShowCommission(showCommission));
|
|
202
205
|
if (tagIds && tagIds.length > 0) {
|
|
203
206
|
dispatch(setTagIds(tagIds ?? undefined));
|
|
204
207
|
}
|
|
@@ -209,7 +212,19 @@ const BookingSelfContained: React.FC<BookingProps> = ({ productCode, productName
|
|
|
209
212
|
if (accommodationViewId && accommodationViewId != 0) {
|
|
210
213
|
dispatch(setAccommodationViewId(accommodationViewId));
|
|
211
214
|
}
|
|
212
|
-
}, [
|
|
215
|
+
}, [
|
|
216
|
+
officeId,
|
|
217
|
+
language,
|
|
218
|
+
bookingOptions,
|
|
219
|
+
showSidebarDeposit,
|
|
220
|
+
showCommission,
|
|
221
|
+
setOfficeId,
|
|
222
|
+
setLanguageCode,
|
|
223
|
+
setCalculateDeposit,
|
|
224
|
+
tagIds,
|
|
225
|
+
agentAdressId,
|
|
226
|
+
accommodationViewId
|
|
227
|
+
]);
|
|
213
228
|
|
|
214
229
|
useEffect(() => {
|
|
215
230
|
if (!productAttributes || !bookingAttributes || !rooms?.length || !isNil(bookingNumber) || !isNil(packageDetails)) {
|
|
@@ -29,6 +29,7 @@ import packageApi from './api';
|
|
|
29
29
|
import { BookingStep, OPTIONS_FORM_STEP } from './constants';
|
|
30
30
|
import {
|
|
31
31
|
selectAccommodationCodes,
|
|
32
|
+
selectAgentAdressId,
|
|
32
33
|
selectBookingAttributes,
|
|
33
34
|
selectBookingRooms,
|
|
34
35
|
selectLanguageCode,
|
|
@@ -43,6 +44,7 @@ export interface BookingState {
|
|
|
43
44
|
productAttributes?: ProductAttributes;
|
|
44
45
|
bookingAttributes?: BookingAttributes;
|
|
45
46
|
calculateDeposit: boolean;
|
|
47
|
+
showCommission?: boolean;
|
|
46
48
|
bookingNumber?: string;
|
|
47
49
|
isRetry: boolean;
|
|
48
50
|
package?: BookingPackage;
|
|
@@ -95,6 +97,7 @@ const initialState: BookingState = {
|
|
|
95
97
|
productAttributes: undefined,
|
|
96
98
|
bookingAttributes: undefined,
|
|
97
99
|
calculateDeposit: false,
|
|
100
|
+
showCommission: false,
|
|
98
101
|
bookingNumber: undefined,
|
|
99
102
|
isRetry: false,
|
|
100
103
|
package: undefined,
|
|
@@ -137,6 +140,7 @@ const fetchPackageDetails = createAsyncThunk('booking/details', async (_, { disp
|
|
|
137
140
|
const productAttributes = selectProductAttributes(state);
|
|
138
141
|
const bookingAttributes = selectBookingAttributes(state);
|
|
139
142
|
const agentId = selectAgentId(state);
|
|
143
|
+
const agentAdressId = selectAgentAdressId(state);
|
|
140
144
|
const rooms = selectBookingRooms(state);
|
|
141
145
|
const languageCode = selectLanguageCode(state);
|
|
142
146
|
|
|
@@ -206,7 +210,7 @@ const fetchPackageDetails = createAsyncThunk('booking/details', async (_, { disp
|
|
|
206
210
|
|
|
207
211
|
const request = {
|
|
208
212
|
officeId: officeId,
|
|
209
|
-
agentId: agentId,
|
|
213
|
+
agentId: agentId ?? agentAdressId,
|
|
210
214
|
payload: {
|
|
211
215
|
searchType: searchType,
|
|
212
216
|
catalogueId: bookingAttributes.catalogueId,
|
|
@@ -380,6 +384,9 @@ const bookingSlice = createSlice({
|
|
|
380
384
|
setCalculateDeposit(state, action: PayloadAction<boolean>) {
|
|
381
385
|
state.calculateDeposit = action.payload;
|
|
382
386
|
},
|
|
387
|
+
setShowCommission(state, action: PayloadAction<boolean | undefined>) {
|
|
388
|
+
state.showCommission = action.payload;
|
|
389
|
+
},
|
|
383
390
|
setBookingNumber(state, action: PayloadAction<string>) {
|
|
384
391
|
state.bookingNumber = action.payload;
|
|
385
392
|
},
|
|
@@ -596,6 +603,7 @@ export const {
|
|
|
596
603
|
setProductAttributes,
|
|
597
604
|
setBookingAttributes,
|
|
598
605
|
setCalculateDeposit,
|
|
606
|
+
setShowCommission,
|
|
599
607
|
setBookingNumber,
|
|
600
608
|
setIsRetry,
|
|
601
609
|
setFetchingPackage,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
setOfficeId,
|
|
26
26
|
setPackage,
|
|
27
27
|
setProductAttributes,
|
|
28
|
+
setShowCommission,
|
|
28
29
|
setSkipPayment,
|
|
29
30
|
setTagIds,
|
|
30
31
|
setTranslations,
|
|
@@ -76,6 +77,7 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
76
77
|
confirmation,
|
|
77
78
|
error,
|
|
78
79
|
showSidebarDeposit,
|
|
80
|
+
showCommission,
|
|
79
81
|
includeFlights,
|
|
80
82
|
loaderComponent,
|
|
81
83
|
skipPaymentWithAgent,
|
|
@@ -232,6 +234,7 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
232
234
|
dispatch(setLanguageCode(language));
|
|
233
235
|
dispatch(setBookingOptions(bookingOptions));
|
|
234
236
|
dispatch(setCalculateDeposit(showSidebarDeposit));
|
|
237
|
+
dispatch(setShowCommission(showCommission));
|
|
235
238
|
if (tagIds && tagIds.length > 0) {
|
|
236
239
|
dispatch(setTagIds(tagIds ?? undefined));
|
|
237
240
|
}
|
|
@@ -242,7 +245,19 @@ const Booking: React.FC<BookingProps> = ({ productCode, productName, thumbnailUr
|
|
|
242
245
|
if (accommodationViewId && accommodationViewId != 0) {
|
|
243
246
|
dispatch(setAccommodationViewId(accommodationViewId));
|
|
244
247
|
}
|
|
245
|
-
}, [
|
|
248
|
+
}, [
|
|
249
|
+
officeId,
|
|
250
|
+
language,
|
|
251
|
+
bookingOptions,
|
|
252
|
+
showSidebarDeposit,
|
|
253
|
+
showCommission,
|
|
254
|
+
setOfficeId,
|
|
255
|
+
setLanguageCode,
|
|
256
|
+
setCalculateDeposit,
|
|
257
|
+
tagIds,
|
|
258
|
+
agentAdressId,
|
|
259
|
+
accommodationViewId
|
|
260
|
+
]);
|
|
246
261
|
|
|
247
262
|
useEffect(() => {
|
|
248
263
|
if (!productAttributes || !bookingAttributes || !rooms?.length || !isNil(bookingNumber) || !isNil(packageDetails)) {
|
|
@@ -151,6 +151,8 @@ export const selectVoucherCodes = (state: RootState) => state.booking.voucherCod
|
|
|
151
151
|
|
|
152
152
|
export const selectCalculateDeposit = (state: RootState) => state.booking.calculateDeposit;
|
|
153
153
|
|
|
154
|
+
export const selectShowCommission = (state: RootState) => state.booking.showCommission;
|
|
155
|
+
|
|
154
156
|
export const selectIsRetry = (state: RootState) => state.booking.isRetry;
|
|
155
157
|
|
|
156
158
|
export const selectStartDate = (state: RootState) => state.booking.package?.options.find((x) => x.isSelected)?.fromDate;
|
|
@@ -289,6 +291,7 @@ export const selectBookingPackageBookRequest = createSelector(
|
|
|
289
291
|
selectBookingAddress,
|
|
290
292
|
selectPackageDetails,
|
|
291
293
|
selectCalculateDeposit,
|
|
294
|
+
selectShowCommission,
|
|
292
295
|
selectAgentId,
|
|
293
296
|
selectGeneratePaymentUrl,
|
|
294
297
|
selectSkipPaymentWithAgent,
|
|
@@ -304,6 +307,7 @@ export const selectBookingPackageBookRequest = createSelector(
|
|
|
304
307
|
address,
|
|
305
308
|
packageDetails,
|
|
306
309
|
calculateDeposit,
|
|
310
|
+
showCommission,
|
|
307
311
|
agentId,
|
|
308
312
|
generatePaymentUrl,
|
|
309
313
|
skipPaymentWithAgent,
|
|
@@ -367,6 +371,7 @@ export const selectBookingPackageBookRequest = createSelector(
|
|
|
367
371
|
pax: pax?.length != 0 ? pax : packageDetails.options[0].requestRooms.flatMap((x) => x.pax),
|
|
368
372
|
nonTravelPax: [],
|
|
369
373
|
calculateDeposit: calculateDeposit,
|
|
374
|
+
showCommission: showCommission,
|
|
370
375
|
returnPaymentUrl: returnPaymentUrl,
|
|
371
376
|
notifications: notifications,
|
|
372
377
|
tagIds: tagIds,
|