@qite/tide-booking-component 1.4.110 → 1.4.112
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/build/build-cjs/index.js +2301 -1565
- package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
- package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -1
- package/build/build-cjs/src/search-results/types.d.ts +3 -0
- package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
- package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
- package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
- package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +6 -0
- package/build/build-esm/index.js +2198 -1463
- package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
- package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -1
- package/build/build-esm/src/search-results/types.d.ts +3 -0
- package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
- package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
- package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
- package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +6 -0
- package/package.json +2 -2
- package/src/booking-wizard/components/step-indicator.tsx +1 -1
- package/src/booking-wizard/components/step-route.tsx +1 -1
- package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
- package/src/booking-wizard/features/sidebar/index.tsx +1 -1
- package/src/booking-wizard/features/summary/summary.tsx +1 -1
- package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
- package/src/search-results/components/book-packaging-entry/index.tsx +201 -21
- package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +1 -4
- package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
- package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +40 -14
- package/src/search-results/store/search-results-slice.ts +8 -2
- package/src/search-results/types.ts +4 -0
- package/src/shared/booking/{BookingPanel.tsx → booking-panel.tsx} +1 -1
- package/src/shared/booking/shared-confirmation.tsx +105 -0
- package/src/shared/booking/summary.tsx +380 -0
- package/src/shared/booking/travelers-form.tsx +868 -0
- package/src/shared/components/flyin/flyin.tsx +8 -9
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +4 -4
- package/src/shared/utils/booking-summary.tsx +46 -0
- package/src/shared/utils/tide-api-utils.ts +2 -2
- package/styles/components/_booking.scss +33 -15
- package/styles/components/_cta.scss +2 -2
- package/styles/components/_dropdown.scss +5 -0
- package/styles/components/_flight-option.scss +1 -1
- package/styles/components/_flyin.scss +43 -0
- package/styles/components/_search.scss +5 -0
- package/styles/components/_step-indicators.scss +41 -15
- package/styles/components/_tree.scss +2 -2
- /package/build/build-cjs/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
- /package/build/build-cjs/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
- /package/build/build-cjs/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
- /package/build/build-esm/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
- /package/build/build-esm/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
- /package/build/build-esm/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
- /package/src/shared/booking/{Sidebar.tsx → shared-sidebar.tsx} +0 -0
- /package/src/shared/booking/{StepIndicators.tsx → step-indicators.tsx} +0 -0
|
@@ -1,43 +1,223 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
1
|
+
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
3
3
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
4
4
|
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
6
|
-
import BookingPanel from '../../../shared/booking/
|
|
7
|
-
import StepIndicators from '../../../shared/booking/
|
|
6
|
+
import BookingPanel from '../../../shared/booking/booking-panel';
|
|
7
|
+
import StepIndicators from '../../../shared/booking/step-indicators';
|
|
8
8
|
import WLSidebar from './wl-sidebar';
|
|
9
9
|
import { SearchSeed } from '../../types';
|
|
10
|
+
import SharedTravelersForm, {
|
|
11
|
+
applyTravelersFormValuesToEditablePackagingEntry,
|
|
12
|
+
createInitialValuesFromEditablePackagingEntry,
|
|
13
|
+
SharedTravelersSettings
|
|
14
|
+
} from '../../../shared/booking/travelers-form';
|
|
15
|
+
import { useFormik } from 'formik';
|
|
16
|
+
import { TravelersFormValues } from '../../../booking-wizard/types';
|
|
17
|
+
import { setBookingNumber, setCurrentStep, setEditablePackagingEntry } from '../../store/search-results-slice';
|
|
18
|
+
import validateForm from '../../../booking-wizard/features/travelers-form/validate-form';
|
|
19
|
+
import { bookPackagingEntry, CountryItem, getCountries, PackagingEntry, PackagingRequestBase, TideClientConfig } from '@qite/tide-client';
|
|
20
|
+
import SharedSummary from '../../../shared/booking/summary';
|
|
21
|
+
import { renderEditablePackagingEntrySummaryOptions } from '../../../shared/utils/booking-summary';
|
|
22
|
+
import SharedConfirmation from '../../../shared/booking/shared-confirmation';
|
|
10
23
|
|
|
11
24
|
interface BookPackagingEntryProps {
|
|
12
25
|
activeSearchSeed: SearchSeed | null;
|
|
26
|
+
isConfirmationPage?: boolean;
|
|
13
27
|
}
|
|
14
28
|
|
|
15
|
-
const
|
|
29
|
+
const travelerFormFields = [{ type: 'gender' }, { type: 'firstName' }, { type: 'lastName' }, { type: 'birthDate' }];
|
|
30
|
+
|
|
31
|
+
const mainBookerFormFields = [
|
|
32
|
+
{ type: 'street' },
|
|
33
|
+
{ type: 'houseNumber' },
|
|
34
|
+
{ type: 'box' },
|
|
35
|
+
{ type: 'zipCode' },
|
|
36
|
+
{ type: 'place' },
|
|
37
|
+
{ type: 'country' },
|
|
38
|
+
{ type: 'phone' },
|
|
39
|
+
{ type: 'email' }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const travellersSettings: SharedTravelersSettings = {
|
|
43
|
+
countries: [
|
|
44
|
+
{ iso2: 'BE', name: 'Belgium', phonePrefix: '+32' },
|
|
45
|
+
{ iso2: 'NL', name: 'Netherlands', phonePrefix: '+31' },
|
|
46
|
+
{ iso2: 'FR', name: 'France', phonePrefix: '+33' }
|
|
47
|
+
],
|
|
48
|
+
formFields: travelerFormFields,
|
|
49
|
+
mainBookerFormFields
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isConfirmationPage }) => {
|
|
16
53
|
const context = useContext(SearchResultsConfigurationContext);
|
|
17
|
-
if (!context) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
21
|
-
const { currentStep } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
22
54
|
const dispatch = useDispatch();
|
|
55
|
+
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
56
|
+
|
|
57
|
+
const [countries, setCountries] = useState<CountryItem[]>([]);
|
|
58
|
+
const [userValidated, setUserValidated] = useState(true);
|
|
59
|
+
const [remarks, setRemarks] = useState('');
|
|
60
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
61
|
+
|
|
62
|
+
if (!context || !editablePackagingEntry || !priceDetails) return null;
|
|
63
|
+
|
|
64
|
+
const config: TideClientConfig = {
|
|
65
|
+
host: context.tideConnection.host,
|
|
66
|
+
apiKey: context.tideConnection.apiKey
|
|
67
|
+
};
|
|
23
68
|
|
|
69
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
24
70
|
const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
|
|
25
71
|
|
|
72
|
+
console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
|
|
73
|
+
console.log('priceDetails in WLSidebar:', priceDetails);
|
|
74
|
+
|
|
75
|
+
const initialValues = useMemo(() => createInitialValuesFromEditablePackagingEntry(editablePackagingEntry), [editablePackagingEntry?.transactionId]);
|
|
76
|
+
|
|
77
|
+
const formik = useFormik<TravelersFormValues>({
|
|
78
|
+
initialValues,
|
|
79
|
+
enableReinitialize: true,
|
|
80
|
+
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields),
|
|
81
|
+
onSubmit: (values) => {
|
|
82
|
+
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, values)));
|
|
83
|
+
dispatch(setCurrentStep(1));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!context) return;
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
|
|
91
|
+
(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const result = await getCountries(config, controller.signal);
|
|
94
|
+
setCountries(result.items);
|
|
95
|
+
} catch {}
|
|
96
|
+
})();
|
|
97
|
+
|
|
98
|
+
return () => controller.abort();
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (isConfirmationPage) {
|
|
103
|
+
dispatch(setCurrentStep(2));
|
|
104
|
+
}
|
|
105
|
+
}, [isConfirmationPage, dispatch]);
|
|
106
|
+
|
|
107
|
+
const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
|
|
110
|
+
setIsSubmitting(true);
|
|
111
|
+
|
|
112
|
+
if (typeof window !== 'undefined') {
|
|
113
|
+
window.scrollTo(0, 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let updatedEditablePackagingEntry: PackagingEntry = {
|
|
117
|
+
...editablePackagingEntry,
|
|
118
|
+
remarks
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
122
|
+
const redirectUrl = new URL(window.location.href);
|
|
123
|
+
|
|
124
|
+
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
125
|
+
redirectUrl.searchParams.set('link', '');
|
|
126
|
+
|
|
127
|
+
updatedEditablePackagingEntry = {
|
|
128
|
+
...updatedEditablePackagingEntry,
|
|
129
|
+
redirectUrl: redirectUrl.toString(),
|
|
130
|
+
returnPaymentUrl: true
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const request = {
|
|
138
|
+
language: context.languageCode ?? 'en-GB',
|
|
139
|
+
officeId: context.tideConnection.officeId,
|
|
140
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
141
|
+
agentId: context.agentId,
|
|
142
|
+
payload: updatedEditablePackagingEntry
|
|
143
|
+
} as PackagingRequestBase<PackagingEntry>;
|
|
144
|
+
const bookingResponse = await bookPackagingEntry(config, request);
|
|
145
|
+
|
|
146
|
+
dispatch(setBookingNumber(bookingResponse.number));
|
|
147
|
+
|
|
148
|
+
if (bookingResponse.paymentUrl) {
|
|
149
|
+
window.location.href = bookingResponse.paymentUrl;
|
|
150
|
+
} else {
|
|
151
|
+
dispatch(setCurrentStep(2));
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
dispatch(setCurrentStep(3));
|
|
155
|
+
} finally {
|
|
156
|
+
setIsSubmitting(false);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
26
160
|
return (
|
|
27
161
|
<div className="booking">
|
|
28
162
|
<div className="booking__content">
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
163
|
+
<div className="booking__panel">
|
|
164
|
+
<BookingPanel
|
|
165
|
+
currentStep={currentStep}
|
|
166
|
+
stepLabels={stepLabels}
|
|
167
|
+
StepIndicatorsComponent={StepIndicators}
|
|
168
|
+
renderTitle={(step) => (
|
|
169
|
+
<>
|
|
170
|
+
{step + 1}. {stepLabels[step]}
|
|
171
|
+
</>
|
|
172
|
+
)}>
|
|
173
|
+
{currentStep === 0 && (
|
|
174
|
+
<SharedTravelersForm
|
|
175
|
+
formik={formik}
|
|
176
|
+
translations={translations}
|
|
177
|
+
travellersSettings={travellersSettings}
|
|
178
|
+
countries={countries}
|
|
179
|
+
travelersFirstStep={false}
|
|
180
|
+
isUnavailable={false}
|
|
181
|
+
useCompactForm={false}
|
|
182
|
+
showAgentSelection={false}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{currentStep === 1 && (
|
|
187
|
+
<SharedSummary
|
|
188
|
+
translations={translations}
|
|
189
|
+
travelerFormValues={formik.values}
|
|
190
|
+
isSubmitting={isSubmitting}
|
|
191
|
+
userValidated={userValidated}
|
|
192
|
+
remarks={remarks}
|
|
193
|
+
enableVoucher={false}
|
|
194
|
+
allowOption={false}
|
|
195
|
+
isOffer={false}
|
|
196
|
+
onUserValidatedChange={setUserValidated}
|
|
197
|
+
onRemarksChange={setRemarks}
|
|
198
|
+
onSubmit={handleSummarySubmit}
|
|
199
|
+
renderOptions={() => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations)}
|
|
200
|
+
renderPreviousButton={() => (
|
|
201
|
+
<button type="button" title={translations.STEPS.PREVIOUS} onClick={() => dispatch(setCurrentStep(0))} className="cta cta--secondary">
|
|
202
|
+
{translations.STEPS.PREVIOUS}
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
205
|
+
/>
|
|
206
|
+
)}
|
|
207
|
+
{currentStep === 2 && (
|
|
208
|
+
<SharedConfirmation
|
|
209
|
+
bookingNumber={bookingNumber ?? editablePackagingEntry?.dossierNumber ?? ''}
|
|
210
|
+
isOption={false}
|
|
211
|
+
isOffer={false}
|
|
212
|
+
translations={translations.CONFIRMATION}
|
|
213
|
+
// companyContactPhone={context?.companyContactPhone || ''}
|
|
214
|
+
// companyContactEmail={context?.companyContactEmail || ''}
|
|
215
|
+
// homeUrl={context?.homeUrl || '/'}
|
|
216
|
+
/>
|
|
217
|
+
)}
|
|
218
|
+
{currentStep === 3 && <div>{/* error */}</div>}
|
|
219
|
+
</BookingPanel>
|
|
220
|
+
</div>
|
|
41
221
|
<div className="backdrop" id="backdrop"></div>
|
|
42
222
|
<WLSidebar activeSearchSeed={activeSearchSeed} />
|
|
43
223
|
</div>
|
|
@@ -3,7 +3,6 @@ import { formatDate, getDateOnlyTime, getTranslations } from '../../../shared/ut
|
|
|
3
3
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
4
4
|
import { useSelector } from 'react-redux';
|
|
5
5
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
6
|
-
import SharedSidebar from '../../../shared/booking/Sidebar';
|
|
7
6
|
|
|
8
7
|
import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
|
|
9
8
|
import { first, last, sum } from 'lodash';
|
|
@@ -13,6 +12,7 @@ import { RoomTraveler } from '../../../booking-wizard/types';
|
|
|
13
12
|
|
|
14
13
|
import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingEntryLine } from '@qite/tide-client';
|
|
15
14
|
import Spinner from '../spinner/spinner';
|
|
15
|
+
import SharedSidebar from '../../../shared/booking/shared-sidebar';
|
|
16
16
|
|
|
17
17
|
interface WLSidebarProps {
|
|
18
18
|
activeSearchSeed: SearchSeed | null;
|
|
@@ -79,9 +79,6 @@ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
|
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
|
|
83
|
-
console.log('priceDetails in WLSidebar:', priceDetails);
|
|
84
|
-
|
|
85
82
|
const sortedLines = useMemo(() => {
|
|
86
83
|
return [...(editablePackagingEntry?.lines ?? [])].sort((a, b) => {
|
|
87
84
|
const dateA = getDateOnlyTime(a.from);
|
|
@@ -19,7 +19,7 @@ const GroupTourCard: React.FC<GroupTourCardProps> = ({ result, languageCode, cms
|
|
|
19
19
|
const context = useContext(SearchResultsConfigurationContext);
|
|
20
20
|
const { selectedSearchResult } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
21
21
|
if (!context) {
|
|
22
|
-
return;
|
|
22
|
+
return null;
|
|
23
23
|
}
|
|
24
24
|
const dispatch = useDispatch();
|
|
25
25
|
const translations = getTranslations(languageCode ?? 'en-GB');
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
setInitialFlightFilters,
|
|
30
30
|
resetFlightFilters,
|
|
31
31
|
setFilters,
|
|
32
|
-
setInitialFilters
|
|
32
|
+
setInitialFilters,
|
|
33
|
+
setBookPackagingEntry
|
|
33
34
|
} from '../../store/search-results-slice';
|
|
34
35
|
import { FlyInType, SearchSeed, SortByType } from '../../types';
|
|
35
36
|
import useMediaQuery from '../../../shared/utils/use-media-query-util';
|
|
@@ -58,7 +59,11 @@ import {
|
|
|
58
59
|
PackagingFlightResponse,
|
|
59
60
|
PackagingAccommodationResponse,
|
|
60
61
|
FlightSearchResponseFlightSegment,
|
|
61
|
-
PackagingEntryLineFlightLine
|
|
62
|
+
PackagingEntryLineFlightLine,
|
|
63
|
+
PackagingEntryAddress,
|
|
64
|
+
PackagingEntryPax,
|
|
65
|
+
PackagingEntryRoom,
|
|
66
|
+
PackagingRequestBase
|
|
62
67
|
} from '@qite/tide-client';
|
|
63
68
|
import { getDateFromParams, getNumberFromParams, getRoomsFromParams, getStringFromParams } from '../../../shared/utils/query-string-util';
|
|
64
69
|
import { concat, first, isEmpty, last, range } from 'lodash';
|
|
@@ -99,7 +104,6 @@ import FullItinerary from '../itinerary/full-itinerary';
|
|
|
99
104
|
import { getFlightKey } from '../../utils/flight-utils';
|
|
100
105
|
import IndependentFlightOption from '../flight/flight-selection/independent-flight-option';
|
|
101
106
|
import { Spinner } from '../../..';
|
|
102
|
-
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
103
107
|
import {
|
|
104
108
|
selectSelectedCombinationFlight,
|
|
105
109
|
selectSelectedOutward,
|
|
@@ -167,6 +171,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
167
171
|
const [itineraryIsLoading, setItineraryIsLoading] = useState(false);
|
|
168
172
|
|
|
169
173
|
const [itineraryOpen, setItineraryOpen] = useState(false);
|
|
174
|
+
const [isBookingConfirmation, setIsBookingConfirmation] = useState(false);
|
|
170
175
|
|
|
171
176
|
const [selectedAccommodationSeed, setSelectedAccommodationSeed] = useState<SearchSeed | null>(null);
|
|
172
177
|
|
|
@@ -472,7 +477,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
472
477
|
const handleConfirmHotelSwap = () => {
|
|
473
478
|
const updatedEntry = swapHotelInPackagingEntry();
|
|
474
479
|
if (!updatedEntry) return;
|
|
475
|
-
|
|
476
480
|
dispatch(setEditablePackagingEntry(updatedEntry));
|
|
477
481
|
handleFlyInToggle(false);
|
|
478
482
|
};
|
|
@@ -797,6 +801,14 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
797
801
|
if (context?.packagingEntry) {
|
|
798
802
|
dispatch(setEditablePackagingEntry(structuredClone(context.packagingEntry)));
|
|
799
803
|
dispatch(setTransactionId(context.packagingEntry.transactionId));
|
|
804
|
+
|
|
805
|
+
const params = new URLSearchParams(location.search);
|
|
806
|
+
const bookingConfirmation = getStringFromParams(params, 'bookingConfirmation');
|
|
807
|
+
console.log('bookingConfirmation', bookingConfirmation);
|
|
808
|
+
if (bookingConfirmation == 'true') {
|
|
809
|
+
setIsBookingConfirmation(true);
|
|
810
|
+
dispatch(setBookPackagingEntry(true));
|
|
811
|
+
}
|
|
800
812
|
}
|
|
801
813
|
}, [context?.packagingEntry]);
|
|
802
814
|
|
|
@@ -1111,7 +1123,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1111
1123
|
});
|
|
1112
1124
|
|
|
1113
1125
|
if (!nextEntry) return;
|
|
1114
|
-
|
|
1115
1126
|
dispatch(setEditablePackagingEntry(nextEntry));
|
|
1116
1127
|
|
|
1117
1128
|
if (selectedCombinationFlight) {
|
|
@@ -1418,28 +1429,43 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1418
1429
|
}
|
|
1419
1430
|
|
|
1420
1431
|
let paxId = 0;
|
|
1432
|
+
const pax: PackagingEntryPax[] = [];
|
|
1433
|
+
const rooms: PackagingEntryRoom[] = [];
|
|
1434
|
+
|
|
1435
|
+
seed.rooms?.forEach((room, roomIndex) => {
|
|
1436
|
+
const paxIds = room.pax.map((_, paxIndex) => {
|
|
1437
|
+
const id = paxId++;
|
|
1421
1438
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
room.pax.map((_, paxIndex) => ({
|
|
1425
|
-
id: paxId++,
|
|
1439
|
+
pax.push({
|
|
1440
|
+
id,
|
|
1426
1441
|
firstName: '',
|
|
1427
1442
|
lastName: '',
|
|
1428
1443
|
dateOfBirth: null,
|
|
1429
1444
|
isMainBooker: roomIndex === 0 && paxIndex === 0
|
|
1430
|
-
})
|
|
1431
|
-
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
return id;
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
rooms.push({
|
|
1451
|
+
id: roomIndex,
|
|
1452
|
+
paxIds
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1432
1455
|
|
|
1433
1456
|
return {
|
|
1434
1457
|
language,
|
|
1435
1458
|
transactionId,
|
|
1436
1459
|
dossierNumber: '',
|
|
1437
|
-
status:
|
|
1460
|
+
status: context?.entryStatus,
|
|
1461
|
+
customStatusId: context?.customEntryStatusId,
|
|
1438
1462
|
bookingDate: null,
|
|
1439
1463
|
price: 0,
|
|
1440
1464
|
depositAmount: 0,
|
|
1441
1465
|
pax,
|
|
1442
|
-
|
|
1466
|
+
rooms,
|
|
1467
|
+
lines: [],
|
|
1468
|
+
address: {} as PackagingEntryAddress
|
|
1443
1469
|
} as PackagingEntry;
|
|
1444
1470
|
};
|
|
1445
1471
|
|
|
@@ -1453,7 +1479,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1453
1479
|
{context && (
|
|
1454
1480
|
<div className="search">
|
|
1455
1481
|
{bookPackagingEntry ? (
|
|
1456
|
-
<BookPackagingEntry activeSearchSeed={activeSearchSeed} />
|
|
1482
|
+
<BookPackagingEntry activeSearchSeed={activeSearchSeed} isConfirmationPage={isBookingConfirmation} />
|
|
1457
1483
|
) : (
|
|
1458
1484
|
<div className="search__container">
|
|
1459
1485
|
{context.searchConfiguration.qsmType === PortalQsmType.Flight && (
|
|
@@ -59,6 +59,7 @@ export interface SearchResultsState {
|
|
|
59
59
|
|
|
60
60
|
bookPackagingEntry: boolean;
|
|
61
61
|
currentStep: number;
|
|
62
|
+
bookingNumber?: string;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const initialState: SearchResultsState = {
|
|
@@ -109,7 +110,8 @@ const initialState: SearchResultsState = {
|
|
|
109
110
|
confirmedExcursionsByDay: {},
|
|
110
111
|
|
|
111
112
|
bookPackagingEntry: false,
|
|
112
|
-
currentStep: 0
|
|
113
|
+
currentStep: 0,
|
|
114
|
+
bookingNumber: undefined
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
const searchResultsSlice = createSlice({
|
|
@@ -292,6 +294,9 @@ const searchResultsSlice = createSlice({
|
|
|
292
294
|
},
|
|
293
295
|
setCurrentStep(state, action: PayloadAction<number>) {
|
|
294
296
|
state.currentStep = action.payload;
|
|
297
|
+
},
|
|
298
|
+
setBookingNumber(state, action: PayloadAction<string>) {
|
|
299
|
+
state.bookingNumber = action.payload;
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
});
|
|
@@ -339,7 +344,8 @@ export const {
|
|
|
339
344
|
removeConfirmedExcursionForDay,
|
|
340
345
|
clearConfirmedExcursionsForDay,
|
|
341
346
|
setBookPackagingEntry,
|
|
342
|
-
setCurrentStep
|
|
347
|
+
setCurrentStep,
|
|
348
|
+
setBookingNumber
|
|
343
349
|
} = searchResultsSlice.actions;
|
|
344
350
|
|
|
345
351
|
export default searchResultsSlice.reducer;
|
|
@@ -61,6 +61,10 @@ export interface SearchResultsConfiguration {
|
|
|
61
61
|
destinationImage?: { url: string; alt: string };
|
|
62
62
|
onBook?: (result: BookingPackage) => void;
|
|
63
63
|
packagingEntry?: PackagingEntry | null;
|
|
64
|
+
|
|
65
|
+
generatePaymentUrl?: boolean;
|
|
66
|
+
entryStatus?: number;
|
|
67
|
+
customEntryStatusId?: number;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
@@ -10,7 +10,7 @@ interface BookingPanelProps {
|
|
|
10
10
|
|
|
11
11
|
const BookingPanel: React.FC<BookingPanelProps> = ({ currentStep, stepLabels, renderTitle, children, StepIndicatorsComponent }) => {
|
|
12
12
|
return (
|
|
13
|
-
<div className="
|
|
13
|
+
<div className="booking__panel__wrapper">
|
|
14
14
|
<StepIndicatorsComponent currentStep={currentStep} stepLabels={stepLabels} />
|
|
15
15
|
<div className="booking__panel-frame booking__panel-frame--transparent">
|
|
16
16
|
<div className="booking__panel-heading">
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Icon from '../components/icon';
|
|
3
|
+
import Message from '../../booking-wizard/components/message';
|
|
4
|
+
|
|
5
|
+
export interface SharedConfirmationTranslations {
|
|
6
|
+
MAIL_SUBJECT: string;
|
|
7
|
+
TITLE_TEXT_OPTION: string;
|
|
8
|
+
TITLE_TEXT_OFFER: string;
|
|
9
|
+
TITLE_TEXT_BOOKING: string;
|
|
10
|
+
MESSAGE_TEXT1: string;
|
|
11
|
+
MESSAGE_TEXT2_OFFER: string;
|
|
12
|
+
MESSAGE_TEXT2_BOOKING: string;
|
|
13
|
+
QUESTIONS_TEXT1: string;
|
|
14
|
+
QUESTIONS_TEXT2: string;
|
|
15
|
+
QUESTIONS_TEXT3: string;
|
|
16
|
+
QUESTIONS_ALT: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SharedConfirmationProps {
|
|
20
|
+
bookingNumber?: string;
|
|
21
|
+
isOption?: boolean;
|
|
22
|
+
isOffer?: boolean;
|
|
23
|
+
translations: SharedConfirmationTranslations;
|
|
24
|
+
companyContactPhone?: string;
|
|
25
|
+
companyContactEmail?: string;
|
|
26
|
+
homeUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function format(str: string, args: (string | number | undefined)[]): string {
|
|
30
|
+
// Simple format function: replaces {0}, {1}, ... with args
|
|
31
|
+
return str.replace(/\{(\d+)\}/g, (match, number) => (typeof args[number] !== 'undefined' ? String(args[number]) : match));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SharedConfirmation: React.FC<SharedConfirmationProps> = ({
|
|
35
|
+
bookingNumber,
|
|
36
|
+
isOption,
|
|
37
|
+
isOffer,
|
|
38
|
+
translations,
|
|
39
|
+
companyContactPhone,
|
|
40
|
+
companyContactEmail,
|
|
41
|
+
homeUrl = '/'
|
|
42
|
+
}) => {
|
|
43
|
+
const encodedMailSubject = encodeURI(translations.MAIL_SUBJECT);
|
|
44
|
+
const titleText = isOption
|
|
45
|
+
? format(translations.TITLE_TEXT_OPTION, [bookingNumber])
|
|
46
|
+
: isOffer
|
|
47
|
+
? format(translations.TITLE_TEXT_OFFER, [bookingNumber])
|
|
48
|
+
: format(translations.TITLE_TEXT_BOOKING, [bookingNumber]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="form form__booking--message" id="booking--confirmation">
|
|
52
|
+
<div className="form__region">
|
|
53
|
+
<div className="form__row">
|
|
54
|
+
<div className="form__group">
|
|
55
|
+
<Message
|
|
56
|
+
type="success"
|
|
57
|
+
title={titleText}
|
|
58
|
+
actionComponent={
|
|
59
|
+
companyContactPhone || companyContactEmail ? (
|
|
60
|
+
<div className="sm">
|
|
61
|
+
{companyContactPhone && (
|
|
62
|
+
<a href={`tel://${companyContactPhone}`} className="sm__icon">
|
|
63
|
+
<Icon name="tel" />
|
|
64
|
+
</a>
|
|
65
|
+
)}
|
|
66
|
+
{companyContactEmail && (
|
|
67
|
+
<a href={`mailto://${companyContactEmail}`} className="sm__icon">
|
|
68
|
+
<Icon name="mail" />
|
|
69
|
+
</a>
|
|
70
|
+
)}
|
|
71
|
+
{homeUrl && (
|
|
72
|
+
<a href={homeUrl} className="sm__icon">
|
|
73
|
+
<Icon name="home" />
|
|
74
|
+
</a>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
) : undefined
|
|
78
|
+
}>
|
|
79
|
+
{!isOption ? (
|
|
80
|
+
<>
|
|
81
|
+
<p>
|
|
82
|
+
{translations.MESSAGE_TEXT1}
|
|
83
|
+
<br />
|
|
84
|
+
{isOffer ? translations.MESSAGE_TEXT2_OFFER : translations.MESSAGE_TEXT2_BOOKING}
|
|
85
|
+
</p>
|
|
86
|
+
{companyContactEmail && (
|
|
87
|
+
<p>
|
|
88
|
+
{translations.QUESTIONS_TEXT1}{' '}
|
|
89
|
+
<a href={`mailto:${companyContactEmail}?subject=${encodedMailSubject}`} title={translations.QUESTIONS_ALT}>
|
|
90
|
+
{translations.QUESTIONS_TEXT2}
|
|
91
|
+
</a>
|
|
92
|
+
{translations.QUESTIONS_TEXT3}
|
|
93
|
+
</p>
|
|
94
|
+
)}
|
|
95
|
+
</>
|
|
96
|
+
) : undefined}
|
|
97
|
+
</Message>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default SharedConfirmation;
|