@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.
Files changed (58) hide show
  1. package/build/build-cjs/index.js +2301 -1565
  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 +2198 -1463
  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 +201 -21
  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 +40 -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/{BookingPanel.tsx → booking-panel.tsx} +1 -1
  36. package/src/shared/booking/shared-confirmation.tsx +105 -0
  37. package/src/shared/booking/summary.tsx +380 -0
  38. package/src/shared/booking/travelers-form.tsx +868 -0
  39. package/src/shared/components/flyin/flyin.tsx +8 -9
  40. package/src/shared/components/flyin/packaging-flights-flyin.tsx +4 -4
  41. package/src/shared/utils/booking-summary.tsx +46 -0
  42. package/src/shared/utils/tide-api-utils.ts +2 -2
  43. package/styles/components/_booking.scss +33 -15
  44. package/styles/components/_cta.scss +2 -2
  45. package/styles/components/_dropdown.scss +5 -0
  46. package/styles/components/_flight-option.scss +1 -1
  47. package/styles/components/_flyin.scss +43 -0
  48. package/styles/components/_search.scss +5 -0
  49. package/styles/components/_step-indicators.scss +41 -15
  50. package/styles/components/_tree.scss +2 -2
  51. /package/build/build-cjs/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  52. /package/build/build-cjs/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  53. /package/build/build-cjs/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  54. /package/build/build-esm/src/shared/booking/{BookingPanel.d.ts → booking-panel.d.ts} +0 -0
  55. /package/build/build-esm/src/shared/booking/{Sidebar.d.ts → shared-sidebar.d.ts} +0 -0
  56. /package/build/build-esm/src/shared/booking/{StepIndicators.d.ts → step-indicators.d.ts} +0 -0
  57. /package/src/shared/booking/{Sidebar.tsx → shared-sidebar.tsx} +0 -0
  58. /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/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, 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 BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed }) => {
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
- <BookingPanel
30
- currentStep={currentStep}
31
- stepLabels={stepLabels}
32
- StepIndicatorsComponent={StepIndicators}
33
- renderTitle={(step) => (
34
- <>
35
- {step + 1}.&nbsp;{stepLabels[step]}
36
- </>
37
- )}>
38
- {/* Panel body content goes here */}
39
- <div></div>
40
- </BookingPanel>
163
+ <div className="booking__panel">
164
+ <BookingPanel
165
+ currentStep={currentStep}
166
+ stepLabels={stepLabels}
167
+ StepIndicatorsComponent={StepIndicators}
168
+ renderTitle={(step) => (
169
+ <>
170
+ {step + 1}.&nbsp;{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');
@@ -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,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
- const pax =
1423
- seed.rooms?.flatMap((room, roomIndex) =>
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: 0,
1460
+ status: context?.entryStatus,
1461
+ customStatusId: context?.customEntryStatusId,
1438
1462
  bookingDate: null,
1439
1463
  price: 0,
1440
1464
  depositAmount: 0,
1441
1465
  pax,
1442
- lines: []
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="booking__panel">
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;