@qite/tide-booking-component 1.4.97 → 1.4.99
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 +1423 -975
- package/build/build-cjs/src/search-results/components/itinerary/index.d.ts +2 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +9 -10
- package/build/build-cjs/src/search-results/types.d.ts +24 -1
- package/build/build-cjs/src/search-results/utils/packaging-utils.d.ts +7 -0
- package/build/build-cjs/src/search-results/utils/query-utils.d.ts +11 -0
- package/build/build-cjs/src/shared/components/flyin/accommodation-flyin.d.ts +1 -2
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +4 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +1419 -965
- package/build/build-esm/src/search-results/components/itinerary/index.d.ts +2 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +9 -10
- package/build/build-esm/src/search-results/types.d.ts +24 -1
- package/build/build-esm/src/search-results/utils/packaging-utils.d.ts +7 -0
- package/build/build-esm/src/search-results/utils/query-utils.d.ts +11 -0
- package/build/build-esm/src/shared/components/flyin/accommodation-flyin.d.ts +1 -2
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +4 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +2 -2
- package/src/qsm/components/search-input-group/index.tsx +0 -1
- package/src/search-results/components/itinerary/index.tsx +331 -234
- package/src/search-results/components/search-results-container/search-results-container.tsx +444 -383
- package/src/search-results/store/search-results-slice.ts +22 -10
- package/src/search-results/types.ts +26 -1
- package/src/search-results/utils/packaging-utils.ts +75 -0
- package/src/search-results/utils/query-utils.ts +152 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +10 -11
- package/src/shared/components/flyin/flyin.tsx +52 -4
- package/styles/components/_flyin.scss +25 -0
- package/styles/components/_search.scss +28 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
-
import { ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
|
|
3
|
-
import { BookingPackage, BookingPackageItem,
|
|
2
|
+
import { AccommodationFlyInStep, ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
|
|
3
|
+
import { BookingPackage, BookingPackageItem, PackagingAccommodationResponse, PackagingEntry } from '@qite/tide-client/build/types';
|
|
4
4
|
|
|
5
5
|
export interface SearchResultsState {
|
|
6
6
|
results: BookingPackageItem[];
|
|
@@ -13,13 +13,15 @@ export interface SearchResultsState {
|
|
|
13
13
|
selectedFlight: ExtendedFlightSearchResponseItem | null;
|
|
14
14
|
selectedFlightDetails: ExtendedFlightSearchResponseItem | null;
|
|
15
15
|
bookingPackageDetails: BookingPackage | null;
|
|
16
|
-
entry: EntryLight | null;
|
|
17
16
|
isLoading: boolean;
|
|
18
17
|
filters: Filter[];
|
|
19
18
|
selectedSortType: SortByType | null;
|
|
20
19
|
activeTab: string | null;
|
|
21
20
|
currentPage: number;
|
|
22
21
|
flyInIsOpen: boolean;
|
|
22
|
+
editablePackagingEntry: PackagingEntry | null;
|
|
23
|
+
transactionId: string | null;
|
|
24
|
+
accommodationFlyInStep: AccommodationFlyInStep;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const initialState: SearchResultsState = {
|
|
@@ -33,13 +35,15 @@ const initialState: SearchResultsState = {
|
|
|
33
35
|
selectedFlight: null,
|
|
34
36
|
selectedFlightDetails: null,
|
|
35
37
|
bookingPackageDetails: null,
|
|
36
|
-
entry: null,
|
|
37
38
|
isLoading: false,
|
|
38
39
|
filters: [],
|
|
39
40
|
selectedSortType: null,
|
|
40
41
|
activeTab: 'compact',
|
|
41
42
|
currentPage: 1,
|
|
42
|
-
flyInIsOpen: false
|
|
43
|
+
flyInIsOpen: false,
|
|
44
|
+
editablePackagingEntry: null,
|
|
45
|
+
transactionId: null,
|
|
46
|
+
accommodationFlyInStep: 'details'
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
const searchResultsSlice = createSlice({
|
|
@@ -76,9 +80,6 @@ const searchResultsSlice = createSlice({
|
|
|
76
80
|
setBookingPackageDetails(state, action: PayloadAction<{ details: BookingPackage }>) {
|
|
77
81
|
state.bookingPackageDetails = action.payload.details;
|
|
78
82
|
},
|
|
79
|
-
setEntry(state, action: PayloadAction<{ entry: EntryLight }>) {
|
|
80
|
-
state.entry = action.payload.entry;
|
|
81
|
-
},
|
|
82
83
|
selectFlight(state, action: PayloadAction<{ flightOptionId: string; isDeparture: boolean }>) {
|
|
83
84
|
if (!state.bookingPackageDetails) return;
|
|
84
85
|
|
|
@@ -128,6 +129,15 @@ const searchResultsSlice = createSlice({
|
|
|
128
129
|
},
|
|
129
130
|
setFlyInIsOpen(state, action: PayloadAction<boolean>) {
|
|
130
131
|
state.flyInIsOpen = action.payload;
|
|
132
|
+
},
|
|
133
|
+
setEditablePackagingEntry(state, action: PayloadAction<PackagingEntry | null>) {
|
|
134
|
+
state.editablePackagingEntry = action.payload;
|
|
135
|
+
},
|
|
136
|
+
setTransactionId(state, action: PayloadAction<string | null>) {
|
|
137
|
+
state.transactionId = action.payload;
|
|
138
|
+
},
|
|
139
|
+
setAccommodationFlyInStep(state, action: PayloadAction<AccommodationFlyInStep>) {
|
|
140
|
+
state.accommodationFlyInStep = action.payload;
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
});
|
|
@@ -143,7 +153,6 @@ export const {
|
|
|
143
153
|
setSelectedFlight,
|
|
144
154
|
setSelectedFlightDetails,
|
|
145
155
|
setBookingPackageDetails,
|
|
146
|
-
setEntry,
|
|
147
156
|
selectFlight,
|
|
148
157
|
setIsLoading,
|
|
149
158
|
setFilters,
|
|
@@ -152,7 +161,10 @@ export const {
|
|
|
152
161
|
setActiveTab,
|
|
153
162
|
setCurrentPage,
|
|
154
163
|
resetSearchState,
|
|
155
|
-
setFlyInIsOpen
|
|
164
|
+
setFlyInIsOpen,
|
|
165
|
+
setEditablePackagingEntry,
|
|
166
|
+
setTransactionId,
|
|
167
|
+
setAccommodationFlyInStep
|
|
156
168
|
} = searchResultsSlice.actions;
|
|
157
169
|
|
|
158
170
|
export default searchResultsSlice.reducer;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BookingPackage,
|
|
3
|
+
BookingPackageRequestRoom,
|
|
4
|
+
FlightSearchResponseItem,
|
|
5
|
+
PackagingEntry,
|
|
6
|
+
WebsiteConfigurationSearchConfiguration
|
|
7
|
+
} from '@qite/tide-client';
|
|
2
8
|
import { ReactNode } from 'react';
|
|
3
9
|
|
|
4
10
|
export type FlightSelectionMode = 'paired' | 'independent';
|
|
@@ -53,6 +59,7 @@ export interface SearchResultsConfiguration {
|
|
|
53
59
|
|
|
54
60
|
destinationImage?: { url: string; alt: string };
|
|
55
61
|
onBook?: (result: BookingPackage) => void;
|
|
62
|
+
packagingEntry?: PackagingEntry;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
@@ -179,3 +186,21 @@ export interface TideTag {
|
|
|
179
186
|
id: number;
|
|
180
187
|
name: string;
|
|
181
188
|
}
|
|
189
|
+
|
|
190
|
+
export type SearchSeed = {
|
|
191
|
+
fromDate: string;
|
|
192
|
+
toDate: string;
|
|
193
|
+
country?: number | null;
|
|
194
|
+
region?: number | null;
|
|
195
|
+
oord?: number | null;
|
|
196
|
+
location?: number | null;
|
|
197
|
+
hotel?: number | null;
|
|
198
|
+
hotelCode?: string | null;
|
|
199
|
+
tagId?: number | null;
|
|
200
|
+
departureAirport?: string | null;
|
|
201
|
+
returnAirport?: string | null;
|
|
202
|
+
destinationAirport?: string | null;
|
|
203
|
+
rooms: BookingPackageRequestRoom[];
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export type AccommodationFlyInStep = 'results' | 'details';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BookingPackagePax, BookingPackageRequestRoom, PackagingAccommodationResponse, PackagingEntry, PackagingEntryLine } from '@qite/tide-client';
|
|
2
|
+
import { ACCOMMODATION_SERVICE_TYPE } from './query-utils';
|
|
3
|
+
|
|
4
|
+
export const getSelectedOptionsPerRoom = (details: PackagingAccommodationResponse[]) => {
|
|
5
|
+
const firstResult = details[0];
|
|
6
|
+
if (!firstResult?.rooms?.length) return [];
|
|
7
|
+
|
|
8
|
+
return firstResult.rooms.map((room, roomIndex) => {
|
|
9
|
+
const selectedOption = room.options.find((option) => option.isSelected) ?? room.options[0];
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
roomIndex,
|
|
13
|
+
option: selectedOption ?? null
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getRoomIndexFromLine = (line: PackagingEntryLine): number => {
|
|
19
|
+
if (!line.pax?.length) return 0;
|
|
20
|
+
|
|
21
|
+
const firstPax = [...line.pax].sort((a, b) => a.order - b.order)[0];
|
|
22
|
+
return firstPax?.room ?? 0;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const getRequestRoomsFromPackagingSegments = (entry: PackagingEntry, segments: PackagingEntryLine[]): BookingPackageRequestRoom[] => {
|
|
26
|
+
const accommodationLines = [...(segments ?? [])]
|
|
27
|
+
.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE && line.pax?.length > 0)
|
|
28
|
+
.sort((a, b) => a.order - b.order);
|
|
29
|
+
|
|
30
|
+
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
31
|
+
|
|
32
|
+
if (!accommodationLines.length) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const roomGroups: BookingPackagePax[][] = [];
|
|
37
|
+
|
|
38
|
+
accommodationLines.forEach((line) => {
|
|
39
|
+
const groupedByRoom = new Map<number, BookingPackagePax[]>();
|
|
40
|
+
|
|
41
|
+
line.pax.forEach((linePax) => {
|
|
42
|
+
const roomIndexWithinLine = Number(linePax.room ?? 0);
|
|
43
|
+
const paxId = Number(linePax.paxId);
|
|
44
|
+
const paxSource = paxById.get(paxId);
|
|
45
|
+
|
|
46
|
+
const pax: BookingPackagePax = {
|
|
47
|
+
id: paxSource?.id ?? paxId,
|
|
48
|
+
guid: paxSource?.id?.toString() ?? paxId.toString(),
|
|
49
|
+
firstName: paxSource?.firstName ?? '',
|
|
50
|
+
lastName: paxSource?.lastName ?? '',
|
|
51
|
+
dateOfBirth: paxSource?.dateOfBirth ?? undefined,
|
|
52
|
+
age: paxSource?.dateOfBirth ? undefined : 30,
|
|
53
|
+
isMainBooker: paxSource?.isMainBooker,
|
|
54
|
+
email: ''
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (!groupedByRoom.has(roomIndexWithinLine)) {
|
|
58
|
+
groupedByRoom.set(roomIndexWithinLine, []);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
groupedByRoom.get(roomIndexWithinLine)!.push(pax);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const sortedGroups = Array.from(groupedByRoom.entries())
|
|
65
|
+
.sort(([a], [b]) => a - b)
|
|
66
|
+
.map(([, pax]) => pax);
|
|
67
|
+
|
|
68
|
+
roomGroups.push(...sortedGroups);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return roomGroups.map((pax, index) => ({
|
|
72
|
+
index,
|
|
73
|
+
pax
|
|
74
|
+
}));
|
|
75
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PackagingEntryLine,
|
|
3
|
+
PackagingEntryPax,
|
|
4
|
+
BookingPackagePax,
|
|
5
|
+
PackagingEntry,
|
|
6
|
+
BookingPackageRequestRoom,
|
|
7
|
+
PackagingRoom,
|
|
8
|
+
PackagingTraveller
|
|
9
|
+
} from '@qite/tide-client';
|
|
10
|
+
import { range } from 'lodash';
|
|
11
|
+
|
|
12
|
+
export const GROUP_TOUR_SERVICE_TYPE = 1;
|
|
13
|
+
export const ACCOMMODATION_SERVICE_TYPE = 3;
|
|
14
|
+
export const FLIGHT_SERVICE_TYPE = 7;
|
|
15
|
+
|
|
16
|
+
export const toDateOnlyString = (value: string | Date): string => {
|
|
17
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
18
|
+
return date.toISOString().split('T')[0];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getAgeAtReferenceDate = (dateOfBirth: string | Date, referenceDate: string | Date): number => {
|
|
22
|
+
const dob = dateOfBirth instanceof Date ? dateOfBirth : new Date(dateOfBirth);
|
|
23
|
+
const ref = referenceDate instanceof Date ? referenceDate : new Date(referenceDate);
|
|
24
|
+
|
|
25
|
+
let age = ref.getFullYear() - dob.getFullYear();
|
|
26
|
+
const monthDiff = ref.getMonth() - dob.getMonth();
|
|
27
|
+
|
|
28
|
+
if (monthDiff < 0 || (monthDiff === 0 && ref.getDate() < dob.getDate())) {
|
|
29
|
+
age--;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return age;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getPrimaryAccommodationLine = (lines: PackagingEntryLine[]): PackagingEntryLine | undefined => {
|
|
36
|
+
return [...lines]
|
|
37
|
+
.filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
38
|
+
.sort((a, b) => new Date(a.from).getTime() - new Date(b.from).getTime())[0];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getFlightLines = (lines: PackagingEntryLine[]): PackagingEntryLine[] => {
|
|
42
|
+
return [...lines].filter((line) => line.serviceType === FLIGHT_SERVICE_TYPE).sort((a, b) => new Date(a.from).getTime() - new Date(b.from).getTime());
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const getDepartureAirportFromEntry = (lines: PackagingEntryLine[]): string | null => {
|
|
46
|
+
const firstFlight = getFlightLines(lines)[0];
|
|
47
|
+
const firstSegment = firstFlight?.flightInformation?.flightLines?.[0];
|
|
48
|
+
return firstSegment?.departureAirportCode ?? null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getDestinationAirportFromEntry = (lines: PackagingEntryLine[]): string | null => {
|
|
52
|
+
const outboundFlight = getFlightLines(lines)[0];
|
|
53
|
+
const flightSegments = outboundFlight?.flightInformation?.flightLines;
|
|
54
|
+
|
|
55
|
+
if (!flightSegments?.length) return null;
|
|
56
|
+
|
|
57
|
+
return flightSegments[flightSegments.length - 1]?.arrivalAirportCode ?? null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const mapPackagingPaxToBookingPax = (pax: PackagingEntryPax | undefined, fallbackId: number, referenceDate: string): BookingPackagePax => ({
|
|
61
|
+
id: pax?.id ?? fallbackId,
|
|
62
|
+
guid: pax?.id?.toString() ?? fallbackId.toString(),
|
|
63
|
+
firstName: pax?.firstName ?? '',
|
|
64
|
+
lastName: pax?.lastName ?? '',
|
|
65
|
+
dateOfBirth: pax?.dateOfBirth ?? undefined,
|
|
66
|
+
age: pax?.dateOfBirth ? getAgeAtReferenceDate(pax.dateOfBirth, referenceDate) : undefined,
|
|
67
|
+
isMainBooker: pax?.isMainBooker,
|
|
68
|
+
email: ''
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const getRequestRoomsFromPackagingEntry = (entry: PackagingEntry): BookingPackageRequestRoom[] => {
|
|
72
|
+
const accommodationLines = (entry.lines ?? []).filter((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE && line.pax?.length > 0);
|
|
73
|
+
|
|
74
|
+
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
75
|
+
|
|
76
|
+
if (!accommodationLines.length) {
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
index: 0,
|
|
80
|
+
pax: (entry.pax ?? []).map((p) => mapPackagingPaxToBookingPax(p, p.id, new Date().toISOString()))
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const roomGroups: BookingPackagePax[][] = [];
|
|
86
|
+
|
|
87
|
+
accommodationLines.forEach((line) => {
|
|
88
|
+
const groupedByRoom = new Map<number, BookingPackagePax[]>();
|
|
89
|
+
|
|
90
|
+
line.pax.forEach((linePax) => {
|
|
91
|
+
const roomIndexWithinLine = Number(linePax.room ?? 0);
|
|
92
|
+
const paxId = Number(linePax.paxId);
|
|
93
|
+
const pax = mapPackagingPaxToBookingPax(paxById.get(paxId), paxId, line.from);
|
|
94
|
+
|
|
95
|
+
if (!groupedByRoom.has(roomIndexWithinLine)) {
|
|
96
|
+
groupedByRoom.set(roomIndexWithinLine, []);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
groupedByRoom.get(roomIndexWithinLine)!.push(pax);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const sortedGroups = Array.from(groupedByRoom.entries())
|
|
103
|
+
.sort(([a], [b]) => a - b)
|
|
104
|
+
.map(([, pax]) => pax);
|
|
105
|
+
|
|
106
|
+
roomGroups.push(...sortedGroups);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!roomGroups.length) {
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
index: 0,
|
|
113
|
+
pax: (entry.pax ?? []).map((p) => mapPackagingPaxToBookingPax(p, p.id, accommodationLines[0].from))
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return roomGroups.map((pax, index) => ({
|
|
119
|
+
index,
|
|
120
|
+
pax
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const parseHotelId = (line?: PackagingEntryLine): number | null => {
|
|
125
|
+
if (!line?.productCode) return null;
|
|
126
|
+
const parsed = Number(line.productCode);
|
|
127
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const getPackagingRequestRoomsFromBookingRooms = (rooms: BookingPackageRequestRoom[] | null) => {
|
|
131
|
+
if (!rooms?.length) {
|
|
132
|
+
const room = { index: 0, travellers: [] } as PackagingRoom;
|
|
133
|
+
range(0, 2).forEach(() => {
|
|
134
|
+
room.travellers.push({
|
|
135
|
+
age: 30
|
|
136
|
+
} as PackagingTraveller);
|
|
137
|
+
});
|
|
138
|
+
return [room];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return rooms.map((x, i) => {
|
|
142
|
+
const room = { index: x.index ?? i, travellers: [] } as PackagingRoom;
|
|
143
|
+
|
|
144
|
+
x.pax.forEach((p) => {
|
|
145
|
+
room.travellers.push({
|
|
146
|
+
age: p.age ?? 30
|
|
147
|
+
} as PackagingTraveller);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return room;
|
|
151
|
+
});
|
|
152
|
+
};
|
|
@@ -5,14 +5,14 @@ import { SearchResultsRootState } from '../../../search-results/store/search-res
|
|
|
5
5
|
import SearchResultsConfigurationContext from '../../../search-results/search-results-configuration-context';
|
|
6
6
|
import { getTranslations } from '../../utils/localization-util';
|
|
7
7
|
import { setPackagingAccoSearchDetails } from '../../../search-results/store/search-results-slice';
|
|
8
|
-
import { PackageMainRoom } from '@qite/tide-client';
|
|
8
|
+
import { PackageMainRoom, PortalQsmType } from '@qite/tide-client';
|
|
9
9
|
import { PickerItem } from '../../types';
|
|
10
10
|
import { first } from 'lodash';
|
|
11
|
+
import Spinner from '../../../search-results/components/spinner/spinner';
|
|
11
12
|
|
|
12
13
|
type AccommodationFlyInProps = {
|
|
13
14
|
isLoading: boolean;
|
|
14
|
-
|
|
15
|
-
setIsOpen: (open: boolean) => void;
|
|
15
|
+
handleConfirm: () => void;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
type PackagingAccommodationResponse = {
|
|
@@ -38,9 +38,14 @@ const formatPrice = (price?: number, currencyCode = 'EUR') => {
|
|
|
38
38
|
}).format(price);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading,
|
|
41
|
+
const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, handleConfirm }) => {
|
|
42
42
|
const dispatch = useDispatch();
|
|
43
43
|
const context = useContext(SearchResultsConfigurationContext);
|
|
44
|
+
|
|
45
|
+
if (isLoading) {
|
|
46
|
+
return <>{context?.customSpinner ?? <Spinner />}</>;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
const language = context?.languageCode ?? 'en-GB';
|
|
45
50
|
const translations = getTranslations(language);
|
|
46
51
|
|
|
@@ -118,12 +123,6 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, isOp
|
|
|
118
123
|
dispatch(setPackagingAccoSearchDetails(updatedPackagingAccoSearchDetails));
|
|
119
124
|
};
|
|
120
125
|
|
|
121
|
-
const handleConfirm = () => {
|
|
122
|
-
if (isOpen) {
|
|
123
|
-
setIsOpen(false);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
126
|
if (!selectedPackagingAccoSearchDetails) {
|
|
128
127
|
return null;
|
|
129
128
|
}
|
|
@@ -248,7 +247,7 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, isOp
|
|
|
248
247
|
|
|
249
248
|
<div className="flyin__button-wrapper">
|
|
250
249
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
251
|
-
{translations.PRODUCT.BOOK_NOW}
|
|
250
|
+
{context?.searchConfiguration.qsmType == PortalQsmType.AccommodationAndFlight ? translations.QSM.CONFIRM : translations.PRODUCT.BOOK_NOW}
|
|
252
251
|
</button>
|
|
253
252
|
</div>
|
|
254
253
|
</div>
|
|
@@ -2,11 +2,19 @@ import React, { useEffect, useRef } from 'react';
|
|
|
2
2
|
import Icon from '../icon';
|
|
3
3
|
import { useFlightSearch } from '../../../search-results/components/flight/flight-search-context';
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
setAccommodationFlyInStep,
|
|
7
|
+
setSelectedFlight,
|
|
8
|
+
setSelectedFlightDetails,
|
|
9
|
+
setSelectedPackagingAccoResult,
|
|
10
|
+
setSelectedSearchResult
|
|
11
|
+
} from '../../../search-results/store/search-results-slice';
|
|
6
12
|
import FlightsFlyIn from './flights-flyin';
|
|
7
13
|
import AccommodationFlyIn from './accommodation-flyin';
|
|
8
14
|
import { PortalQsmType } from '@qite/tide-client';
|
|
9
15
|
import GroupTourFlyIn from './group-tour-flyin';
|
|
16
|
+
import { AccommodationFlyInStep } from '../../../search-results/types';
|
|
17
|
+
import HotelAccommodationResults from '../../../search-results/components/hotel/hotel-accommodation-results';
|
|
10
18
|
|
|
11
19
|
type FlyInProps = {
|
|
12
20
|
title: string;
|
|
@@ -16,9 +24,23 @@ type FlyInProps = {
|
|
|
16
24
|
className?: string;
|
|
17
25
|
onPanelRef?: (el: HTMLDivElement | null) => void;
|
|
18
26
|
detailsLoading: boolean;
|
|
27
|
+
handleConfirm?: () => void;
|
|
28
|
+
accommodationStep?: AccommodationFlyInStep;
|
|
29
|
+
isPackageEditFlow?: boolean;
|
|
19
30
|
};
|
|
20
31
|
|
|
21
|
-
const FlyIn: React.FC<FlyInProps> = ({
|
|
32
|
+
const FlyIn: React.FC<FlyInProps> = ({
|
|
33
|
+
title,
|
|
34
|
+
srpType,
|
|
35
|
+
isOpen,
|
|
36
|
+
setIsOpen,
|
|
37
|
+
className = '',
|
|
38
|
+
onPanelRef,
|
|
39
|
+
detailsLoading,
|
|
40
|
+
accommodationStep,
|
|
41
|
+
isPackageEditFlow,
|
|
42
|
+
handleConfirm
|
|
43
|
+
}) => {
|
|
22
44
|
const dispatch = useDispatch();
|
|
23
45
|
const { onCancelSearch } = useFlightSearch();
|
|
24
46
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
@@ -55,13 +77,21 @@ const FlyIn: React.FC<FlyInProps> = ({ title, srpType, isOpen, setIsOpen, classN
|
|
|
55
77
|
dispatch(setSelectedFlight(null));
|
|
56
78
|
dispatch(setSelectedFlightDetails(null));
|
|
57
79
|
onCancelSearch();
|
|
80
|
+
} else {
|
|
81
|
+
dispatch(setSelectedSearchResult(null));
|
|
82
|
+
dispatch(setSelectedPackagingAccoResult(null));
|
|
58
83
|
}
|
|
84
|
+
dispatch(setAccommodationFlyInStep('details'));
|
|
59
85
|
setIsOpen(false);
|
|
60
86
|
}
|
|
61
87
|
};
|
|
62
88
|
|
|
89
|
+
const handleGoBack = () => {
|
|
90
|
+
dispatch(setAccommodationFlyInStep('results'));
|
|
91
|
+
};
|
|
92
|
+
|
|
63
93
|
return (
|
|
64
|
-
<div className={`flyin ${isOpen ? 'flyin--active' : ''} ${className}`}>
|
|
94
|
+
<div className={`flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow ? 'flyin--large' : ''}`}>
|
|
65
95
|
<div className={`flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`} ref={panelRef}>
|
|
66
96
|
<div className="flyin__content">
|
|
67
97
|
<div className="flyin__content-title-row">
|
|
@@ -70,9 +100,27 @@ const FlyIn: React.FC<FlyInProps> = ({ title, srpType, isOpen, setIsOpen, classN
|
|
|
70
100
|
<Icon name="ui-close" width={30} height={30} aria-hidden="true" />
|
|
71
101
|
</span>
|
|
72
102
|
</div>
|
|
103
|
+
{isPackageEditFlow && accommodationStep === 'details' && (
|
|
104
|
+
<div className="flyin__content-title-row">
|
|
105
|
+
<div onClick={handleGoBack} className="flyin__content-title__back">
|
|
106
|
+
<Icon name="ui-chevron" width={14} height={14} aria-hidden="true" />
|
|
107
|
+
Go Back
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
73
111
|
</div>
|
|
74
112
|
{srpType === PortalQsmType.Flight && <FlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />}
|
|
75
|
-
|
|
113
|
+
|
|
114
|
+
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && accommodationStep === 'results' && (
|
|
115
|
+
<div className="flyin__content">
|
|
116
|
+
<HotelAccommodationResults isLoading={detailsLoading} />
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && accommodationStep === 'details' && (
|
|
121
|
+
<AccommodationFlyIn isLoading={detailsLoading} handleConfirm={handleConfirm!} />
|
|
122
|
+
)}
|
|
123
|
+
|
|
76
124
|
{srpType === PortalQsmType.GroupTour && <GroupTourFlyIn isLoading={detailsLoading} isOpen={isOpen} setIsOpen={setIsOpen} />}
|
|
77
125
|
</div>
|
|
78
126
|
</div>
|
|
@@ -19,6 +19,20 @@
|
|
|
19
19
|
pointer-events: auto;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
&--large {
|
|
23
|
+
.flyin__panel {
|
|
24
|
+
@include mixins.media-sm {
|
|
25
|
+
width: 80%;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.flyin__acco__cards {
|
|
30
|
+
@include mixins.media-lg {
|
|
31
|
+
grid-template-columns: repeat(3, 1fr);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
&__panel {
|
|
23
37
|
background: var(--tide-booking-gray-xlight);
|
|
24
38
|
height: 100%;
|
|
@@ -89,6 +103,17 @@
|
|
|
89
103
|
align-items: center;
|
|
90
104
|
padding-bottom: 15px;
|
|
91
105
|
}
|
|
106
|
+
|
|
107
|
+
&__back {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 3px;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
|
|
113
|
+
svg {
|
|
114
|
+
transform: rotate(180deg);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
92
117
|
}
|
|
93
118
|
|
|
94
119
|
&-arrow {
|
|
@@ -370,6 +370,7 @@
|
|
|
370
370
|
display: flex;
|
|
371
371
|
flex-direction: column;
|
|
372
372
|
align-items: center;
|
|
373
|
+
|
|
373
374
|
p {
|
|
374
375
|
margin: 0px;
|
|
375
376
|
line-height: 1.5;
|
|
@@ -493,6 +494,8 @@
|
|
|
493
494
|
}
|
|
494
495
|
|
|
495
496
|
&-date {
|
|
497
|
+
min-width: 26px;
|
|
498
|
+
|
|
496
499
|
p {
|
|
497
500
|
margin: 0px;
|
|
498
501
|
color: var(--tide-booking-search-itinerary-filter-transport-date-month-color);
|
|
@@ -626,6 +629,14 @@
|
|
|
626
629
|
&--start {
|
|
627
630
|
align-items: flex-start;
|
|
628
631
|
}
|
|
632
|
+
|
|
633
|
+
&--editable {
|
|
634
|
+
cursor: pointer;
|
|
635
|
+
|
|
636
|
+
&:hover {
|
|
637
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
629
640
|
}
|
|
630
641
|
|
|
631
642
|
&-badge {
|
|
@@ -670,6 +681,7 @@
|
|
|
670
681
|
display: flex;
|
|
671
682
|
align-items: center;
|
|
672
683
|
flex-flow: row nowrap;
|
|
684
|
+
min-width: 26px;
|
|
673
685
|
max-width: 26px;
|
|
674
686
|
|
|
675
687
|
svg {
|
|
@@ -690,11 +702,18 @@
|
|
|
690
702
|
color: var(--tide-booking-search-itinerary-filter-segment-title-color);
|
|
691
703
|
}
|
|
692
704
|
|
|
693
|
-
|
|
705
|
+
&__room {
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
gap: 5px;
|
|
694
709
|
margin: 0px;
|
|
695
710
|
line-height: 1.2;
|
|
696
711
|
font-weight: var(--tide-booking-search-itinerary-filter-segment-text-font-weight);
|
|
697
712
|
color: var(--tide-booking-search-itinerary-filter-segment-text-color);
|
|
713
|
+
|
|
714
|
+
svg {
|
|
715
|
+
flex: 12px 0 0;
|
|
716
|
+
}
|
|
698
717
|
}
|
|
699
718
|
|
|
700
719
|
.rating {
|
|
@@ -1001,6 +1020,7 @@
|
|
|
1001
1020
|
|
|
1002
1021
|
&--custom {
|
|
1003
1022
|
gap: 20px;
|
|
1023
|
+
|
|
1004
1024
|
.search__result-card__footer {
|
|
1005
1025
|
padding: 20px;
|
|
1006
1026
|
padding-top: 0px;
|
|
@@ -1667,6 +1687,7 @@
|
|
|
1667
1687
|
|
|
1668
1688
|
&__allotment {
|
|
1669
1689
|
padding: 0px;
|
|
1690
|
+
|
|
1670
1691
|
@include mixins.media-sm {
|
|
1671
1692
|
flex-direction: column;
|
|
1672
1693
|
}
|
|
@@ -1689,9 +1710,11 @@
|
|
|
1689
1710
|
@include mixins.media-md {
|
|
1690
1711
|
padding: 20px 0px;
|
|
1691
1712
|
}
|
|
1713
|
+
|
|
1692
1714
|
@include mixins.media-lg {
|
|
1693
1715
|
padding: 0px 20px;
|
|
1694
1716
|
}
|
|
1717
|
+
|
|
1695
1718
|
@include mixins.media-xl {
|
|
1696
1719
|
padding: 20px 0px;
|
|
1697
1720
|
}
|
|
@@ -1700,6 +1723,7 @@
|
|
|
1700
1723
|
&__footer {
|
|
1701
1724
|
padding: 0px 20px;
|
|
1702
1725
|
padding-bottom: 20px;
|
|
1726
|
+
|
|
1703
1727
|
@include mixins.media-sm {
|
|
1704
1728
|
flex-direction: row;
|
|
1705
1729
|
align-items: flex-start;
|
|
@@ -1732,12 +1756,15 @@
|
|
|
1732
1756
|
@include mixins.media-sm {
|
|
1733
1757
|
align-items: flex-start;
|
|
1734
1758
|
}
|
|
1759
|
+
|
|
1735
1760
|
@include mixins.media-md {
|
|
1736
1761
|
align-items: flex-end;
|
|
1737
1762
|
}
|
|
1763
|
+
|
|
1738
1764
|
@include mixins.media-lg {
|
|
1739
1765
|
align-items: flex-start;
|
|
1740
1766
|
}
|
|
1767
|
+
|
|
1741
1768
|
@include mixins.media-xl {
|
|
1742
1769
|
align-items: flex-end;
|
|
1743
1770
|
}
|