@qite/tide-booking-component 1.4.112 → 1.4.114

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 (43) hide show
  1. package/build/build-cjs/index.js +425 -263
  2. package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  3. package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +2 -0
  4. package/build/build-cjs/src/search-results/components/search-results-container/search-results-container.d.ts +4 -1
  5. package/build/build-cjs/src/search-results/index.d.ts +1 -0
  6. package/build/build-cjs/src/shared/booking/summary.d.ts +1 -0
  7. package/build/build-cjs/src/shared/utils/booking-summary.d.ts +1 -0
  8. package/build/build-esm/index.js +424 -263
  9. package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +1 -0
  10. package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +2 -0
  11. package/build/build-esm/src/search-results/components/search-results-container/search-results-container.d.ts +4 -1
  12. package/build/build-esm/src/search-results/index.d.ts +1 -0
  13. package/build/build-esm/src/shared/booking/summary.d.ts +1 -0
  14. package/build/build-esm/src/shared/utils/booking-summary.d.ts +1 -0
  15. package/package.json +2 -2
  16. package/src/search-results/components/book-packaging-entry/index.tsx +60 -22
  17. package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +27 -16
  18. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +5 -2
  19. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +11 -2
  20. package/src/search-results/components/search-results-container/search-results-container.tsx +22 -8
  21. package/src/search-results/index.tsx +3 -2
  22. package/src/shared/booking/summary.tsx +4 -2
  23. package/src/shared/booking/travelers-form.tsx +12 -6
  24. package/src/shared/components/flyin/flyin.tsx +10 -1
  25. package/src/shared/translations/ar-SA.json +3 -1
  26. package/src/shared/translations/da-DK.json +3 -1
  27. package/src/shared/translations/de-DE.json +3 -1
  28. package/src/shared/translations/en-GB.json +3 -1
  29. package/src/shared/translations/es-ES.json +3 -1
  30. package/src/shared/translations/fr-BE.json +3 -1
  31. package/src/shared/translations/fr-FR.json +3 -1
  32. package/src/shared/translations/is-IS.json +3 -1
  33. package/src/shared/translations/it-IT.json +3 -1
  34. package/src/shared/translations/ja-JP.json +3 -1
  35. package/src/shared/translations/nl-BE.json +3 -1
  36. package/src/shared/translations/nl-NL.json +3 -1
  37. package/src/shared/translations/no-NO.json +3 -1
  38. package/src/shared/translations/pl-PL.json +3 -1
  39. package/src/shared/translations/pt-PT.json +3 -1
  40. package/src/shared/translations/sv-SE.json +3 -1
  41. package/src/shared/utils/booking-summary.tsx +11 -0
  42. package/styles/components/_booking.scss +10 -0
  43. package/styles/components/_select-wrapper.scss +5 -0
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { SearchSeed } from '../../types';
3
3
  interface BookPackagingEntryProps {
4
4
  activeSearchSeed: SearchSeed | null;
5
+ isLoading: boolean;
5
6
  isConfirmationPage?: boolean;
6
7
  }
7
8
  declare const BookPackagingEntry: React.FC<BookPackagingEntryProps>;
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import { SearchSeed } from '../../types';
3
+ import { PackagingAccommodationResponse } from '@qite/tide-client';
3
4
  interface WLSidebarProps {
4
5
  activeSearchSeed: SearchSeed | null;
6
+ packagingAccoResult: PackagingAccommodationResponse | null;
5
7
  }
6
8
  declare const WLSidebar: React.FC<WLSidebarProps>;
7
9
  export default WLSidebar;
@@ -1,3 +1,6 @@
1
1
  import React from 'react';
2
- declare const SearchResultsContainer: React.FC;
2
+ interface SearchResultsContainerProps {
3
+ onBookingStarted?: () => void;
4
+ }
5
+ declare const SearchResultsContainer: React.FC<SearchResultsContainerProps>;
3
6
  export default SearchResultsContainer;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { SearchResultsConfiguration } from './types';
3
3
  interface SearchResultsProps {
4
4
  configuration: SearchResultsConfiguration;
5
+ onBookingStarted?: () => void;
5
6
  }
6
7
  declare const SearchResults: React.FC<SearchResultsProps>;
7
8
  export default SearchResults;
@@ -25,6 +25,7 @@ export interface SharedSummaryProps {
25
25
  isOffer?: boolean;
26
26
  customValidateText?: string;
27
27
  isSubmitting?: boolean;
28
+ skipPayment?: boolean;
28
29
  userValidated?: boolean;
29
30
  renderOptions: () => ReactNode;
30
31
  renderPreviousButton: () => ReactNode;
@@ -1 +1,2 @@
1
1
  export declare const renderEditablePackagingEntrySummaryOptions: (editablePackagingEntry: any, priceDetails: any, translations: any) => any;
2
+ export declare const getImageSrcFromHtml: (html?: string | null) => string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-booking-component",
3
- "version": "1.4.112",
3
+ "version": "1.4.114",
4
4
  "description": "React Booking wizard & Booking product component for Tide",
5
5
  "main": "build/build-cjs/index.js",
6
6
  "types": "build/build-cjs/src/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "devDependencies": {
30
30
  "@jsonurl/jsonurl": "^1.1.4",
31
31
  "@popperjs/core": "^2.10.2",
32
- "@qite/tide-client": "^1.1.172",
32
+ "@qite/tide-client": "^1.1.173",
33
33
  "@reduxjs/toolkit": "^2.8.2",
34
34
  "@rollup/plugin-commonjs": "^19.0.1",
35
35
  "@rollup/plugin-json": "^4.1.0",
@@ -16,13 +16,23 @@ import { useFormik } from 'formik';
16
16
  import { TravelersFormValues } from '../../../booking-wizard/types';
17
17
  import { setBookingNumber, setCurrentStep, setEditablePackagingEntry } from '../../store/search-results-slice';
18
18
  import validateForm from '../../../booking-wizard/features/travelers-form/validate-form';
19
- import { bookPackagingEntry, CountryItem, getCountries, PackagingEntry, PackagingRequestBase, TideClientConfig } from '@qite/tide-client';
19
+ import {
20
+ bookPackagingEntry,
21
+ CountryItem,
22
+ getCountries,
23
+ PackagingAccommodationResponse,
24
+ PackagingEntry,
25
+ PackagingRequestBase,
26
+ TideClientConfig
27
+ } from '@qite/tide-client';
20
28
  import SharedSummary from '../../../shared/booking/summary';
21
29
  import { renderEditablePackagingEntrySummaryOptions } from '../../../shared/utils/booking-summary';
22
30
  import SharedConfirmation from '../../../shared/booking/shared-confirmation';
31
+ import Spinner from '../spinner/spinner';
23
32
 
24
33
  interface BookPackagingEntryProps {
25
34
  activeSearchSeed: SearchSeed | null;
35
+ isLoading: boolean;
26
36
  isConfirmationPage?: boolean;
27
37
  }
28
38
 
@@ -49,54 +59,70 @@ const travellersSettings: SharedTravelersSettings = {
49
59
  mainBookerFormFields
50
60
  };
51
61
 
52
- const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isConfirmationPage }) => {
62
+ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSeed, isLoading, isConfirmationPage }) => {
53
63
  const context = useContext(SearchResultsConfigurationContext);
54
64
  const dispatch = useDispatch();
55
- const { editablePackagingEntry, priceDetails, currentStep, bookingNumber } = useSelector((state: SearchResultsRootState) => state.searchResults);
65
+
66
+ const { editablePackagingEntry, priceDetails, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = useSelector(
67
+ (state: SearchResultsRootState) => state.searchResults
68
+ );
56
69
 
57
70
  const [countries, setCountries] = useState<CountryItem[]>([]);
58
71
  const [userValidated, setUserValidated] = useState(true);
59
72
  const [remarks, setRemarks] = useState('');
60
73
  const [isSubmitting, setIsSubmitting] = useState(false);
74
+ const [selectedPackagingAccoResult, setSelectedPackagingAccoResult] = useState<PackagingAccommodationResponse | null>(null);
61
75
 
62
- if (!context || !editablePackagingEntry || !priceDetails) return null;
63
-
64
- const config: TideClientConfig = {
65
- host: context.tideConnection.host,
66
- apiKey: context.tideConnection.apiKey
67
- };
76
+ const translations = useMemo(() => getTranslations(context?.languageCode ?? 'en-GB'), [context?.languageCode]);
68
77
 
69
- const translations = getTranslations(context?.languageCode ?? 'en-GB');
70
78
  const stepLabels = [translations.STEPS.PERSONAL_DETAILS, translations.STEPS.SUMMARY, translations.STEPS.CONFIRMATION];
71
79
 
72
- console.log('editablePackagingEntry in WLSidebar:', editablePackagingEntry);
73
- console.log('priceDetails in WLSidebar:', priceDetails);
80
+ const config: TideClientConfig | null = useMemo(() => {
81
+ if (!context) return null;
74
82
 
75
- const initialValues = useMemo(() => createInitialValuesFromEditablePackagingEntry(editablePackagingEntry), [editablePackagingEntry?.transactionId]);
83
+ return {
84
+ host: context.tideConnection.host,
85
+ apiKey: context.tideConnection.apiKey
86
+ };
87
+ }, [context]);
88
+
89
+ const initialValues = useMemo(() => {
90
+ if (!editablePackagingEntry) {
91
+ return {} as TravelersFormValues;
92
+ }
93
+
94
+ return createInitialValuesFromEditablePackagingEntry(editablePackagingEntry);
95
+ }, [editablePackagingEntry?.transactionId]);
76
96
 
77
97
  const formik = useFormik<TravelersFormValues>({
78
98
  initialValues,
79
99
  enableReinitialize: true,
80
100
  validate: (values) => validateForm(values, false, 'b2c', translations, travellersSettings.formFields, travellersSettings.mainBookerFormFields),
81
101
  onSubmit: (values) => {
102
+ if (!editablePackagingEntry) return;
103
+
82
104
  dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, values)));
105
+
83
106
  dispatch(setCurrentStep(1));
84
107
  }
85
108
  });
86
109
 
87
110
  useEffect(() => {
88
- if (!context) return;
111
+ if (!context || !config) return;
112
+
89
113
  const controller = new AbortController();
90
114
 
91
115
  (async () => {
92
116
  try {
93
117
  const result = await getCountries(config, controller.signal);
94
118
  setCountries(result.items);
95
- } catch {}
119
+ } catch {
120
+ // optionally handle error
121
+ }
96
122
  })();
97
123
 
98
124
  return () => controller.abort();
99
- }, []);
125
+ }, [context, config]);
100
126
 
101
127
  useEffect(() => {
102
128
  if (isConfirmationPage) {
@@ -104,6 +130,15 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
104
130
  }
105
131
  }, [isConfirmationPage, dispatch]);
106
132
 
133
+ useEffect(() => {
134
+ const selectedPackagingAccoResult = packagingAccoResults?.find((result) => result.code === selectedPackagingAccoResultCode);
135
+ if (selectedPackagingAccoResult) {
136
+ setSelectedPackagingAccoResult(selectedPackagingAccoResult);
137
+ }
138
+ }, [selectedPackagingAccoResultCode, packagingAccoResults]);
139
+
140
+ if (!context || !editablePackagingEntry || !priceDetails || !config) return <Spinner label={translations.SUMMARY.PROCESS_BOOKING} />;
141
+
107
142
  const handleSummarySubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
108
143
  e.preventDefault();
109
144
 
@@ -141,6 +176,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
141
176
  agentId: context.agentId,
142
177
  payload: updatedEditablePackagingEntry
143
178
  } as PackagingRequestBase<PackagingEntry>;
179
+
144
180
  const bookingResponse = await bookPackagingEntry(config, request);
145
181
 
146
182
  dispatch(setBookingNumber(bookingResponse.number));
@@ -149,11 +185,10 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
149
185
  window.location.href = bookingResponse.paymentUrl;
150
186
  } else {
151
187
  dispatch(setCurrentStep(2));
188
+ setIsSubmitting(false);
152
189
  }
153
190
  } catch (error) {
154
191
  dispatch(setCurrentStep(3));
155
- } finally {
156
- setIsSubmitting(false);
157
192
  }
158
193
  };
159
194
 
@@ -170,6 +205,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
170
205
  {step + 1}.&nbsp;{stepLabels[step]}
171
206
  </>
172
207
  )}>
208
+ {isConfirmationPage && isLoading && <Spinner label={translations.SUMMARY.PROCESS_BOOKING} />}
173
209
  {currentStep === 0 && (
174
210
  <SharedTravelersForm
175
211
  formik={formik}
@@ -188,6 +224,7 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
188
224
  translations={translations}
189
225
  travelerFormValues={formik.values}
190
226
  isSubmitting={isSubmitting}
227
+ skipPayment={!context.generatePaymentUrl}
191
228
  userValidated={userValidated}
192
229
  remarks={remarks}
193
230
  enableVoucher={false}
@@ -204,22 +241,23 @@ const BookPackagingEntry: React.FC<BookPackagingEntryProps> = ({ activeSearchSee
204
241
  )}
205
242
  />
206
243
  )}
244
+
207
245
  {currentStep === 2 && (
208
246
  <SharedConfirmation
209
247
  bookingNumber={bookingNumber ?? editablePackagingEntry?.dossierNumber ?? ''}
210
248
  isOption={false}
211
249
  isOffer={false}
212
250
  translations={translations.CONFIRMATION}
213
- // companyContactPhone={context?.companyContactPhone || ''}
214
- // companyContactEmail={context?.companyContactEmail || ''}
215
- // homeUrl={context?.homeUrl || '/'}
216
251
  />
217
252
  )}
253
+
218
254
  {currentStep === 3 && <div>{/* error */}</div>}
219
255
  </BookingPanel>
220
256
  </div>
257
+
221
258
  <div className="backdrop" id="backdrop"></div>
222
- <WLSidebar activeSearchSeed={activeSearchSeed} />
259
+
260
+ <WLSidebar activeSearchSeed={activeSearchSeed} packagingAccoResult={selectedPackagingAccoResult} />
223
261
  </div>
224
262
  </div>
225
263
  );
@@ -10,12 +10,14 @@ import { SearchSeed } from '../../types';
10
10
  import { getTravelersText } from '../../../booking-wizard/features/sidebar/sidebar-util';
11
11
  import { RoomTraveler } from '../../../booking-wizard/types';
12
12
 
13
- import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingEntryLine } from '@qite/tide-client';
13
+ import { BookingPackageFlightMetaData, BookingPriceDetail, PackagingAccommodationResponse, PackagingEntryLine, PortalQsmType } from '@qite/tide-client';
14
14
  import Spinner from '../spinner/spinner';
15
15
  import SharedSidebar from '../../../shared/booking/shared-sidebar';
16
+ import { getImageSrcFromHtml } from '../../../shared/utils/booking-summary';
16
17
 
17
18
  interface WLSidebarProps {
18
19
  activeSearchSeed: SearchSeed | null;
20
+ packagingAccoResult: PackagingAccommodationResponse | null;
19
21
  }
20
22
 
21
23
  const mapToSidebarFlightMetaData = (entryLine: PackagingEntryLine) => {
@@ -66,18 +68,12 @@ const selectSeparatePackagePriceDetails = (priceDetails: BookingPriceDetail[]) =
66
68
  return result;
67
69
  };
68
70
 
69
- const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
71
+ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed, packagingAccoResult }) => {
70
72
  const context = useContext(SearchResultsConfigurationContext);
71
- if (!context) {
72
- return null;
73
- }
74
- const translations = getTranslations(context.languageCode ?? 'en-GB');
73
+
75
74
  const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
76
75
 
77
- // Map editablePackagingEntry to sidebar props (example, adjust as needed)
78
- if (!editablePackagingEntry) {
79
- return null;
80
- }
76
+ const translations = getTranslations(context?.languageCode ?? 'en-GB');
81
77
 
82
78
  const sortedLines = useMemo(() => {
83
79
  return [...(editablePackagingEntry?.lines ?? [])].sort((a, b) => {
@@ -92,16 +88,31 @@ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
92
88
  });
93
89
  }, [editablePackagingEntry]);
94
90
 
91
+ const accoImage = useMemo(() => {
92
+ return getImageSrcFromHtml(packagingAccoResult?.contents);
93
+ }, [packagingAccoResult?.contents]);
94
+
95
+ if (!context || !editablePackagingEntry) {
96
+ return null;
97
+ }
98
+
95
99
  const firstEntryLine = first(sortedLines);
96
100
  const accommodationLines = editablePackagingEntry.lines.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE);
97
101
  const accommodationLine = first(accommodationLines) ?? firstEntryLine;
98
102
 
99
103
  const location =
100
- accommodationLine?.location?.name ??
101
- accommodationLine?.oord?.name ??
102
- accommodationLine?.region?.name ??
103
- accommodationLine?.country?.name ??
104
- firstEntryLine?.location?.name;
104
+ context.searchConfiguration.qsmType === PortalQsmType.Accommodation
105
+ ? packagingAccoResult?.name ??
106
+ accommodationLine?.location?.name ??
107
+ accommodationLine?.oord?.name ??
108
+ accommodationLine?.region?.name ??
109
+ accommodationLine?.country?.name ??
110
+ firstEntryLine?.location?.name
111
+ : accommodationLine?.location?.name ??
112
+ accommodationLine?.oord?.name ??
113
+ accommodationLine?.region?.name ??
114
+ accommodationLine?.country?.name ??
115
+ firstEntryLine?.location?.name;
105
116
 
106
117
  const rooms =
107
118
  activeSearchSeed?.rooms.map((room) => {
@@ -136,7 +147,7 @@ const WLSidebar: React.FC<WLSidebarProps> = ({ activeSearchSeed }) => {
136
147
  return (
137
148
  <SharedSidebar
138
149
  productName={location ?? ''}
139
- thumbnailUrl={context.destinationImage?.url}
150
+ thumbnailUrl={accoImage ?? context.destinationImage?.url}
140
151
  translations={translations}
141
152
  travelerRooms={travelerRooms}
142
153
  startDateText={first(sortedLines)?.from && formatDate(new Date(first(sortedLines)!.from))}
@@ -94,9 +94,12 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
94
94
  }, [searchResults, selectedReturnKey]);
95
95
 
96
96
  const firstResultDate = uniqueOutwardFlights.length > 0 ? uniqueOutwardFlights[0].outward.segments[0].departureDateTime : null;
97
+ const firstResultReturnDate = uniqueReturnFlights.length > 0 ? uniqueReturnFlights[0].return.segments[0].departureDateTime : null;
97
98
 
98
99
  const firstResultDay = firstResultDate ? format(firstResultDate, 'd') : null;
99
100
  const firstResultMonth = firstResultDate ? format(firstResultDate, 'MMM') : null;
101
+ const firstResultReturnDay = firstResultReturnDate ? format(firstResultReturnDate, 'd') : null;
102
+ const firstResultReturnMonth = firstResultReturnDate ? format(firstResultReturnDate, 'MMM') : null;
100
103
 
101
104
  return (
102
105
  <>
@@ -138,8 +141,8 @@ const IndependentFlightSelection: React.FC<IndependentFlightSelectionProps> = ({
138
141
 
139
142
  <div className="search__results__label search__results__label--secondary">
140
143
  <div className="search__results__label__date">
141
- {/* <p className="search__results__label__date-date">{format(day, 'd', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p>
142
- <p>{format(day, 'MMM', { locale: getLocale(context?.languageCode ?? 'en-GB') })}</p> */}
144
+ <p className="search__results__label__date-date">{firstResultReturnDay}</p>
145
+ <p>{firstResultReturnMonth}</p>
143
146
  </div>
144
147
  <div className="search__results__label__text">
145
148
  <Icon name="ui-flight" height={16} fill="white" />
@@ -175,12 +175,21 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
175
175
  const visibleResults = React.useMemo(() => {
176
176
  const shouldShowAll = context?.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight || isFlyIn;
177
177
 
178
+ let filteredMapperResults = mappedResults;
179
+
180
+ if (selectedPackagingAccoResult) {
181
+ filteredMapperResults = mappedResults.filter((result) => result.code !== selectedPackagingAccoResult.code);
182
+ }
183
+
178
184
  if (shouldShowAll) {
179
- return mappedResults;
185
+ if (isFlyIn) {
186
+ return mappedResults;
187
+ }
188
+ return filteredMapperResults;
180
189
  }
181
190
 
182
191
  if (selectedPackagingAccoResult) {
183
- return mappedResults.filter((result) => result.code !== selectedPackagingAccoResult.code).slice(0, 2);
192
+ return filteredMapperResults.filter((result) => result.code !== selectedPackagingAccoResult.code).slice(0, 2);
184
193
  }
185
194
 
186
195
  return mappedResults.slice(0, 3);
@@ -128,7 +128,11 @@ type BuildPackagingEntryPartialArgs = {
128
128
  language: string;
129
129
  };
130
130
 
131
- const SearchResultsContainer: React.FC = () => {
131
+ interface SearchResultsContainerProps {
132
+ onBookingStarted?: () => void;
133
+ }
134
+
135
+ const SearchResultsContainer: React.FC<SearchResultsContainerProps> = ({ onBookingStarted }) => {
132
136
  const currentSearch = typeof window !== 'undefined' ? window.location.search : '';
133
137
 
134
138
  const dispatch = useDispatch();
@@ -361,8 +365,8 @@ const SearchResultsContainer: React.FC = () => {
361
365
  if (typeof window !== 'undefined') {
362
366
  window.scrollTo(0, 0);
363
367
  }
364
- var adults = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 18).length;
365
- var kids = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 2 && x.age! < 18).length;
368
+ var adults = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 12).length;
369
+ var kids = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 2 && x.age! < 12).length;
366
370
  var babies = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! < 2).length;
367
371
 
368
372
  return {
@@ -812,6 +816,12 @@ const SearchResultsContainer: React.FC = () => {
812
816
  }
813
817
  }, [context?.packagingEntry]);
814
818
 
819
+ useEffect(() => {
820
+ if (bookPackagingEntry && onBookingStarted) {
821
+ onBookingStarted();
822
+ }
823
+ }, [bookPackagingEntry, onBookingStarted]);
824
+
815
825
  // separate detailsCall
816
826
  useEffect(() => {
817
827
  const fetchDetails = async () => {
@@ -1435,12 +1445,12 @@ const SearchResultsContainer: React.FC = () => {
1435
1445
  seed.rooms?.forEach((room, roomIndex) => {
1436
1446
  const paxIds = room.pax.map((_, paxIndex) => {
1437
1447
  const id = paxId++;
1438
-
1439
1448
  pax.push({
1440
1449
  id,
1441
- firstName: '',
1442
- lastName: '',
1443
- dateOfBirth: null,
1450
+ firstName: _.firstName || '',
1451
+ lastName: _.lastName || '',
1452
+ dateOfBirth: _.dateOfBirth || null,
1453
+ age: _.age || null,
1444
1454
  isMainBooker: roomIndex === 0 && paxIndex === 0
1445
1455
  });
1446
1456
 
@@ -1479,7 +1489,11 @@ const SearchResultsContainer: React.FC = () => {
1479
1489
  {context && (
1480
1490
  <div className="search">
1481
1491
  {bookPackagingEntry ? (
1482
- <BookPackagingEntry activeSearchSeed={activeSearchSeed} isConfirmationPage={isBookingConfirmation} />
1492
+ <BookPackagingEntry
1493
+ activeSearchSeed={activeSearchSeed}
1494
+ isLoading={itineraryIsLoading || pricesAreLoading}
1495
+ isConfirmationPage={isBookingConfirmation}
1496
+ />
1483
1497
  ) : (
1484
1498
  <div className="search__container">
1485
1499
  {context.searchConfiguration.qsmType === PortalQsmType.Flight && (
@@ -7,15 +7,16 @@ import { createSearchResultsStore } from './store/search-results-store';
7
7
 
8
8
  interface SearchResultsProps {
9
9
  configuration: SearchResultsConfiguration;
10
+ onBookingStarted?: () => void;
10
11
  }
11
12
 
12
- const SearchResults: React.FC<SearchResultsProps> = ({ configuration }) => {
13
+ const SearchResults: React.FC<SearchResultsProps> = ({ configuration, onBookingStarted }) => {
13
14
  const store = React.useMemo(() => createSearchResultsStore(), []);
14
15
 
15
16
  return (
16
17
  <Provider store={store}>
17
18
  <SearchResultsConfigurationContext.Provider value={configuration}>
18
- <SearchResultsContainer />
19
+ <SearchResultsContainer onBookingStarted={onBookingStarted} />
19
20
  </SearchResultsConfigurationContext.Provider>
20
21
  </Provider>
21
22
  );
@@ -1,9 +1,9 @@
1
1
  import { compact, findIndex, isEmpty, isNil, uniqBy } from 'lodash';
2
2
  import React, { ReactNode, useEffect, useState } from 'react';
3
3
  import { SummaryCheckbox, TravelersFormValues } from '../../booking-wizard/types';
4
- import Loader from '../components/loader';
5
4
  import { buildClassName } from '../utils/class-util';
6
5
  import Icon from '../components/icon';
6
+ import Spinner from '../../search-results/components/spinner/spinner';
7
7
 
8
8
  export interface SummaryNotification {
9
9
  id: number;
@@ -32,6 +32,7 @@ export interface SharedSummaryProps {
32
32
  isOffer?: boolean;
33
33
  customValidateText?: string;
34
34
  isSubmitting?: boolean;
35
+ skipPayment?: boolean;
35
36
  userValidated?: boolean;
36
37
  renderOptions: () => ReactNode;
37
38
  renderPreviousButton: () => ReactNode;
@@ -66,6 +67,7 @@ const SharedSummary: React.FC<SharedSummaryProps> = ({
66
67
  isOffer = false,
67
68
  customValidateText,
68
69
  isSubmitting = false,
70
+ skipPayment = false,
69
71
  userValidated = true,
70
72
  renderOptions,
71
73
  renderPreviousButton,
@@ -122,7 +124,7 @@ const SharedSummary: React.FC<SharedSummaryProps> = ({
122
124
 
123
125
  return (
124
126
  <>
125
- {isSubmitting && (renderLoader?.() || <Loader />)}
127
+ {isSubmitting && <Spinner label={skipPayment ? translations.SUMMARY.PROCESS_BOOKING : translations.SUMMARY.REDIRECT} />}
126
128
  {!isSubmitting && (
127
129
  <form className="form" name="booking--summary" id="booking--summary" onSubmit={onSubmit}>
128
130
  <div className="form__booking--summary">
@@ -98,13 +98,13 @@ export function createInitialValuesFromRooms(
98
98
  }
99
99
 
100
100
  export function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry: PackagingEntry, agentAdressId?: number): TravelersFormValues {
101
- console.log('editablePackagingEntry?.pax:', editablePackagingEntry?.pax);
102
101
  const pax = editablePackagingEntry?.pax ?? [];
103
102
  const rooms = editablePackagingEntry.rooms.map((room) => {
104
103
  const roomPax = pax.filter((x: PackagingEntryPax) => room.paxIds.includes(x.id));
105
- // TODO children/dateofbirth is missing at this point
104
+ const adults = roomPax.filter((x: PackagingEntryPax) => x.age! >= 18);
105
+ const children = roomPax.filter((x: PackagingEntryPax) => x.age! < 18);
106
106
  return {
107
- adults: roomPax.map((roomTraveler: PackagingEntryPax) => {
107
+ adults: adults.map((roomTraveler: PackagingEntryPax) => {
108
108
  return {
109
109
  id: roomTraveler.id,
110
110
  firstName: roomTraveler.firstName ?? '',
@@ -113,7 +113,15 @@ export function createInitialValuesFromEditablePackagingEntry(editablePackagingE
113
113
  gender: ''
114
114
  } as Traveler;
115
115
  }),
116
- children: [] as Traveler[]
116
+ children: children.map((roomTraveler: PackagingEntryPax) => {
117
+ return {
118
+ id: roomTraveler.id,
119
+ firstName: roomTraveler.firstName ?? '',
120
+ lastName: roomTraveler.lastName ?? '',
121
+ birthDate: roomTraveler.dateOfBirth ? format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '',
122
+ gender: ''
123
+ } as Traveler;
124
+ })
117
125
  };
118
126
  });
119
127
 
@@ -144,8 +152,6 @@ export function createInitialValuesFromEditablePackagingEntry(editablePackagingE
144
152
 
145
153
  export function applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry: PackagingEntry, values: TravelersFormValues) {
146
154
  const travelers = values.rooms.flatMap((room) => [...room.adults, ...room.children]);
147
- console.log('Applying form values:', values);
148
- console.log('editablePackagingEntry:', editablePackagingEntry);
149
155
 
150
156
  return {
151
157
  ...editablePackagingEntry,
@@ -4,6 +4,7 @@ import { useFlightSearch } from '../../../search-results/components/flight/fligh
4
4
  import { useDispatch, useSelector } from 'react-redux';
5
5
  import {
6
6
  resetFilters,
7
+ setBookPackagingEntry,
7
8
  setFilters,
8
9
  setFlyInType,
9
10
  setSelectedFlight,
@@ -125,6 +126,14 @@ const FlyIn: React.FC<FlyInProps> = ({
125
126
  }
126
127
  };
127
128
 
129
+ const onHandleConfirm = () => {
130
+ if (context?.packagingEntry) {
131
+ handleConfirm?.();
132
+ } else {
133
+ dispatch(setBookPackagingEntry(true));
134
+ }
135
+ };
136
+
128
137
  return (
129
138
  <div
130
139
  className={`flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${
@@ -211,7 +220,7 @@ const FlyIn: React.FC<FlyInProps> = ({
211
220
  )}
212
221
 
213
222
  {(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && flyInType === 'acco-details' && (
214
- <AccommodationFlyIn isLoading={detailsLoading} handleConfirm={handleConfirm!} />
223
+ <AccommodationFlyIn isLoading={detailsLoading} handleConfirm={onHandleConfirm} />
215
224
  )}
216
225
 
217
226
  {srpType === PortalQsmType.AccommodationAndFlight && (flyInType === 'flight-outward-results' || flyInType === 'flight-return-results') && (
@@ -225,7 +225,9 @@
225
225
  "VOUCHER_VALIDATE": "تحقق من القسيمة",
226
226
  "ADD_VOUCHER": "إضافة قسيمة",
227
227
  "VOUCHER_VALID": "القسيمة صالحة",
228
- "VOUCHER_INVALID": "القسيمة غير صالحة"
228
+ "VOUCHER_INVALID": "القسيمة غير صالحة",
229
+ "REDIRECT": "إعادة التوجيه إلى مزود الدفع...",
230
+ "PROCESS_BOOKING": "يرجى الانتظار، جاري معالجة حجزك"
229
231
  },
230
232
  "CONFIRMATION": {
231
233
  "TITLE_TEXT_OFFER": "تم طلب عرضك رقم {0}",
@@ -225,7 +225,9 @@
225
225
  "VOUCHER_VALIDATE": "Valider voucher",
226
226
  "ADD_VOUCHER": "Tilføj voucher",
227
227
  "VOUCHER_VALID": "Voucher er gyldig",
228
- "VOUCHER_INVALID": "Voucher er ikke gyldig"
228
+ "VOUCHER_INVALID": "Voucher er ikke gyldig",
229
+ "REDIRECT": "Omdirigerer til betalingsudbyder...",
230
+ "PROCESS_BOOKING": "Vent venligst, din booking behandles"
229
231
  },
230
232
  "CONFIRMATION": {
231
233
  "TITLE_TEXT_OFFER": "Dit tilbud med nummer {0} er blevet anmodet",
@@ -225,7 +225,9 @@
225
225
  "VOUCHER_VALIDATE": "Gutschein prüfen",
226
226
  "ADD_VOUCHER": "Gutschein hinzufügen",
227
227
  "VOUCHER_VALID": "Gutschein ist gültig",
228
- "VOUCHER_INVALID": "Gutschein ist ungültig"
228
+ "VOUCHER_INVALID": "Gutschein ist ungültig",
229
+ "REDIRECT": "Weiterleitung zum Zahlungsanbieter...",
230
+ "PROCESS_BOOKING": "Bitte warten Sie, Ihre Buchung wird bearbeitet"
229
231
  },
230
232
  "CONFIRMATION": {
231
233
  "TITLE_TEXT_OFFER": "Ihr Angebot mit der Nummer {0} wurde angefordert",
@@ -229,7 +229,9 @@
229
229
  "VOUCHER_VALIDATE": "Validate voucher",
230
230
  "ADD_VOUCHER": "Add voucher",
231
231
  "VOUCHER_VALID": "Voucher is valid",
232
- "VOUCHER_INVALID": "Voucher is not valid"
232
+ "VOUCHER_INVALID": "Voucher is not valid",
233
+ "REDIRECT": "Redirecting to payment provider...",
234
+ "PROCESS_BOOKING": "Please wait, your booking is being processed"
233
235
  },
234
236
  "CONFIRMATION": {
235
237
  "TITLE_TEXT_OFFER": "Your quote with number {0} has been requested",
@@ -225,7 +225,9 @@
225
225
  "VOUCHER_VALIDATE": "Validar vale",
226
226
  "ADD_VOUCHER": "Agregar vale",
227
227
  "VOUCHER_VALID": "Vale válido",
228
- "VOUCHER_INVALID": "Vale no válido"
228
+ "VOUCHER_INVALID": "Vale no válido",
229
+ "REDIRECT": "Redirigiendo al proveedor de pagos...",
230
+ "PROCESS_BOOKING": "Por favor, espere, su reserva se está procesando"
229
231
  },
230
232
  "CONFIRMATION": {
231
233
  "TITLE_TEXT_OFFER": "Su presupuesto con número {0} ha sido solicitado",