@qite/tide-booking-component 1.4.109 → 1.4.110
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 +1351 -775
- package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-cjs/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
- package/build/build-cjs/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-cjs/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +5 -1
- package/build/build-cjs/src/shared/booking/BookingPanel.d.ts +13 -0
- package/build/build-cjs/src/shared/booking/Sidebar.d.ts +34 -0
- package/build/build-cjs/src/shared/booking/StepIndicators.d.ts +7 -0
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +1335 -770
- package/build/build-esm/src/booking-wizard/components/step-route.d.ts +2 -2
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +2 -1
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +0 -31
- package/build/build-esm/src/search-results/components/book-packaging-entry/index.d.ts +7 -0
- package/build/build-esm/src/search-results/components/book-packaging-entry/wl-sidebar.d.ts +7 -0
- package/build/build-esm/src/search-results/components/spinner/spinner.d.ts +4 -1
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +5 -1
- package/build/build-esm/src/shared/booking/BookingPanel.d.ts +13 -0
- package/build/build-esm/src/shared/booking/Sidebar.d.ts +34 -0
- package/build/build-esm/src/shared/booking/StepIndicators.d.ts +7 -0
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -0
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +2 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +1 -1
- package/src/booking-wizard/components/step-indicator.tsx +10 -31
- package/src/booking-wizard/components/step-route.tsx +39 -14
- package/src/booking-wizard/features/sidebar/index.tsx +10 -4
- package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +2 -2
- package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -5
- package/src/booking-wizard/features/sidebar/sidebar.tsx +331 -326
- package/src/search-results/components/book-packaging-entry/index.tsx +48 -0
- package/src/search-results/components/book-packaging-entry/wl-sidebar.tsx +165 -0
- package/src/search-results/components/excursions/day-by-day-excursions.tsx +6 -2
- package/src/search-results/components/excursions/excursion-results.tsx +1 -1
- package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +12 -3
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +6 -3
- package/src/search-results/components/itinerary/full-itinerary.tsx +1 -1
- package/src/search-results/components/itinerary/index.tsx +13 -12
- package/src/search-results/components/search-results-container/flight-search-results.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +239 -204
- package/src/search-results/components/spinner/spinner.tsx +12 -4
- package/src/search-results/store/search-results-slice.ts +16 -2
- package/src/shared/booking/BookingPanel.tsx +25 -0
- package/src/shared/booking/Sidebar.tsx +432 -0
- package/src/shared/booking/StepIndicators.tsx +30 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +3 -4
- package/src/shared/components/flyin/flights-flyin.tsx +1 -1
- package/src/shared/components/flyin/flyin.tsx +12 -4
- package/src/shared/components/flyin/group-tour-flyin.tsx +3 -4
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +11 -4
- package/src/shared/components/icon.tsx +13 -0
- package/src/shared/translations/ar-SA.json +7 -1
- package/src/shared/translations/da-DK.json +7 -1
- package/src/shared/translations/de-DE.json +7 -1
- package/src/shared/translations/en-GB.json +8 -2
- package/src/shared/translations/es-ES.json +7 -1
- package/src/shared/translations/fr-BE.json +7 -1
- package/src/shared/translations/fr-FR.json +7 -1
- package/src/shared/translations/is-IS.json +7 -1
- package/src/shared/translations/it-IT.json +7 -1
- package/src/shared/translations/ja-JP.json +7 -1
- package/src/shared/translations/nl-BE.json +7 -1
- package/src/shared/translations/nl-NL.json +7 -1
- package/src/shared/translations/no-NO.json +7 -1
- package/src/shared/translations/pl-PL.json +7 -1
- package/src/shared/translations/pt-PT.json +7 -1
- package/src/shared/translations/sv-SE.json +7 -1
- package/src/shared/utils/localization-util.ts +8 -0
- package/styles/components/_loader.scss +82 -0
- package/styles/components/_search.scss +9 -2
- package/styles/content-blocks-variables.scss +14 -14
- /package/build/build-cjs/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /package/build/build-esm/src/{booking-wizard/components → shared/booking}/product-card.d.ts +0 -0
- /package/src/{booking-wizard/components → shared/booking}/product-card.tsx +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { formatPrice } from '../utils/localization-util';
|
|
3
|
+
import { BookingPackageRoom, PackagingEntryLine, ServiceType } from '@qite/tide-client';
|
|
4
|
+
import { buildClassName } from '../utils/class-util';
|
|
5
|
+
import ProductCard from './product-card';
|
|
6
|
+
import { compact, isEmpty } from 'lodash';
|
|
7
|
+
import SidebarFlight from '../../booking-wizard/features/sidebar/sidebar-flight';
|
|
8
|
+
import { getDatePeriodText, getPaxTypeTranslation } from '../../booking-wizard/features/sidebar/sidebar-util';
|
|
9
|
+
import { PricePerPaxType } from '../../booking-wizard/types';
|
|
10
|
+
|
|
11
|
+
interface SharedSidebarProps {
|
|
12
|
+
productName: string;
|
|
13
|
+
thumbnailUrl?: string;
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
travelerRooms?: string[];
|
|
16
|
+
startDateText?: string;
|
|
17
|
+
endDateText?: string;
|
|
18
|
+
departureFlightMetaData?: any;
|
|
19
|
+
returnFlightMetaData?: any;
|
|
20
|
+
basePrice?: number;
|
|
21
|
+
commission?: number;
|
|
22
|
+
totalPrice?: number;
|
|
23
|
+
remainingAmountText?: string;
|
|
24
|
+
includedCosts?: any[];
|
|
25
|
+
extraCosts?: any[];
|
|
26
|
+
deposit?: number;
|
|
27
|
+
accommodations?: BookingPackageRoom[];
|
|
28
|
+
packagingAccommodations?: PackagingEntryLine[];
|
|
29
|
+
includedServiceTypes?: number[];
|
|
30
|
+
isOnRequest?: boolean;
|
|
31
|
+
headerComponent?: React.ReactNode;
|
|
32
|
+
footerComponent?: React.ReactNode;
|
|
33
|
+
loaderComponent?: React.ReactNode;
|
|
34
|
+
isUnavailable?: boolean;
|
|
35
|
+
basePricePerPaxType?: PricePerPaxType[];
|
|
36
|
+
seperateExtraPricePerPaxType?: PricePerPaxType[];
|
|
37
|
+
translations: any;
|
|
38
|
+
agent?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const SharedSidebar: React.FC<SharedSidebarProps> = ({
|
|
42
|
+
productName,
|
|
43
|
+
thumbnailUrl,
|
|
44
|
+
isLoading,
|
|
45
|
+
translations,
|
|
46
|
+
headerComponent,
|
|
47
|
+
travelerRooms,
|
|
48
|
+
startDateText,
|
|
49
|
+
endDateText,
|
|
50
|
+
loaderComponent,
|
|
51
|
+
departureFlightMetaData,
|
|
52
|
+
returnFlightMetaData,
|
|
53
|
+
accommodations,
|
|
54
|
+
packagingAccommodations,
|
|
55
|
+
isOnRequest,
|
|
56
|
+
includedServiceTypes,
|
|
57
|
+
basePrice,
|
|
58
|
+
commission,
|
|
59
|
+
totalPrice,
|
|
60
|
+
includedCosts,
|
|
61
|
+
extraCosts,
|
|
62
|
+
deposit,
|
|
63
|
+
basePricePerPaxType,
|
|
64
|
+
seperateExtraPricePerPaxType,
|
|
65
|
+
isUnavailable,
|
|
66
|
+
agent,
|
|
67
|
+
footerComponent
|
|
68
|
+
}) => {
|
|
69
|
+
if (!translations) return null;
|
|
70
|
+
|
|
71
|
+
const [active, setActive] = useState<boolean>(false);
|
|
72
|
+
|
|
73
|
+
const handleToggleClick = () => {
|
|
74
|
+
setActive(!active);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isFlightOnly = includedServiceTypes?.length === 1 && includedServiceTypes.includes(ServiceType.flight);
|
|
78
|
+
|
|
79
|
+
const canShowPriceBreakdownSection = Boolean(basePrice && basePrice > 0) || !isEmpty(includedCosts) || !isEmpty(extraCosts);
|
|
80
|
+
const canShowTotalPriceSection = Boolean(totalPrice && totalPrice > 0);
|
|
81
|
+
|
|
82
|
+
const remainingAmount = Number(((totalPrice ?? 0) - (deposit ?? 0)).toFixed(2));
|
|
83
|
+
|
|
84
|
+
// TODO: Determine currency code based on the data, for now default to EUR
|
|
85
|
+
let currencyCode = 'EUR';
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className={buildClassName(['booking__sidebar', active && 'booking__sidebar--active'])}>
|
|
89
|
+
{headerComponent}
|
|
90
|
+
|
|
91
|
+
<div className="booking__sidebar-frame">
|
|
92
|
+
<ProductCard productName={productName} thumbnailUrl={thumbnailUrl} handleToggleClick={handleToggleClick} />
|
|
93
|
+
<div className="pricing-summary">
|
|
94
|
+
<div className="pricing-summary__wrapper">
|
|
95
|
+
<div className="pricing-summary__region pricing-summary__region--fade-in">
|
|
96
|
+
<div className="pricing-summary__group">
|
|
97
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.TRAVEL_INFO}</h6>
|
|
98
|
+
{!isEmpty(travelerRooms) &&
|
|
99
|
+
travelerRooms?.map((room, rIndex) => (
|
|
100
|
+
<div className="pricing-summary__row" key={rIndex}>
|
|
101
|
+
<div className="pricing-summary__property">
|
|
102
|
+
{travelerRooms.length > 1 && `${translations.SHARED.ROOM} ${rIndex + 1}`}
|
|
103
|
+
{travelerRooms.length === 1 && translations.ROOM_OPTIONS_FORM.TRAVELER_GROUP}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div className="pricing-summary__value">{room}</div>
|
|
107
|
+
</div>
|
|
108
|
+
))}
|
|
109
|
+
{startDateText && (
|
|
110
|
+
<div className="pricing-summary__row">
|
|
111
|
+
<div className="pricing-summary__property">
|
|
112
|
+
{startDateText && endDateText ? translations.SIDEBAR.DEPARTURE : translations.SIDEBAR.DEPARTURE_SINGLE}
|
|
113
|
+
</div>
|
|
114
|
+
<div className="pricing-summary__value">{startDateText}</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{endDateText && (
|
|
119
|
+
<div className="pricing-summary__row">
|
|
120
|
+
<div className="pricing-summary__property">{translations.SIDEBAR.ARRIVAL}</div>
|
|
121
|
+
<div className="pricing-summary__value">{endDateText}</div>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{/* {lines.map((line: any, idx: number) => (
|
|
126
|
+
<div className="pricing-summary__row" key={line.guid || idx}>
|
|
127
|
+
<div className="pricing-summary__property">
|
|
128
|
+
{line.serviceType === 3 && (
|
|
129
|
+
<>
|
|
130
|
+
{line.productName} <br />
|
|
131
|
+
{line.accommodationName && <span>{line.accommodationName}</span>}
|
|
132
|
+
<br />
|
|
133
|
+
{line.from && line.to && (
|
|
134
|
+
<span>
|
|
135
|
+
{new Date(line.from).toLocaleDateString()} - {new Date(line.to).toLocaleDateString()}
|
|
136
|
+
</span>
|
|
137
|
+
)}
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
{line.serviceType === 7 && line.flightInformation && (
|
|
141
|
+
<>
|
|
142
|
+
<strong>{line.productName}</strong>
|
|
143
|
+
<br />
|
|
144
|
+
{line.flightInformation.flightLines &&
|
|
145
|
+
line.flightInformation.flightLines.map((flight: any, fIdx: number) => (
|
|
146
|
+
<div key={fIdx} style={{ marginBottom: 4 }}>
|
|
147
|
+
{flight.airlineDescription} {flight.flightNumber}
|
|
148
|
+
<br />
|
|
149
|
+
{flight.departureAirportDescription} ({flight.departureAirportCode}) {flight.departureTime} → {flight.arrivalAirportDescription}{' '}
|
|
150
|
+
({flight.arrivalAirportCode}) {flight.arrivalTime}
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
))} */}
|
|
158
|
+
</div>
|
|
159
|
+
{isLoading && loaderComponent}
|
|
160
|
+
|
|
161
|
+
{!isLoading && departureFlightMetaData && (
|
|
162
|
+
<SidebarFlight title={translations.SIDEBAR.DEPARTURE_FLIGHT} flightMetaData={departureFlightMetaData} translations={translations} />
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{!isLoading && returnFlightMetaData && (
|
|
166
|
+
<SidebarFlight title={translations.SIDEBAR.ARRIVAL_FLIGHT} flightMetaData={returnFlightMetaData} translations={translations} />
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{accommodations && (
|
|
170
|
+
<div className="pricing-summary__group">
|
|
171
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
|
|
172
|
+
{accommodations.map((accommodation) => {
|
|
173
|
+
let option = accommodation.options.find((x) => x.isSelected);
|
|
174
|
+
return (
|
|
175
|
+
<div key={accommodation.index}>
|
|
176
|
+
<div className="pricing-summary__row">
|
|
177
|
+
<div className="pricing-summary__property">
|
|
178
|
+
{option?.accommodationName}
|
|
179
|
+
{isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
|
|
180
|
+
{option?.isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
|
|
181
|
+
</div>
|
|
182
|
+
{/*<div className="pricing-summary__value">
|
|
183
|
+
{option && option.price > 0 && formatPrice(option?.price)}
|
|
184
|
+
</div>*/}
|
|
185
|
+
</div>
|
|
186
|
+
<div className="pricing-summary__row">
|
|
187
|
+
<div className="price-summarty__property">{option?.regimeName}</div>
|
|
188
|
+
<div className="price-summary__value">{!isFlightOnly && getDatePeriodText(translations, option?.from, option?.to, true)}</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
{packagingAccommodations && (
|
|
196
|
+
<div className="pricing-summary__group">
|
|
197
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.ACCOMMODATION}</h6>
|
|
198
|
+
{packagingAccommodations.map((accommodation) => {
|
|
199
|
+
return (
|
|
200
|
+
<div key={accommodation.guid}>
|
|
201
|
+
<div className="pricing-summary__row">
|
|
202
|
+
<div className="pricing-summary__property">
|
|
203
|
+
{accommodation?.accommodationName}
|
|
204
|
+
{isOnRequest ? ` (${translations.SIDEBAR.ON_REQUEST})` : ''}
|
|
205
|
+
</div>
|
|
206
|
+
{/*<div className="pricing-summary__value">
|
|
207
|
+
{option && option.price > 0 && formatPrice(option?.price)}
|
|
208
|
+
</div>*/}
|
|
209
|
+
</div>
|
|
210
|
+
<div className="pricing-summary__row">
|
|
211
|
+
<div className="price-summarty__property">{accommodation?.regimeName}</div>
|
|
212
|
+
<div className="price-summary__value">
|
|
213
|
+
{!isFlightOnly && getDatePeriodText(translations, accommodation?.from, accommodation?.to, true)}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{!isLoading && canShowPriceBreakdownSection && (
|
|
224
|
+
<div className={`pricing-summary__region ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
225
|
+
{basePrice !== undefined && basePrice > 0 && (
|
|
226
|
+
<div className="pricing-summary__group">
|
|
227
|
+
<div className="pricing-summary__row">
|
|
228
|
+
<div className="pricing-summary__property">
|
|
229
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.BASE_PRICE}</h6>
|
|
230
|
+
</div>
|
|
231
|
+
<div className="pricing-summary__value">{formatPrice(basePrice, currencyCode)}</div>
|
|
232
|
+
</div>
|
|
233
|
+
{basePricePerPaxType &&
|
|
234
|
+
basePricePerPaxType.map((ppt, index) => (
|
|
235
|
+
<React.Fragment key={`${ppt.paxType}-${index}`}>
|
|
236
|
+
<div className="pricing-summary__row">
|
|
237
|
+
<div className="pricing-summary__property">
|
|
238
|
+
{ppt.numberOfPax} {getPaxTypeTranslation(translations, ppt.paxType, ppt.numberOfPax)}
|
|
239
|
+
</div>
|
|
240
|
+
<div className="pricing-summary__value">{formatPrice(ppt.pricePerPaxType, currencyCode)}</div>
|
|
241
|
+
</div>
|
|
242
|
+
{ppt.details.map((detail, dIndex) => (
|
|
243
|
+
<div className="pricing-summary__row pricing-summary__row--sub" key={`${ppt.paxType}-${index}-${dIndex}`}>
|
|
244
|
+
<div className="pricing-summary__property">
|
|
245
|
+
{detail.numberOfPax}x {detail.description}
|
|
246
|
+
</div>
|
|
247
|
+
<div className="pricing-summary__value">{formatPrice(detail.price / detail.numberOfPax, currencyCode)}</div>
|
|
248
|
+
</div>
|
|
249
|
+
))}
|
|
250
|
+
</React.Fragment>
|
|
251
|
+
))}
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
{!isEmpty(includedCosts) && (
|
|
255
|
+
<div className="pricing-summary__group">
|
|
256
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.INCLUDED_COSTS}</h6>
|
|
257
|
+
{includedCosts?.map((priceDetail, index) => (
|
|
258
|
+
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
259
|
+
<div className="pricing-summary__row">
|
|
260
|
+
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
261
|
+
{priceDetail.showPrice && (
|
|
262
|
+
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
<div className="pricing-summary__row">
|
|
266
|
+
<div className="price-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
267
|
+
</div>
|
|
268
|
+
</React.Fragment>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
{!isEmpty(extraCosts) && (
|
|
273
|
+
<div className="pricing-summary__group">
|
|
274
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.EXTRA_COSTS}</h6>
|
|
275
|
+
{extraCosts?.map((priceDetail, index) => (
|
|
276
|
+
<React.Fragment key={compact([priceDetail.productCode, priceDetail.accommodationCode, index]).join('_')}>
|
|
277
|
+
<div className="pricing-summary__row">
|
|
278
|
+
<div className="pricing-summary__property">{priceDetail.productName}</div>
|
|
279
|
+
{priceDetail.showPrice && (
|
|
280
|
+
<div className="pricing-summary__value">{formatPrice(priceDetail.price * priceDetail.amount, currencyCode)}</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
<div className="pricing-summary__row">
|
|
284
|
+
<div className="pricing-summary__property">{priceDetail.accommodationName ?? priceDetail.accommodationCode}</div>
|
|
285
|
+
</div>
|
|
286
|
+
{seperateExtraPricePerPaxType &&
|
|
287
|
+
seperateExtraPricePerPaxType.map((ppt, index) => (
|
|
288
|
+
<React.Fragment key={`${ppt.paxType}-${index}`}>
|
|
289
|
+
<div className="pricing-summary__row">
|
|
290
|
+
<div className="pricing-summary__property">
|
|
291
|
+
{ppt.numberOfPax} {getPaxTypeTranslation(translations, ppt.paxType, ppt.numberOfPax)}
|
|
292
|
+
</div>
|
|
293
|
+
<div className="pricing-summary__value">{formatPrice(ppt.pricePerPaxType, currencyCode)}</div>
|
|
294
|
+
</div>
|
|
295
|
+
{ppt.details.map((detail, dIndex) => (
|
|
296
|
+
<div className="pricing-summary__row pricing-summary__row--sub" key={`${ppt.paxType}-${index}-${dIndex}`}>
|
|
297
|
+
<div className="pricing-summary__property">
|
|
298
|
+
{detail.numberOfPax}x {detail.description}
|
|
299
|
+
</div>
|
|
300
|
+
<div className="pricing-summary__value">{formatPrice(detail.price / detail.numberOfPax, currencyCode)}</div>
|
|
301
|
+
</div>
|
|
302
|
+
))}
|
|
303
|
+
</React.Fragment>
|
|
304
|
+
))}
|
|
305
|
+
</React.Fragment>
|
|
306
|
+
))}
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
{!isLoading && canShowTotalPriceSection && !isUnavailable && (
|
|
313
|
+
<div className={`pricing-summary__region pricing-summary__region--pricing ${!isLoading ? 'pricing-summary__region--fade-in' : ''}`}>
|
|
314
|
+
{deposit && remainingAmount > 0 ? (
|
|
315
|
+
<div className="pricing-summary__group">
|
|
316
|
+
{agent && (
|
|
317
|
+
<div className="pricing-summary__row pricing-summary__row--total-price">
|
|
318
|
+
<div className="pricing-summary__property">
|
|
319
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.COMMISSION}</h6>
|
|
320
|
+
</div>
|
|
321
|
+
<div className="pricing-summary__value">
|
|
322
|
+
<div className="pricing">
|
|
323
|
+
<div className="pricing__price">{formatPrice(commission ?? 0, currencyCode)}</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
{totalPrice !== undefined && totalPrice > 0 && (
|
|
329
|
+
<div className="pricing-summary__row pricing-summary__row--total-price">
|
|
330
|
+
<div className="pricing-summary__property">
|
|
331
|
+
<h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
|
|
332
|
+
</div>
|
|
333
|
+
<div className="pricing-summary__value">
|
|
334
|
+
<div className="pricing">
|
|
335
|
+
<div className="pricing__price">{formatPrice(totalPrice, currencyCode)}</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
<div className="pricing-summary__row">
|
|
341
|
+
<div className="pricing-summary__property">
|
|
342
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.DEPOSIT}</h6>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div className="pricing-summary__value">
|
|
346
|
+
<div className="pricing">
|
|
347
|
+
<div className="pricing__price">{formatPrice(deposit, currencyCode)}</div>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="pricing-summary__row">
|
|
352
|
+
<small>
|
|
353
|
+
<em>
|
|
354
|
+
{translations.SIDEBAR.DEPOSIT_TEXT1}
|
|
355
|
+
<strong>{translations.SIDEBAR.DEPOSIT_TEXT2}</strong>
|
|
356
|
+
{translations.SIDEBAR.DEPOSIT_TEXT3}
|
|
357
|
+
{formatPrice(remainingAmount, currencyCode)}
|
|
358
|
+
{translations.SIDEBAR.DEPOSIT_TEXT4}
|
|
359
|
+
<strong>{translations.SIDEBAR.DEPOSIT_TEXT5}</strong>
|
|
360
|
+
{translations.SIDEBAR.DEPOSIT_TEXT6}
|
|
361
|
+
</em>
|
|
362
|
+
</small>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
) : (
|
|
366
|
+
<div className="pricing-summary__group">
|
|
367
|
+
{agent && (
|
|
368
|
+
<div className="pricing-summary__row pricing-summary__row--total-price">
|
|
369
|
+
<div className="pricing-summary__property">
|
|
370
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.COMMISSION}</h6>
|
|
371
|
+
</div>
|
|
372
|
+
<div className="pricing-summary__value">
|
|
373
|
+
<div className="pricing">
|
|
374
|
+
<div className="pricing__price">{formatPrice(commission ?? 0, currencyCode)}</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
{totalPrice !== undefined && totalPrice > 0 && (
|
|
380
|
+
<div className="pricing-summary__row pricing-summary__row--total-price">
|
|
381
|
+
<div className="pricing-summary__property">
|
|
382
|
+
<h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div className="pricing-summary__value">
|
|
386
|
+
<div className="pricing">
|
|
387
|
+
<div className="pricing__price">{formatPrice(totalPrice, currencyCode)}</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
</div>
|
|
395
|
+
)}
|
|
396
|
+
|
|
397
|
+
{footerComponent}
|
|
398
|
+
|
|
399
|
+
{/* <div className="pricing-summary__region pricing-summary__region--pricing pricing-summary__region--fade-in">
|
|
400
|
+
<div className="pricing-summary__group">
|
|
401
|
+
<div className="pricing-summary__row pricing-summary__row--total-price">
|
|
402
|
+
<div className="pricing-summary__property">
|
|
403
|
+
<h6 className="pricing-summary__title">{translations.SHARED.TOTAL_PRICE}</h6>
|
|
404
|
+
</div>
|
|
405
|
+
<div className="pricing-summary__value">
|
|
406
|
+
<div className="pricing">
|
|
407
|
+
<div className="pricing__price">{formatPrice(price || 0, 'EUR')}</div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
{depositAmount > 0 && (
|
|
412
|
+
<div className="pricing-summary__row">
|
|
413
|
+
<div className="pricing-summary__property">
|
|
414
|
+
<h6 className="pricing-summary__title">{translations.SIDEBAR.DEPOSIT}</h6>
|
|
415
|
+
</div>
|
|
416
|
+
<div className="pricing-summary__value">
|
|
417
|
+
<div className="pricing">
|
|
418
|
+
<div className="pricing__price">{formatPrice(depositAmount, 'EUR')}</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
</div> */}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
export default SharedSidebar;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { buildClassName } from '../utils/class-util';
|
|
3
|
+
|
|
4
|
+
interface StepIndicatorsProps {
|
|
5
|
+
currentStep: number;
|
|
6
|
+
stepLabels: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const StepIndicators: React.FC<StepIndicatorsProps> = ({ currentStep, stepLabels }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="step-indicators">
|
|
12
|
+
<div className="step-indicators__items">
|
|
13
|
+
{stepLabels.map((stepName, index) => (
|
|
14
|
+
<div
|
|
15
|
+
key={`${index + 1}-${stepName}`}
|
|
16
|
+
className={buildClassName([
|
|
17
|
+
'step-indicators__item',
|
|
18
|
+
currentStep === index && 'step-indicators__item--active',
|
|
19
|
+
currentStep > index && 'step-indicators__item--completed'
|
|
20
|
+
])}>
|
|
21
|
+
<div className="step-indicators__icon">{index + 1}</div>
|
|
22
|
+
<div className="step-indicators__text">{stepName}</div>
|
|
23
|
+
</div>
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default StepIndicators;
|
|
@@ -43,14 +43,13 @@ const formatPrice = (price?: number, currencyCode?: string | null) => {
|
|
|
43
43
|
const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, handleConfirm }) => {
|
|
44
44
|
const dispatch = useDispatch();
|
|
45
45
|
const context = useContext(SearchResultsConfigurationContext);
|
|
46
|
+
const language = context?.languageCode ?? 'en-GB';
|
|
47
|
+
const translations = getTranslations(language);
|
|
46
48
|
|
|
47
49
|
if (isLoading) {
|
|
48
|
-
return <>{context?.customSpinner ?? <Spinner />}</>;
|
|
50
|
+
return <>{context?.customSpinner ?? <Spinner label={translations.SRP.LOADING_ACCOMMODATIONS} />}</>;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const language = context?.languageCode ?? 'en-GB';
|
|
52
|
-
const translations = getTranslations(language);
|
|
53
|
-
|
|
54
53
|
const { packagingAccoSearchDetails, selectedPackagingAccoResultCode } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
55
54
|
|
|
56
55
|
const selectedPackagingAccoSearchDetails = useMemo<PackagingAccommodationResponse | undefined>(() => {
|
|
@@ -151,7 +151,7 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
|
151
151
|
<>
|
|
152
152
|
<div className="flyin__content">
|
|
153
153
|
{flightSearchDetailsLoading || isEmpty(flights) ? (
|
|
154
|
-
<Spinner />
|
|
154
|
+
<Spinner label={translations.SRP.LOADING_FLIGHTS} />
|
|
155
155
|
) : (
|
|
156
156
|
flight && (
|
|
157
157
|
<div className="flyin__content-text-row">
|
|
@@ -38,6 +38,8 @@ type FlyInProps = {
|
|
|
38
38
|
isPackageEditFlow?: boolean;
|
|
39
39
|
sortByTypes?: SortByType[];
|
|
40
40
|
activeSearchSeed?: SearchSeed | null;
|
|
41
|
+
toggleFilters?: () => void;
|
|
42
|
+
filtersOpen: boolean;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
const FlyIn: React.FC<FlyInProps> = ({
|
|
@@ -51,7 +53,9 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
51
53
|
isPackageEditFlow,
|
|
52
54
|
handleConfirm,
|
|
53
55
|
sortByTypes,
|
|
54
|
-
activeSearchSeed
|
|
56
|
+
activeSearchSeed,
|
|
57
|
+
toggleFilters,
|
|
58
|
+
filtersOpen
|
|
55
59
|
}) => {
|
|
56
60
|
const dispatch = useDispatch();
|
|
57
61
|
const context = useContext(SearchResultsConfigurationContext);
|
|
@@ -166,8 +170,8 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
166
170
|
<Filters
|
|
167
171
|
initialFilters={initialFilters}
|
|
168
172
|
filters={filters}
|
|
169
|
-
isOpen={
|
|
170
|
-
handleSetIsOpen={() =>
|
|
173
|
+
isOpen={filtersOpen}
|
|
174
|
+
handleSetIsOpen={() => toggleFilters && toggleFilters()}
|
|
171
175
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
172
176
|
isLoading={isLoading}
|
|
173
177
|
setFilters={(filters) => dispatch(setFilters(filters))}
|
|
@@ -183,6 +187,10 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
183
187
|
</>
|
|
184
188
|
)}
|
|
185
189
|
</span>
|
|
190
|
+
<div className="cta cta--filter" onClick={() => toggleFilters && toggleFilters()}>
|
|
191
|
+
<Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
|
|
192
|
+
{translations.SRP.FILTERS}
|
|
193
|
+
</div>
|
|
186
194
|
{sortByTypes && sortByTypes.length > 0 && (
|
|
187
195
|
<div className="search__result-row-filter">
|
|
188
196
|
<ItemPicker
|
|
@@ -208,7 +216,7 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
208
216
|
)}
|
|
209
217
|
|
|
210
218
|
{srpType === PortalQsmType.AccommodationAndFlight && (flyInType === 'flight-outward-results' || flyInType === 'flight-return-results') && (
|
|
211
|
-
<PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
219
|
+
<PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} toggleFilters={toggleFilters} filtersOpen={filtersOpen} />
|
|
212
220
|
)}
|
|
213
221
|
|
|
214
222
|
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-results' && (
|
|
@@ -36,14 +36,13 @@ const formatPrice = (price?: number, currencyCode = 'EUR') => {
|
|
|
36
36
|
const GroupTourFlyIn: React.FC<GroupTourFlyInProps> = ({ isLoading, isOpen, setIsOpen }) => {
|
|
37
37
|
const dispatch = useDispatch();
|
|
38
38
|
const context = useContext(SearchResultsConfigurationContext);
|
|
39
|
+
const language = context?.languageCode ?? 'en-GB';
|
|
40
|
+
const translations = getTranslations(language);
|
|
39
41
|
|
|
40
42
|
if (isLoading) {
|
|
41
|
-
return <>{context?.customSpinner ?? <Spinner />}</>;
|
|
43
|
+
return <>{context?.customSpinner ?? <Spinner label={translations.SRP.LOADING_OPTIONS} />}</>;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
const language = context?.languageCode ?? 'en-GB';
|
|
45
|
-
const translations = getTranslations(language);
|
|
46
|
-
|
|
47
46
|
const { bookingPackageDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
48
47
|
|
|
49
48
|
const selectedBookingPackageDetails = useMemo<BookingPackageOption | undefined>(() => {
|
|
@@ -24,13 +24,16 @@ import IndependentFlightOption from '../../../search-results/components/flight/f
|
|
|
24
24
|
import Filters from '../../../search-results/components/filters/filters';
|
|
25
25
|
import { SortByType } from '../../../search-results/types';
|
|
26
26
|
import ItemPicker from '../../../search-results/components/item-picker';
|
|
27
|
+
import Icon from '../icon';
|
|
27
28
|
|
|
28
29
|
type FlightsFlyInProps = {
|
|
29
30
|
isOpen: boolean;
|
|
30
31
|
setIsOpen: (open: boolean) => void;
|
|
32
|
+
toggleFilters?: () => void;
|
|
33
|
+
filtersOpen: boolean;
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
36
|
+
const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen, toggleFilters, filtersOpen }) => {
|
|
34
37
|
const context = useContext(SearchResultsConfigurationContext);
|
|
35
38
|
const language = context?.languageCode ?? 'en-GB';
|
|
36
39
|
const translations = getTranslations(language);
|
|
@@ -80,14 +83,14 @@ const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen
|
|
|
80
83
|
<>
|
|
81
84
|
<div className="flyin__content flyin__content--columns">
|
|
82
85
|
{flightsLoading ? (
|
|
83
|
-
<Spinner />
|
|
86
|
+
<Spinner label={translations.SRP.LOADING_FLIGHTS} />
|
|
84
87
|
) : (
|
|
85
88
|
<>
|
|
86
89
|
<Filters
|
|
87
90
|
initialFilters={initialFlightFilters}
|
|
88
91
|
filters={flightFilters}
|
|
89
|
-
isOpen={
|
|
90
|
-
handleSetIsOpen={() =>
|
|
92
|
+
isOpen={filtersOpen}
|
|
93
|
+
handleSetIsOpen={() => toggleFilters && toggleFilters()}
|
|
91
94
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
92
95
|
isLoading={flightsLoading}
|
|
93
96
|
setFilters={(filters) => dispatch(setFlightFilters(filters))}
|
|
@@ -99,6 +102,10 @@ const PackageingFlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen
|
|
|
99
102
|
{uniqueOutwardFlights?.length && uniqueOutwardFlights.length}
|
|
100
103
|
{translations.FLIGHTS_FORM.FLIGHTS_FOUND_2} {translations.FLIGHTS_FORM.FLIGHTS_FOUND_3}
|
|
101
104
|
</span>
|
|
105
|
+
<div className="cta cta--filter" onClick={() => toggleFilters && toggleFilters()}>
|
|
106
|
+
<Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
|
|
107
|
+
{translations.SRP.FILTERS}
|
|
108
|
+
</div>
|
|
102
109
|
{sortByTypes && sortByTypes.length > 0 && (
|
|
103
110
|
<div className="search__result-row-filter">
|
|
104
111
|
<ItemPicker
|
|
@@ -1093,6 +1093,19 @@ const Icon: React.FC<IconProps> = ({ name, className, title, width, height, fill
|
|
|
1093
1093
|
</svg>
|
|
1094
1094
|
);
|
|
1095
1095
|
|
|
1096
|
+
case 'ui-shopping-cart':
|
|
1097
|
+
return (
|
|
1098
|
+
<svg
|
|
1099
|
+
className={['icon', `icon--${name}`, className].filter((className) => !isEmpty(className)).join(' ')}
|
|
1100
|
+
width={width}
|
|
1101
|
+
height={height}
|
|
1102
|
+
viewBox="0 0 640 512"
|
|
1103
|
+
fill={fill ?? 'currentColor'}>
|
|
1104
|
+
<HTMLComment text="!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." />
|
|
1105
|
+
{title && <title>{title}</title>}
|
|
1106
|
+
<path d="M24-16C10.7-16 0-5.3 0 8S10.7 32 24 32l45.3 0c3.9 0 7.2 2.8 7.9 6.6l52.1 286.3c6.2 34.2 36 59.1 70.8 59.1L456 384c13.3 0 24-10.7 24-24s-10.7-24-24-24l-255.9 0c-11.6 0-21.5-8.3-23.6-19.7l-5.1-28.3 303.6 0c30.8 0 57.2-21.9 62.9-52.2L568.9 69.9C572.6 50.2 557.5 32 537.4 32l-412.7 0-.4-2c-4.8-26.6-28-46-55.1-46L24-16zM208 512a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm224 0a48 48 0 1 0 0-96 48 48 0 1 0 0 96z" />
|
|
1107
|
+
</svg>
|
|
1108
|
+
);
|
|
1096
1109
|
default:
|
|
1097
1110
|
return null;
|
|
1098
1111
|
}
|
|
@@ -348,6 +348,7 @@
|
|
|
348
348
|
"DEPARTURE_ASC": "تاريخ المغادرة (الأقرب أولاً)",
|
|
349
349
|
"FILTERS": "عوامل التصفية",
|
|
350
350
|
"SHOW_ITINERARY": "عرض خط سير الرحلة",
|
|
351
|
+
"VIEW_BOOKING": "عرض الحجز",
|
|
351
352
|
"ITINERARY_TITLE": "ملخص رحلتك",
|
|
352
353
|
"DOSSIER_NUMBER": "رقم الملف",
|
|
353
354
|
"PACKAGE_PRICE_PER_PERSON": "سعر الباقة للشخص الواحد",
|
|
@@ -381,7 +382,12 @@
|
|
|
381
382
|
"DURATION_ASC": "المدة تصاعدياً",
|
|
382
383
|
"DURATION_DESC": "المدة تنازلياً",
|
|
383
384
|
"TRAVEL_GROUP": "مجموعة المسافرين",
|
|
384
|
-
"EXCURSION": "رحلة"
|
|
385
|
+
"EXCURSION": "رحلة",
|
|
386
|
+
"LOADING_EXCURSIONS": "جارٍ تحميل الرحلات...",
|
|
387
|
+
"LOADING_FLIGHTS": "جارٍ تحميل الرحلات الجوية...",
|
|
388
|
+
"LOADING_ACCOMMODATIONS": "جارٍ تحميل الإقامات...",
|
|
389
|
+
"LOADING_ITINERARY": "جارٍ تحميل خط سير الرحلة...",
|
|
390
|
+
"LOADING_OPTIONS": "جارٍ تحميل الخيارات..."
|
|
385
391
|
},
|
|
386
392
|
"ITINERARY": {
|
|
387
393
|
"DAY": "اليوم",
|