@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.
Files changed (53) hide show
  1. package/build/build-cjs/index.js +2316 -1555
  2. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  3. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  4. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -1
  5. package/build/build-cjs/src/search-results/types.d.ts +3 -0
  6. package/build/build-cjs/src/shared/booking/shared-confirmation.d.ts +25 -0
  7. package/build/build-cjs/src/shared/booking/summary.d.ts +43 -0
  8. package/build/build-cjs/src/shared/booking/travelers-form.d.ts +93 -0
  9. package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
  10. package/build/build-cjs/src/shared/utils/localization-util.d.ts +6 -0
  11. package/build/build-esm/index.js +2213 -1453
  12. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  13. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  14. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -1
  15. package/build/build-esm/src/search-results/types.d.ts +3 -0
  16. package/build/build-esm/src/shared/booking/shared-confirmation.d.ts +25 -0
  17. package/build/build-esm/src/shared/booking/summary.d.ts +43 -0
  18. package/build/build-esm/src/shared/booking/travelers-form.d.ts +93 -0
  19. package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
  20. package/build/build-esm/src/shared/utils/localization-util.d.ts +6 -0
  21. package/package.json +2 -2
  22. package/src/booking-wizard/components/step-indicator.tsx +1 -1
  23. package/src/booking-wizard/components/step-route.tsx +1 -1
  24. package/src/booking-wizard/features/confirmation/confirmation.tsx +11 -55
  25. package/src/booking-wizard/features/sidebar/index.tsx +1 -1
  26. package/src/booking-wizard/features/summary/summary.tsx +1 -1
  27. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +84 -1010
  28. package/src/search-results/components/book-packaging-entry/index.tsx +192 -11
  29. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +1 -4
  30. package/src/search-results/components/group-tour/group-tour-card.tsx +1 -1
  31. package/src/search-results/components/group-tour/group-tour-results.tsx +1 -1
  32. package/src/search-results/components/search-results-container/search-results-container.tsx +42 -14
  33. package/src/search-results/store/search-results-slice.ts +8 -2
  34. package/src/search-results/types.ts +4 -0
  35. package/src/shared/booking/shared-confirmation.tsx +105 -0
  36. package/src/shared/booking/summary.tsx +380 -0
  37. package/src/shared/booking/travelers-form.tsx +870 -0
  38. package/src/shared/components/flyin/flyin.tsx +8 -9
  39. package/src/shared/components/flyin/packaging-flights-flyin.tsx +4 -4
  40. package/src/shared/utils/booking-summary.tsx +46 -0
  41. package/src/shared/utils/tide-api-utils.ts +2 -2
  42. package/styles/components/_dropdown.scss +5 -0
  43. package/styles/components/_flyin.scss +43 -0
  44. package/styles/components/_search.scss +5 -0
  45. /package/build/build-cjs/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  46. /package/build/build-cjs/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  47. /package/build/build-cjs/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  48. /package/build/build-esm/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  49. /package/build/build-esm/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  50. /package/build/build-esm/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  51. /package/src/shared/booking/{BookingPanel.tsx → booking-panel.tsx} +0 -0
  52. /package/src/shared/booking/{Sidebar.tsx → shared-sidebar.tsx} +0 -0
  53. /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/BookingPanel';
7
- import StepIndicators from '../../../shared/booking/StepIndicators';
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 BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed }) => {
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}.&nbsp;{stepLabels[step]}
36
173
  </>
37
174
  )}>
38
- {/* Panel body content goes here */}
39
- <div></div>
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');
@@ -15,7 +15,7 @@ interface GroupTourResultsProps {
15
15
  const GroupTourResults: React.FC<GroupTourResultsProps> = ({ isLoading }) => {
16
16
  const context = useContext(SearchResultsConfigurationContext);
17
17
  if (!context) {
18
- return;
18
+ return null;
19
19
  }
20
20
 
21
21
  if (isLoading) {
@@ -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
- const pax =
1423
- seed.rooms?.flatMap((room, roomIndex) =>
1424
- room.pax.map((_, paxIndex) => ({
1425
- id: paxId++,
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: 0,
1462
+ status: context?.entryStatus,
1463
+ customStatusId: context?.customEntryStatusId,
1438
1464
  bookingDate: null,
1439
1465
  price: 0,
1440
1466
  depositAmount: 0,
1441
1467
  pax,
1442
- lines: []
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;