@qite/tide-booking-component 1.4.110 → 1.4.111
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 +2316 -1555
- 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 +2213 -1453
- 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 +192 -11
- 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 +42 -14
- package/src/search-results/store/search-results-slice.ts +8 -2
- package/src/search-results/types.ts +4 -0
- package/src/shared/booking/shared-confirmation.tsx +105 -0
- package/src/shared/booking/summary.tsx +380 -0
- package/src/shared/booking/travelers-form.tsx +870 -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/_dropdown.scss +5 -0
- package/styles/components/_flyin.scss +43 -0
- package/styles/components/_search.scss +5 -0
- /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/{BookingPanel.tsx → booking-panel.tsx} +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,28 +1,165 @@
|
|
|
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, TideClientConfig } from '@qite/tide-client';
|
|
20
|
+
import SharedSummary from '../../../shared/booking/summary';
|
|
21
|
+
import { renderEditablePackagingEntrySummaryOptions } from '../../../shared/utils/booking-summary';
|
|
22
|
+
|
|
23
|
+
// TODO; fix import
|
|
24
|
+
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
25
|
+
import SharedConfirmation from '../../../shared/booking/shared-confirmation';
|
|
10
26
|
|
|
11
27
|
interface BookPackagingEntryProps {
|
|
12
28
|
activeSearchSeed: SearchSeed | null;
|
|
29
|
+
isConfirmationPage?: boolean;
|
|
13
30
|
}
|
|
14
31
|
|
|
15
|
-
const
|
|
32
|
+
const travelerFormFields = [{ type: 'gender' }, { type: 'firstName' }, { type: 'lastName' }, { type: 'birthDate' }];
|
|
33
|
+
|
|
34
|
+
const mainBookerFormFields = [
|
|
35
|
+
{ type: 'street' },
|
|
36
|
+
{ type: 'houseNumber' },
|
|
37
|
+
{ type: 'box' },
|
|
38
|
+
{ type: 'zipCode' },
|
|
39
|
+
{ type: 'place' },
|
|
40
|
+
{ type: 'country' },
|
|
41
|
+
{ type: 'phone' },
|
|
42
|
+
{ type: 'email' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const travellersSettings: SharedTravelersSettings = {
|
|
46
|
+
countries: [
|
|
47
|
+
{ iso2: 'BE', name: 'Belgium', phonePrefix: '+32' },
|
|
48
|
+
{ iso2: 'NL', name: 'Netherlands', phonePrefix: '+31' },
|
|
49
|
+
{ iso2: 'FR', name: 'France', phonePrefix: '+33' }
|
|
50
|
+
],
|
|
51
|
+
formFields: travelerFormFields,
|
|
52
|
+
mainBookerFormFields
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isConfirmationPage }) => {
|
|
16
56
|
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
57
|
const dispatch = useDispatch();
|
|
58
|
+
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
59
|
+
|
|
60
|
+
const [countries, setCountries] = useState<CountryItem[]>([]);
|
|
61
|
+
const [userValidated, setUserValidated] = useState(true);
|
|
62
|
+
const [remarks, setRemarks] = useState('');
|
|
63
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
64
|
+
|
|
65
|
+
if (!context || !editablePackagingEntry || !priceDetails) return null;
|
|
23
66
|
|
|
67
|
+
const config: TideClientConfig = {
|
|
68
|
+
host: context.tideConnection.host,
|
|
69
|
+
apiKey: context.tideConnection.apiKey
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
24
73
|
const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
|
|
25
74
|
|
|
75
|
+
console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
|
|
76
|
+
console.log('priceDetails in WLSidebar:', priceDetails);
|
|
77
|
+
|
|
78
|
+
const initialValues = useMemo(() => createInitialValuesFromEditablePackagingEntry(editablePackagingEntry), [editablePackagingEntry?.transactionId]);
|
|
79
|
+
|
|
80
|
+
const formik = useFormik<TravelersFormValues>({
|
|
81
|
+
initialValues,
|
|
82
|
+
enableReinitialize: true,
|
|
83
|
+
validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields),
|
|
84
|
+
onSubmit: (values) => {
|
|
85
|
+
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, values)));
|
|
86
|
+
dispatch(setCurrentStep(1));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!context) return;
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
|
|
94
|
+
(async () => {
|
|
95
|
+
try {
|
|
96
|
+
const result = await getCountries(config, controller.signal);
|
|
97
|
+
setCountries(result.items);
|
|
98
|
+
} catch {}
|
|
99
|
+
})();
|
|
100
|
+
|
|
101
|
+
return () => controller.abort();
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (isConfirmationPage) {
|
|
106
|
+
dispatch(setCurrentStep(2));
|
|
107
|
+
}
|
|
108
|
+
}, [isConfirmationPage, dispatch]);
|
|
109
|
+
|
|
110
|
+
const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
|
|
113
|
+
setIsSubmitting(true);
|
|
114
|
+
|
|
115
|
+
if (typeof window !== 'undefined') {
|
|
116
|
+
window.scrollTo(0, 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let updatedEditablePackagingEntry: PackagingEntry = {
|
|
120
|
+
...editablePackagingEntry,
|
|
121
|
+
remarks
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
125
|
+
const redirectUrl = new URL(window.location.href);
|
|
126
|
+
|
|
127
|
+
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
128
|
+
redirectUrl.searchParams.set('link', '');
|
|
129
|
+
|
|
130
|
+
updatedEditablePackagingEntry = {
|
|
131
|
+
...updatedEditablePackagingEntry,
|
|
132
|
+
redirectUrl: redirectUrl.toString(),
|
|
133
|
+
returnPaymentUrl: true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const request = {
|
|
141
|
+
language: context.languageCode ?? 'en-GB',
|
|
142
|
+
officeId: context.tideConnection.officeId,
|
|
143
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
144
|
+
agentId: context.agentId,
|
|
145
|
+
payload: updatedEditablePackagingEntry
|
|
146
|
+
} as PackagingRequestBase<PackagingEntry>;
|
|
147
|
+
const bookingResponse = await bookPackagingEntry(config, request);
|
|
148
|
+
|
|
149
|
+
dispatch(setBookingNumber(bookingResponse.number));
|
|
150
|
+
|
|
151
|
+
if (bookingResponse.paymentUrl) {
|
|
152
|
+
window.location.href = bookingResponse.paymentUrl;
|
|
153
|
+
} else {
|
|
154
|
+
dispatch(setCurrentStep(2));
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
dispatch(setCurrentStep(3));
|
|
158
|
+
} finally {
|
|
159
|
+
setIsSubmitting(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
26
163
|
return (
|
|
27
164
|
<div className="booking">
|
|
28
165
|
<div className="booking__content">
|
|
@@ -35,8 +172,52 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
|
|
|
35
172
|
{step + 1}. {stepLabels[step]}
|
|
36
173
|
</>
|
|
37
174
|
)}>
|
|
38
|
-
{
|
|
39
|
-
|
|
175
|
+
{currentStep === 0 && (
|
|
176
|
+
<SharedTravelersForm
|
|
177
|
+
formik={formik}
|
|
178
|
+
translations={translations}
|
|
179
|
+
travellersSettings={travellersSettings}
|
|
180
|
+
countries={countries}
|
|
181
|
+
travelersFirstStep={false}
|
|
182
|
+
isUnavailable={false}
|
|
183
|
+
useCompactForm={false}
|
|
184
|
+
showAgentSelection={false}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{currentStep === 1 && (
|
|
189
|
+
<SharedSummary
|
|
190
|
+
translations={translations}
|
|
191
|
+
travelerFormValues={formik.values}
|
|
192
|
+
isSubmitting={isSubmitting}
|
|
193
|
+
userValidated={userValidated}
|
|
194
|
+
remarks={remarks}
|
|
195
|
+
enableVoucher={false}
|
|
196
|
+
allowOption={false}
|
|
197
|
+
isOffer={false}
|
|
198
|
+
onUserValidatedChange={setUserValidated}
|
|
199
|
+
onRemarksChange={setRemarks}
|
|
200
|
+
onSubmit={handleSummarySubmit}
|
|
201
|
+
renderOptions={() => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations)}
|
|
202
|
+
renderPreviousButton={() => (
|
|
203
|
+
<button type="button" title={translations.STEPS.PREVIOUS} onClick={() => dispatch(setCurrentStep(0))} className="cta cta--secondary">
|
|
204
|
+
{translations.STEPS.PREVIOUS}
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
{currentStep === 2 && (
|
|
210
|
+
<SharedConfirmation
|
|
211
|
+
bookingNumber={bookingNumber ?? editablePackagingEntry?.dossierNumber ?? ''}
|
|
212
|
+
isOption={false}
|
|
213
|
+
isOffer={false}
|
|
214
|
+
translations={translations.CONFIRMATION}
|
|
215
|
+
// companyContactPhone={context?.companyContactPhone || ''}
|
|
216
|
+
// companyContactEmail={context?.companyContactEmail || ''}
|
|
217
|
+
// homeUrl={context?.homeUrl || '/'}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
{currentStep === 3 && <div>{/* error */}</div>}
|
|
40
221
|
</BookingPanel>
|
|
41
222
|
<div className="backdrop" id="backdrop"></div>
|
|
42
223
|
<WLSidebar activeSearchSeed={activeSearchSeed} />
|
|
@@ -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,10 @@ import {
|
|
|
58
59
|
PackagingFlightResponse,
|
|
59
60
|
PackagingAccommodationResponse,
|
|
60
61
|
FlightSearchResponseFlightSegment,
|
|
61
|
-
PackagingEntryLineFlightLine
|
|
62
|
+
PackagingEntryLineFlightLine,
|
|
63
|
+
PackagingEntryAddress,
|
|
64
|
+
PackagingEntryPax,
|
|
65
|
+
PackagingEntryRoom
|
|
62
66
|
} from '@qite/tide-client';
|
|
63
67
|
import { getDateFromParams, getNumberFromParams, getRoomsFromParams, getStringFromParams } from '../../../shared/utils/query-string-util';
|
|
64
68
|
import { concat, first, isEmpty, last, range } from 'lodash';
|
|
@@ -99,7 +103,6 @@ import FullItinerary from '../itinerary/full-itinerary';
|
|
|
99
103
|
import { getFlightKey } from '../../utils/flight-utils';
|
|
100
104
|
import IndependentFlightOption from '../flight/flight-selection/independent-flight-option';
|
|
101
105
|
import { Spinner } from '../../..';
|
|
102
|
-
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
103
106
|
import {
|
|
104
107
|
selectSelectedCombinationFlight,
|
|
105
108
|
selectSelectedOutward,
|
|
@@ -113,6 +116,9 @@ import DayByDayExcursions from '../excursions/day-by-day-excursions';
|
|
|
113
116
|
import BookPackagingEntry from '../book-packaging-entry';
|
|
114
117
|
import { format } from 'date-fns';
|
|
115
118
|
|
|
119
|
+
// TODO; fix import
|
|
120
|
+
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
121
|
+
|
|
116
122
|
type BuildPackagingEntryPartialArgs = {
|
|
117
123
|
sourceEntry: PackagingEntry | null | undefined;
|
|
118
124
|
selectedHotelCode: string | null | undefined;
|
|
@@ -167,6 +173,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
167
173
|
const [itineraryIsLoading, setItineraryIsLoading] = useState(false);
|
|
168
174
|
|
|
169
175
|
const [itineraryOpen, setItineraryOpen] = useState(false);
|
|
176
|
+
const [isBookingConfirmation, setIsBookingConfirmation] = useState(false);
|
|
170
177
|
|
|
171
178
|
const [selectedAccommodationSeed, setSelectedAccommodationSeed] = useState<SearchSeed | null>(null);
|
|
172
179
|
|
|
@@ -472,7 +479,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
472
479
|
const handleConfirmHotelSwap = () => {
|
|
473
480
|
const updatedEntry = swapHotelInPackagingEntry();
|
|
474
481
|
if (!updatedEntry) return;
|
|
475
|
-
|
|
476
482
|
dispatch(setEditablePackagingEntry(updatedEntry));
|
|
477
483
|
handleFlyInToggle(false);
|
|
478
484
|
};
|
|
@@ -797,6 +803,14 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
797
803
|
if (context?.packagingEntry) {
|
|
798
804
|
dispatch(setEditablePackagingEntry(structuredClone(context.packagingEntry)));
|
|
799
805
|
dispatch(setTransactionId(context.packagingEntry.transactionId));
|
|
806
|
+
|
|
807
|
+
const params = new URLSearchParams(location.search);
|
|
808
|
+
const bookingConfirmation = getStringFromParams(params, 'bookingConfirmation');
|
|
809
|
+
console.log('bookingConfirmation', bookingConfirmation);
|
|
810
|
+
if (bookingConfirmation == 'true') {
|
|
811
|
+
setIsBookingConfirmation(true);
|
|
812
|
+
dispatch(setBookPackagingEntry(true));
|
|
813
|
+
}
|
|
800
814
|
}
|
|
801
815
|
}, [context?.packagingEntry]);
|
|
802
816
|
|
|
@@ -1111,7 +1125,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1111
1125
|
});
|
|
1112
1126
|
|
|
1113
1127
|
if (!nextEntry) return;
|
|
1114
|
-
|
|
1115
1128
|
dispatch(setEditablePackagingEntry(nextEntry));
|
|
1116
1129
|
|
|
1117
1130
|
if (selectedCombinationFlight) {
|
|
@@ -1418,28 +1431,43 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1418
1431
|
}
|
|
1419
1432
|
|
|
1420
1433
|
let paxId = 0;
|
|
1434
|
+
const pax: PackagingEntryPax[] = [];
|
|
1435
|
+
const rooms: PackagingEntryRoom[] = [];
|
|
1421
1436
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1437
|
+
seed.rooms?.forEach((room, roomIndex) => {
|
|
1438
|
+
const paxIds = room.pax.map((_, paxIndex) => {
|
|
1439
|
+
const id = paxId++;
|
|
1440
|
+
|
|
1441
|
+
pax.push({
|
|
1442
|
+
id,
|
|
1426
1443
|
firstName: '',
|
|
1427
1444
|
lastName: '',
|
|
1428
1445
|
dateOfBirth: null,
|
|
1429
1446
|
isMainBooker: roomIndex === 0 && paxIndex === 0
|
|
1430
|
-
})
|
|
1431
|
-
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
return id;
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
rooms.push({
|
|
1453
|
+
id: roomIndex,
|
|
1454
|
+
paxIds
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1432
1457
|
|
|
1433
1458
|
return {
|
|
1434
1459
|
language,
|
|
1435
1460
|
transactionId,
|
|
1436
1461
|
dossierNumber: '',
|
|
1437
|
-
status:
|
|
1462
|
+
status: context?.entryStatus,
|
|
1463
|
+
customStatusId: context?.customEntryStatusId,
|
|
1438
1464
|
bookingDate: null,
|
|
1439
1465
|
price: 0,
|
|
1440
1466
|
depositAmount: 0,
|
|
1441
1467
|
pax,
|
|
1442
|
-
|
|
1468
|
+
rooms,
|
|
1469
|
+
lines: [],
|
|
1470
|
+
address: {} as PackagingEntryAddress
|
|
1443
1471
|
} as PackagingEntry;
|
|
1444
1472
|
};
|
|
1445
1473
|
|
|
@@ -1453,7 +1481,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1453
1481
|
{context && (
|
|
1454
1482
|
<div className="search">
|
|
1455
1483
|
{bookPackagingEntry ? (
|
|
1456
|
-
<BookPackagingEntry activeSearchSeed={activeSearchSeed} />
|
|
1484
|
+
<BookPackagingEntry activeSearchSeed={activeSearchSeed} isConfirmationPage={isBookingConfirmation} />
|
|
1457
1485
|
) : (
|
|
1458
1486
|
<div className="search__container">
|
|
1459
1487
|
{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';
|
|
@@ -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;
|