@odynn/awayz-flights 0.9.3 → 0.9.4
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/package.json +3 -4
- package/lib/components/BookingOption/BookingOption.tsx +0 -240
- package/lib/components/BookingOption/_styles.booking-option.scss +0 -69
- package/lib/components/FlightItinerary/FlightItinerary.tsx +0 -290
- package/lib/components/FlightItinerary/_styles.flight-itinerary.scss +0 -102
- package/lib/components/FlightResult/FlightResult.stories.tsx +0 -495
- package/lib/components/FlightResult/FlightResult.tsx +0 -612
- package/lib/components/FlightResult/_styles.flight-result.scss +0 -408
- package/lib/components/FlightResult/mocks/DefaultFlightData.args.json +0 -645
- package/lib/components/FlightResult/mocks/NoCabinClassFlightData.args.json +0 -310
- package/lib/components/FlightResult/mocks/index.ts +0 -4
- package/lib/components/HorizontalScroller/HorizontalScroller.tsx +0 -118
- package/lib/components/HorizontalScroller/_styles.horizontal-scroller.scss +0 -79
- package/lib/components/index.ts +0 -2
- package/lib/constants/endpoints.ts +0 -13
- package/lib/enums/EPaymentType.ts +0 -4
- package/lib/enums/index.ts +0 -1
- package/lib/hooks/index.ts +0 -10
- package/lib/hooks/useAirportSearch/useAirportSearch.ts +0 -24
- package/lib/hooks/useAirportSearch/useAirportSearch.types.ts +0 -12
- package/lib/hooks/useFlightSearch/useFlightSearch.ts +0 -488
- package/lib/hooks/useFlightSearch/useFlightSearch.types.ts +0 -254
- package/lib/main.ts +0 -51
- package/lib/services/flights/FlightsService.ts +0 -171
- package/lib/services/flights/FlightsService.types.ts +0 -244
- package/lib/services/wallet/WalletService.ts +0 -60
- package/lib/services/wallet/WalletService.types.ts +0 -159
- package/lib/stores/useFlightStore.ts +0 -35
- package/lib/styles/_animations.scss +0 -366
- package/lib/styles/_colours.scss +0 -20
- package/lib/styles/_fonts.scss +0 -18
- package/lib/styles/_layout.scss +0 -88
- package/lib/styles/_mixins.scss +0 -34
- package/lib/styles/_shared.scss +0 -116
- package/lib/styles/index.scss +0 -4
- package/lib/types/ECabinClass.ts +0 -46
- package/lib/types/enums.ts +0 -10
- package/lib/types/index.ts +0 -1
- package/lib/utils/flightDateUtils.ts +0 -20
- package/lib/utils/flightUtils.ts +0 -563
- package/lib/utils/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odynn/awayz-flights",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
|
-
"dist"
|
|
7
|
-
"lib"
|
|
6
|
+
"dist"
|
|
8
7
|
],
|
|
9
|
-
"main": "
|
|
8
|
+
"main": "dist/main.js",
|
|
10
9
|
"types": "./dist/lib/main.d.ts",
|
|
11
10
|
"scripts": {
|
|
12
11
|
"test": "echo \"NOTE: Please run tests from the root\"",
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CashValue,
|
|
3
|
-
ClientPointsValue,
|
|
4
|
-
EAmountsDisplayFeature,
|
|
5
|
-
EInvalidAmountDisplayOption,
|
|
6
|
-
EToolTipPosition,
|
|
7
|
-
useFeatureFlags
|
|
8
|
-
} from '@odynn/awayz-core';
|
|
9
|
-
import { useQuery } from '@tanstack/react-query';
|
|
10
|
-
import {
|
|
11
|
-
commaSeparatedNumber,
|
|
12
|
-
DEFAULT_CURRENCY,
|
|
13
|
-
ISegment,
|
|
14
|
-
ISlice,
|
|
15
|
-
pluralise,
|
|
16
|
-
toTitleCase
|
|
17
|
-
} from '@type-op/shared';
|
|
18
|
-
import React, { useState } from 'react';
|
|
19
|
-
import { FaTimes } from 'react-icons/fa';
|
|
20
|
-
import {
|
|
21
|
-
FaCheck,
|
|
22
|
-
FaChevronDown,
|
|
23
|
-
FaChevronUp,
|
|
24
|
-
FaSuitcase
|
|
25
|
-
} from 'react-icons/fa6';
|
|
26
|
-
import { EPaymentType } from '../../enums';
|
|
27
|
-
import {
|
|
28
|
-
EBaggage,
|
|
29
|
-
IFlightPaymentOptionBaggage,
|
|
30
|
-
IOfferConditions
|
|
31
|
-
} from '../../hooks/useFlightSearch/useFlightSearch.types';
|
|
32
|
-
import { formatCabinClass, getAirlineProgram } from '../../utils/flightUtils';
|
|
33
|
-
import './_styles.booking-option.scss';
|
|
34
|
-
|
|
35
|
-
interface IBookingOptionProps {
|
|
36
|
-
type: EPaymentType;
|
|
37
|
-
title: string;
|
|
38
|
-
cashValue?: {
|
|
39
|
-
amount: number;
|
|
40
|
-
currency: string;
|
|
41
|
-
};
|
|
42
|
-
milesValue?: number;
|
|
43
|
-
cashFee?: {
|
|
44
|
-
amount: number;
|
|
45
|
-
currency: string;
|
|
46
|
-
};
|
|
47
|
-
setSelected: () => void;
|
|
48
|
-
conditions?: IOfferConditions;
|
|
49
|
-
slices?: ISlice[];
|
|
50
|
-
baggages?: IFlightPaymentOptionBaggage[];
|
|
51
|
-
program?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const BookingOption = ({
|
|
55
|
-
type,
|
|
56
|
-
title,
|
|
57
|
-
cashValue,
|
|
58
|
-
milesValue,
|
|
59
|
-
cashFee,
|
|
60
|
-
setSelected,
|
|
61
|
-
conditions,
|
|
62
|
-
slices,
|
|
63
|
-
baggages,
|
|
64
|
-
program
|
|
65
|
-
}: IBookingOptionProps) => {
|
|
66
|
-
const { featureFlags } = useFeatureFlags();
|
|
67
|
-
const [showMoreOptions, setShowMoreOptions] = useState(false);
|
|
68
|
-
|
|
69
|
-
const refundable = conditions?.refundBeforeDeparture;
|
|
70
|
-
|
|
71
|
-
const changeable = conditions?.changeBeforeDeparture;
|
|
72
|
-
|
|
73
|
-
const additionalOptionsCount =
|
|
74
|
-
(refundable?.allowed ? 1 : 0) +
|
|
75
|
-
(changeable?.allowed ? 1 : 0) +
|
|
76
|
-
(slices?.[0]?.segments?.length || 0);
|
|
77
|
-
|
|
78
|
-
const { data: programDetails } = useQuery({
|
|
79
|
-
queryKey: ['airlineProgram', program],
|
|
80
|
-
queryFn: () => getAirlineProgram(program!),
|
|
81
|
-
enabled: !!program
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div
|
|
86
|
-
className={`flight-booking-option`}
|
|
87
|
-
onClick={(e) => e.stopPropagation()}
|
|
88
|
-
>
|
|
89
|
-
<div className='rate-details'>
|
|
90
|
-
{type === EPaymentType.POINTS && (
|
|
91
|
-
<img src={programDetails?.programLogo} />
|
|
92
|
-
)}
|
|
93
|
-
<p className='fare-brand-name'>{toTitleCase(title)}</p>
|
|
94
|
-
</div>
|
|
95
|
-
{type === EPaymentType.CASH && (
|
|
96
|
-
<div className={`fare-comparison-content`}>
|
|
97
|
-
{baggages?.map((item, idx: number) => (
|
|
98
|
-
<React.Fragment key={idx}>
|
|
99
|
-
{item.type === EBaggage.CHECKED && (
|
|
100
|
-
<div className='fare-option checked'>
|
|
101
|
-
<p>Checked bags:</p>
|
|
102
|
-
<p>{item.quantity}</p>
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
{item.type === EBaggage.CARRY_ON && (
|
|
106
|
-
<div className='fare-option'>
|
|
107
|
-
<p>Carry-on bags:</p>
|
|
108
|
-
<p>{item.quantity}</p>
|
|
109
|
-
</div>
|
|
110
|
-
)}
|
|
111
|
-
{item.type === null && (
|
|
112
|
-
<div className='fare-option'>
|
|
113
|
-
<FaSuitcase />
|
|
114
|
-
<p>N/A</p>
|
|
115
|
-
</div>
|
|
116
|
-
)}
|
|
117
|
-
</React.Fragment>
|
|
118
|
-
))}
|
|
119
|
-
<div className='fare-option refundable'>
|
|
120
|
-
Refundable:{' '}
|
|
121
|
-
{refundable?.allowed ? (
|
|
122
|
-
<FaCheck className='yes' />
|
|
123
|
-
) : (
|
|
124
|
-
<FaTimes className='no' />
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
<div className='fare-option changeable'>
|
|
128
|
-
Changeable:{' '}
|
|
129
|
-
{changeable?.allowed ? (
|
|
130
|
-
<FaCheck className='yes' />
|
|
131
|
-
) : (
|
|
132
|
-
<FaTimes className='no' />
|
|
133
|
-
)}
|
|
134
|
-
</div>
|
|
135
|
-
<div
|
|
136
|
-
className='fare-option show-more-toggle'
|
|
137
|
-
onClick={(e) => {
|
|
138
|
-
e.stopPropagation();
|
|
139
|
-
setShowMoreOptions(!showMoreOptions);
|
|
140
|
-
}}
|
|
141
|
-
>
|
|
142
|
-
<p>
|
|
143
|
-
{showMoreOptions
|
|
144
|
-
? `Hide ${pluralise(additionalOptionsCount, 'option')}`
|
|
145
|
-
: `+${additionalOptionsCount} ${pluralise(additionalOptionsCount, 'option')}`}
|
|
146
|
-
</p>
|
|
147
|
-
<p>{showMoreOptions ? <FaChevronUp /> : <FaChevronDown />}</p>
|
|
148
|
-
</div>
|
|
149
|
-
{showMoreOptions && (
|
|
150
|
-
<>
|
|
151
|
-
{refundable?.allowed && (
|
|
152
|
-
<div className='fare-option'>
|
|
153
|
-
Refundable Penalty:{' '}
|
|
154
|
-
<CashValue
|
|
155
|
-
amount={refundable.penaltyAmount || 0}
|
|
156
|
-
currency={refundable.penaltyCurrency || ''}
|
|
157
|
-
zeroDisplayOption={
|
|
158
|
-
EInvalidAmountDisplayOption.DISPLAY_AS_ZERO_WITH_CURRENCY
|
|
159
|
-
}
|
|
160
|
-
position={EToolTipPosition.LEFT}
|
|
161
|
-
/>
|
|
162
|
-
</div>
|
|
163
|
-
)}
|
|
164
|
-
{changeable?.allowed && (
|
|
165
|
-
<div className='fare-option'>
|
|
166
|
-
Changeable Penalty:{' '}
|
|
167
|
-
<CashValue
|
|
168
|
-
amount={changeable.penaltyAmount || 0}
|
|
169
|
-
currency={changeable.penaltyCurrency || ''}
|
|
170
|
-
zeroDisplayOption={
|
|
171
|
-
EInvalidAmountDisplayOption.DISPLAY_AS_ZERO_WITH_CURRENCY
|
|
172
|
-
}
|
|
173
|
-
position={EToolTipPosition.LEFT}
|
|
174
|
-
/>
|
|
175
|
-
</div>
|
|
176
|
-
)}
|
|
177
|
-
{slices?.[0].segments?.map((segment: ISegment) => (
|
|
178
|
-
<div
|
|
179
|
-
className='fare-option checked'
|
|
180
|
-
key={`${segment.origin.iataCode}-${segment.destination.iataCode}`}
|
|
181
|
-
>
|
|
182
|
-
<p>
|
|
183
|
-
{segment.origin.iataCode} - {segment.destination.iataCode}:
|
|
184
|
-
</p>
|
|
185
|
-
<div>
|
|
186
|
-
<img src={segment.operatingCarrier.logo} />
|
|
187
|
-
<p>{formatCabinClass(segment.passengers[0].cabinClass)}</p>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
))}
|
|
191
|
-
</>
|
|
192
|
-
)}
|
|
193
|
-
</div>
|
|
194
|
-
)}
|
|
195
|
-
<button
|
|
196
|
-
onClick={(e) => {
|
|
197
|
-
e.stopPropagation();
|
|
198
|
-
setSelected();
|
|
199
|
-
}}
|
|
200
|
-
>
|
|
201
|
-
{type === EPaymentType.CASH && !!cashValue ? (
|
|
202
|
-
<span>
|
|
203
|
-
<CashValue
|
|
204
|
-
amount={cashValue.amount}
|
|
205
|
-
currency={cashValue.currency}
|
|
206
|
-
position={EToolTipPosition.LEFT}
|
|
207
|
-
zeroDisplayOption={
|
|
208
|
-
EInvalidAmountDisplayOption.DISPLAY_AS_ZERO_WITH_CURRENCY
|
|
209
|
-
}
|
|
210
|
-
decimalPlaces={2}
|
|
211
|
-
/>
|
|
212
|
-
{featureFlags.amountsDisplayFeature ===
|
|
213
|
-
EAmountsDisplayFeature.CASH_AND_CLIENT_POINTS && (
|
|
214
|
-
<>
|
|
215
|
-
{' / '}
|
|
216
|
-
<ClientPointsValue
|
|
217
|
-
currency={cashValue.currency}
|
|
218
|
-
cashAmount={cashValue.amount}
|
|
219
|
-
/>
|
|
220
|
-
</>
|
|
221
|
-
)}
|
|
222
|
-
</span>
|
|
223
|
-
) : (
|
|
224
|
-
<>
|
|
225
|
-
{`${commaSeparatedNumber(milesValue)} miles + `}
|
|
226
|
-
<CashValue
|
|
227
|
-
amount={cashFee?.amount || 0}
|
|
228
|
-
currency={cashFee?.currency || DEFAULT_CURRENCY}
|
|
229
|
-
position={EToolTipPosition.LEFT}
|
|
230
|
-
customDisplay='No fee'
|
|
231
|
-
zeroDisplayOption={EInvalidAmountDisplayOption.CUSTOM}
|
|
232
|
-
/>
|
|
233
|
-
</>
|
|
234
|
-
)}
|
|
235
|
-
</button>
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
export default BookingOption;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
.flight-booking-option {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
border: $border;
|
|
5
|
-
border-radius: $border-radius;
|
|
6
|
-
cursor: default;
|
|
7
|
-
user-select: none;
|
|
8
|
-
transition: all 0.3s ease;
|
|
9
|
-
padding: $padding;
|
|
10
|
-
min-width: 270px;
|
|
11
|
-
gap: $padding;
|
|
12
|
-
height: fit-content;
|
|
13
|
-
|
|
14
|
-
.fare-option {
|
|
15
|
-
display: flex;
|
|
16
|
-
justify-content: space-between;
|
|
17
|
-
border-bottom: $border;
|
|
18
|
-
padding: $padding-sm 0;
|
|
19
|
-
|
|
20
|
-
&.show-more-toggle {
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
|
|
23
|
-
&:hover {
|
|
24
|
-
background-color: colour.$background;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.yes {
|
|
29
|
-
color: colour.$primary;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.no {
|
|
33
|
-
color: colour.$danger;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
> div {
|
|
37
|
-
display: flex;
|
|
38
|
-
gap: $padding-sm;
|
|
39
|
-
|
|
40
|
-
img {
|
|
41
|
-
height: 20px;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.rate-details {
|
|
47
|
-
> img {
|
|
48
|
-
height: 30px;
|
|
49
|
-
max-width: 70%;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.fare-brand-name {
|
|
53
|
-
font-weight: font.$heavy;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
button {
|
|
58
|
-
border-radius: $border-radius-pill;
|
|
59
|
-
padding: $padding-sm $padding;
|
|
60
|
-
border: none;
|
|
61
|
-
background-color: colour.$primary;
|
|
62
|
-
color: colour.$foreground;
|
|
63
|
-
font-weight: font.$heavy;
|
|
64
|
-
cursor: pointer;
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
justify-content: center;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
calculateTimeDifference,
|
|
3
|
-
getCountryNameByCode,
|
|
4
|
-
getTimeString,
|
|
5
|
-
getTimezoneOffsetOrCode,
|
|
6
|
-
ISegment
|
|
7
|
-
} from '@type-op/shared';
|
|
8
|
-
import {
|
|
9
|
-
DUFFEL_ASSET_PATH,
|
|
10
|
-
EDateFormats,
|
|
11
|
-
EDateKeys
|
|
12
|
-
} from '@type-op/shared/constants';
|
|
13
|
-
import moment from 'moment';
|
|
14
|
-
import React, { useMemo } from 'react';
|
|
15
|
-
import { useTranslation } from 'react-i18next';
|
|
16
|
-
import { FaCalendar, FaClock } from 'react-icons/fa';
|
|
17
|
-
import { IAirportLocation } from '../../hooks/useFlightSearch/useFlightSearch.types';
|
|
18
|
-
import './_styles.flight-itinerary.scss';
|
|
19
|
-
|
|
20
|
-
interface FlightItineraryProps {
|
|
21
|
-
origin: IAirportLocation;
|
|
22
|
-
destination: IAirportLocation;
|
|
23
|
-
itinerary: ISegment[];
|
|
24
|
-
onDisableRenderItinerary?: () => void;
|
|
25
|
-
isMultiDayFlight?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const FlightItinerary = ({
|
|
29
|
-
itinerary,
|
|
30
|
-
origin,
|
|
31
|
-
destination,
|
|
32
|
-
onDisableRenderItinerary,
|
|
33
|
-
isMultiDayFlight
|
|
34
|
-
}: FlightItineraryProps) => {
|
|
35
|
-
const getCabinClass = () => {
|
|
36
|
-
const passenger = itinerary[0]?.passengers?.[0];
|
|
37
|
-
return passenger?.cabinClassMarketingName ?? passenger?.cabinClass;
|
|
38
|
-
};
|
|
39
|
-
const layovers = useMemo(() => {
|
|
40
|
-
const layovers = [];
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < itinerary.length; i++) {
|
|
43
|
-
const segment = itinerary[i];
|
|
44
|
-
const nextSegment = itinerary[i + 1];
|
|
45
|
-
|
|
46
|
-
if (!nextSegment) {
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
const layover = {
|
|
50
|
-
arrival: {
|
|
51
|
-
airport: segment.destination?.name,
|
|
52
|
-
city:
|
|
53
|
-
segment.destination?.cityName ?? segment.destination?.city?.name,
|
|
54
|
-
country: getCountryNameByCode(segment.destination?.iataCountryCode),
|
|
55
|
-
time: segment.arrivingAt,
|
|
56
|
-
arrivalTimeZone: segment.destination.timeZone
|
|
57
|
-
},
|
|
58
|
-
departure: {
|
|
59
|
-
airport: nextSegment.origin?.name,
|
|
60
|
-
city: nextSegment.origin?.cityName ?? nextSegment.origin?.city?.name,
|
|
61
|
-
country: getCountryNameByCode(segment.destination?.iataCountryCode),
|
|
62
|
-
time: nextSegment.departingAt,
|
|
63
|
-
airline: nextSegment.operatingCarrier.name,
|
|
64
|
-
airlineCode: nextSegment.operatingCarrier.iataCode,
|
|
65
|
-
aircraft: nextSegment.aircraft?.name,
|
|
66
|
-
flightNumber: nextSegment.operatingCarrierFlightNumber,
|
|
67
|
-
cabin:
|
|
68
|
-
nextSegment.passengers[0].cabinClassMarketingName ??
|
|
69
|
-
nextSegment.passengers[0].cabinClass ??
|
|
70
|
-
getCabinClass(),
|
|
71
|
-
departureTimeZone: nextSegment.origin.timeZone
|
|
72
|
-
},
|
|
73
|
-
layoverTime: calculateTimeDifference({
|
|
74
|
-
// we need to swap around the departure and arrival times
|
|
75
|
-
departureAt: segment.arrivingAt,
|
|
76
|
-
departureTimeZone: segment.destination.timeZone,
|
|
77
|
-
arrivalAt: nextSegment.departingAt,
|
|
78
|
-
arrivalTimeZone: nextSegment.origin.timeZone
|
|
79
|
-
})
|
|
80
|
-
};
|
|
81
|
-
// TODO: get the ASAPI to give us what
|
|
82
|
-
// we need to calculate the layover time
|
|
83
|
-
if (layover.layoverTime <= 0) {
|
|
84
|
-
// if we can't calculate the layover time, we'll just
|
|
85
|
-
onDisableRenderItinerary?.();
|
|
86
|
-
}
|
|
87
|
-
layovers.push(layover);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return layovers;
|
|
91
|
-
}, [itinerary]);
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div className='flight-itinerary'>
|
|
95
|
-
<Departure
|
|
96
|
-
airport={itinerary[0].origin.name}
|
|
97
|
-
city={origin.city}
|
|
98
|
-
country={getCountryNameByCode(origin.country)}
|
|
99
|
-
departureAt={itinerary[0].departingAt}
|
|
100
|
-
departureTimeZone={itinerary[0].origin.timeZone}
|
|
101
|
-
arrivalAt={itinerary[0].arrivingAt}
|
|
102
|
-
arrivalTimeZone={itinerary[0].destination.timeZone}
|
|
103
|
-
airline={itinerary[0].operatingCarrier.name}
|
|
104
|
-
airlineCode={itinerary[0].operatingCarrier.iataCode}
|
|
105
|
-
aircraft={itinerary[0].aircraft?.name}
|
|
106
|
-
flightNumber={itinerary[0].operatingCarrierFlightNumber}
|
|
107
|
-
isMultiDayFlight={isMultiDayFlight}
|
|
108
|
-
/>
|
|
109
|
-
{layovers.map((layover, index) => (
|
|
110
|
-
<React.Fragment key={layover.departure.flightNumber || index}>
|
|
111
|
-
<Arrival
|
|
112
|
-
airport={layover.arrival.airport}
|
|
113
|
-
city={layover.arrival.city}
|
|
114
|
-
country={layover.arrival.country}
|
|
115
|
-
time={layover.arrival.time}
|
|
116
|
-
isLayover
|
|
117
|
-
layoverTime={getTimeString(layover.layoverTime)}
|
|
118
|
-
arrivalTimeZone={getTimezoneOffsetOrCode(
|
|
119
|
-
layover.arrival.arrivalTimeZone,
|
|
120
|
-
layover.arrival.time
|
|
121
|
-
)}
|
|
122
|
-
/>
|
|
123
|
-
<Departure
|
|
124
|
-
airport={layover.departure?.airport}
|
|
125
|
-
city={layover.departure?.city}
|
|
126
|
-
country={layover.departure?.country}
|
|
127
|
-
departureAt={layover.departure.time}
|
|
128
|
-
departureTimeZone={layover.departure.departureTimeZone}
|
|
129
|
-
// we need to get the next segment's arrival time when
|
|
130
|
-
// it's a layover
|
|
131
|
-
arrivalAt={itinerary[index + 1].arrivingAt}
|
|
132
|
-
// we need to get the next segment's arrival timezone when
|
|
133
|
-
// it's a layover
|
|
134
|
-
arrivalTimeZone={itinerary[index + 1].destination.timeZone}
|
|
135
|
-
airline={layover.departure?.airline}
|
|
136
|
-
airlineCode={layover.departure?.airlineCode}
|
|
137
|
-
aircraft={layover.departure?.aircraft}
|
|
138
|
-
flightNumber={layover.departure?.flightNumber}
|
|
139
|
-
/>
|
|
140
|
-
</React.Fragment>
|
|
141
|
-
))}
|
|
142
|
-
<Arrival
|
|
143
|
-
airport={itinerary.getLast()?.destination?.name}
|
|
144
|
-
city={destination.city}
|
|
145
|
-
country={getCountryNameByCode(destination.country)}
|
|
146
|
-
time={itinerary.getLast()?.arrivingAt}
|
|
147
|
-
arrivalTimeZone={getTimezoneOffsetOrCode(
|
|
148
|
-
itinerary.getLast()?.destination.timeZone,
|
|
149
|
-
itinerary.getLast()?.arrivingAt
|
|
150
|
-
)}
|
|
151
|
-
isMultiDayFlight={isMultiDayFlight}
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
interface IDepartureProps {
|
|
158
|
-
airport?: string;
|
|
159
|
-
city?: string;
|
|
160
|
-
country?: string;
|
|
161
|
-
airline?: string;
|
|
162
|
-
airlineCode?: string;
|
|
163
|
-
aircraft?: string;
|
|
164
|
-
flightNumber?: string;
|
|
165
|
-
testTimezone?: string;
|
|
166
|
-
departureAt: string;
|
|
167
|
-
departureTimeZone: string;
|
|
168
|
-
arrivalAt: string;
|
|
169
|
-
arrivalTimeZone: string;
|
|
170
|
-
isMultiDayFlight?: boolean;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const Departure = ({
|
|
174
|
-
airport,
|
|
175
|
-
city,
|
|
176
|
-
country,
|
|
177
|
-
airline,
|
|
178
|
-
airlineCode,
|
|
179
|
-
flightNumber,
|
|
180
|
-
aircraft,
|
|
181
|
-
departureAt,
|
|
182
|
-
departureTimeZone,
|
|
183
|
-
arrivalAt,
|
|
184
|
-
arrivalTimeZone,
|
|
185
|
-
isMultiDayFlight
|
|
186
|
-
}: IDepartureProps) => {
|
|
187
|
-
const { t } = useTranslation();
|
|
188
|
-
const timeTravelled = calculateTimeDifference({
|
|
189
|
-
departureAt,
|
|
190
|
-
departureTimeZone,
|
|
191
|
-
arrivalAt,
|
|
192
|
-
arrivalTimeZone
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<div className='departure'>
|
|
197
|
-
<p>
|
|
198
|
-
{moment(departureAt).format(
|
|
199
|
-
t(EDateKeys.TIME_ONLY, EDateFormats.FlightTime)
|
|
200
|
-
)}{' '}
|
|
201
|
-
<span className='time-zone'>
|
|
202
|
-
({getTimezoneOffsetOrCode(departureTimeZone, departureAt)})
|
|
203
|
-
</span>
|
|
204
|
-
<br />
|
|
205
|
-
{isMultiDayFlight ? (
|
|
206
|
-
<div className='travel-time'>
|
|
207
|
-
<FaCalendar />
|
|
208
|
-
<span>
|
|
209
|
-
Departure:{' '}
|
|
210
|
-
{moment(departureAt).format(
|
|
211
|
-
t(EDateKeys.DISPLAY_MEDIUM, EDateFormats.StringDateFormat)
|
|
212
|
-
)}
|
|
213
|
-
</span>
|
|
214
|
-
</div>
|
|
215
|
-
) : (
|
|
216
|
-
<></>
|
|
217
|
-
)}
|
|
218
|
-
<div className='travel-time'>
|
|
219
|
-
<FaClock />
|
|
220
|
-
<span>Travel Time: {getTimeString(timeTravelled)}</span>
|
|
221
|
-
</div>
|
|
222
|
-
</p>
|
|
223
|
-
<div>
|
|
224
|
-
<span>
|
|
225
|
-
{airport}, {city}, {country}
|
|
226
|
-
</span>
|
|
227
|
-
<img src={`${DUFFEL_ASSET_PATH}${airlineCode}.svg`} alt={airline} />
|
|
228
|
-
<p>
|
|
229
|
-
<strong>Aircraft:</strong> {aircraft}
|
|
230
|
-
</p>
|
|
231
|
-
<p>
|
|
232
|
-
<strong>Flight:</strong> {airlineCode} {flightNumber}
|
|
233
|
-
</p>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
);
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
interface IArrivalProps {
|
|
240
|
-
airport?: string;
|
|
241
|
-
city?: string;
|
|
242
|
-
country?: string;
|
|
243
|
-
time?: string;
|
|
244
|
-
isLayover?: boolean;
|
|
245
|
-
layoverTime?: string;
|
|
246
|
-
arrivalTimeZone?: string;
|
|
247
|
-
isMultiDayFlight?: boolean;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const Arrival = ({
|
|
251
|
-
airport,
|
|
252
|
-
city,
|
|
253
|
-
country,
|
|
254
|
-
time,
|
|
255
|
-
isLayover,
|
|
256
|
-
layoverTime,
|
|
257
|
-
arrivalTimeZone,
|
|
258
|
-
isMultiDayFlight
|
|
259
|
-
}: IArrivalProps) => {
|
|
260
|
-
const { t } = useTranslation('dates');
|
|
261
|
-
return (
|
|
262
|
-
<div className={`arrival ${isLayover ? 'layover' : ''}`}>
|
|
263
|
-
<p>
|
|
264
|
-
{moment(time).format(t(EDateKeys.TIME_ONLY, EDateFormats.FlightTime))}{' '}
|
|
265
|
-
<span className='time-zone'>({arrivalTimeZone})</span>
|
|
266
|
-
{isMultiDayFlight && !isLayover ? (
|
|
267
|
-
<div className='travel-time'>
|
|
268
|
-
<FaCalendar />
|
|
269
|
-
<span>
|
|
270
|
-
Arrival:{' '}
|
|
271
|
-
{moment(time).format(
|
|
272
|
-
t(EDateKeys.DISPLAY_MEDIUM, EDateFormats.StringDateFormat)
|
|
273
|
-
)}
|
|
274
|
-
</span>
|
|
275
|
-
</div>
|
|
276
|
-
) : (
|
|
277
|
-
<></>
|
|
278
|
-
)}
|
|
279
|
-
</p>
|
|
280
|
-
<div>
|
|
281
|
-
<p>
|
|
282
|
-
{isLayover ? 'Layover in' : ''} {airport}, {city}, {country}
|
|
283
|
-
</p>
|
|
284
|
-
<i>{isLayover ? layoverTime : ''}</i>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
);
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
export default FlightItinerary;
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
.flight-itinerary {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
|
|
5
|
-
.layover {
|
|
6
|
-
position: relative;
|
|
7
|
-
padding-bottom: calc($padding * 3);
|
|
8
|
-
|
|
9
|
-
&::before {
|
|
10
|
-
content: '';
|
|
11
|
-
position: absolute;
|
|
12
|
-
top: 0;
|
|
13
|
-
bottom: 0;
|
|
14
|
-
left: calc($padding / 2 + 1px);
|
|
15
|
-
width: 3px;
|
|
16
|
-
background: repeating-linear-gradient(0deg, #d8e3ea 0 5px, #0000 0 8px);
|
|
17
|
-
z-index: 7;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
> div {
|
|
21
|
-
> p,
|
|
22
|
-
> i {
|
|
23
|
-
color: #a9b2b7;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.departure,
|
|
29
|
-
.arrival {
|
|
30
|
-
display: flex;
|
|
31
|
-
gap: calc($padding * 2);
|
|
32
|
-
|
|
33
|
-
> p {
|
|
34
|
-
position: relative;
|
|
35
|
-
margin-left: calc($padding * 2);
|
|
36
|
-
|
|
37
|
-
&::before {
|
|
38
|
-
content: '';
|
|
39
|
-
border: 2px solid #d8e3ea;
|
|
40
|
-
height: 1rem;
|
|
41
|
-
width: 1rem;
|
|
42
|
-
border-radius: 50%;
|
|
43
|
-
position: absolute;
|
|
44
|
-
left: calc($padding * -2);
|
|
45
|
-
top: -0.2rem;
|
|
46
|
-
z-index: 10;
|
|
47
|
-
background-color: colour.$foreground;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.time-zone {
|
|
51
|
-
color: colour.$text-light;
|
|
52
|
-
font-size: font.$small;
|
|
53
|
-
}
|
|
54
|
-
.travel-time {
|
|
55
|
-
color: colour.$text-light;
|
|
56
|
-
font-size: font.$small;
|
|
57
|
-
display: flex;
|
|
58
|
-
flex-direction: row;
|
|
59
|
-
margin-top: $padding;
|
|
60
|
-
gap: calc($padding/4);
|
|
61
|
-
align-items: center;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.departure {
|
|
67
|
-
padding-bottom: calc($padding * 2);
|
|
68
|
-
position: relative;
|
|
69
|
-
|
|
70
|
-
&::before {
|
|
71
|
-
content: '';
|
|
72
|
-
position: absolute;
|
|
73
|
-
top: 0;
|
|
74
|
-
bottom: 0;
|
|
75
|
-
left: calc($padding / 2 + 1px);
|
|
76
|
-
width: 2px;
|
|
77
|
-
background-color: #d8e3ea;
|
|
78
|
-
z-index: 7;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
div {
|
|
82
|
-
flex: 1;
|
|
83
|
-
display: flex;
|
|
84
|
-
flex-direction: column;
|
|
85
|
-
gap: 4px;
|
|
86
|
-
position: relative;
|
|
87
|
-
border-radius: $border-radius;
|
|
88
|
-
|
|
89
|
-
span {
|
|
90
|
-
font-weight: 500;
|
|
91
|
-
}
|
|
92
|
-
img {
|
|
93
|
-
max-width: 150px;
|
|
94
|
-
max-height: 25px;
|
|
95
|
-
align-self: flex-start;
|
|
96
|
-
background-color: colour.$light;
|
|
97
|
-
padding: $padding-xs $padding-sm;
|
|
98
|
-
border-radius: calc($border-radius / 2);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|