@duffel/components 3.0.7-canary → 3.1.2--prototype

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 (232) hide show
  1. package/.eslintrc.js +8 -0
  2. package/.github/CODEOWNERS +4 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  4. package/.github/renovate.json +1 -5
  5. package/.github/workflows/release.yml +3 -0
  6. package/.storybook/__snapshots__/Storyshots.test.js.snap +20384 -533
  7. package/.tool-versions +1 -1
  8. package/README.md +16 -2
  9. package/config/esbuild.base.config.js +6 -2
  10. package/config/esbuild.cdn.config.js +2 -1
  11. package/config/esbuild.dev.config.js +2 -1
  12. package/config/esbuild.react.config.js +1 -1
  13. package/data/airports.csv +9084 -0
  14. package/data/cities.csv +256 -0
  15. package/package.json +11 -1
  16. package/react-dist/index.js +51 -21
  17. package/scripts/generate-fixture.ts +13 -8
  18. package/scripts/setup-suggestion-data.ts +100 -0
  19. package/scripts/upload-to-cdn.sh +1 -1
  20. package/src/components/{Card.tsx → DuffelAncillaries/Card.tsx} +1 -1
  21. package/src/components/{Counter.tsx → DuffelAncillaries/Counter.tsx} +1 -1
  22. package/src/components/{DuffelAncillaries.tsx → DuffelAncillaries/DuffelAncillaries.tsx} +68 -64
  23. package/src/components/{DuffelAncillariesCustomElement.tsx → DuffelAncillaries/DuffelAncillariesCustomElement.tsx} +2 -2
  24. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.tsx +10 -5
  25. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.tsx +2 -2
  26. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.tsx +4 -4
  27. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.tsx +3 -3
  28. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.tsx +4 -4
  29. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.tsx +23 -16
  30. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.tsx +24 -18
  31. package/src/components/{bags → DuffelAncillaries/bags}/IncludedBaggageBanner.tsx +1 -1
  32. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionCard.tsx +4 -4
  33. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModal.tsx +3 -3
  34. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBody.tsx +3 -3
  35. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBodyListItem.tsx +1 -1
  36. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalFooter.tsx +5 -5
  37. package/src/components/{seats → DuffelAncillaries/seats}/Amenity.tsx +2 -2
  38. package/src/components/{seats → DuffelAncillaries/seats}/DeckSelect.tsx +1 -1
  39. package/src/components/{seats → DuffelAncillaries/seats}/Element.tsx +2 -2
  40. package/src/components/{seats → DuffelAncillaries/seats}/ExitElement.tsx +1 -1
  41. package/src/components/{seats → DuffelAncillaries/seats}/Legend.tsx +2 -2
  42. package/src/components/{seats → DuffelAncillaries/seats}/Row.tsx +2 -2
  43. package/src/components/{seats → DuffelAncillaries/seats}/RowSection.tsx +5 -2
  44. package/src/components/{seats → DuffelAncillaries/seats}/SeatElement.tsx +3 -3
  45. package/src/components/{seats → DuffelAncillaries/seats}/SeatInfo.tsx +1 -1
  46. package/src/components/{seats → DuffelAncillaries/seats}/SeatMap.tsx +6 -2
  47. package/src/components/{seats → DuffelAncillaries/seats}/SeatMapUnavailable.tsx +1 -1
  48. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionCard.tsx +5 -5
  49. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModal.tsx +5 -5
  50. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.tsx +1 -1
  51. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.tsx +24 -17
  52. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.tsx +30 -20
  53. package/src/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.tsx +2 -2
  54. package/src/components/DuffelPayments/DuffelPayments.tsx +224 -0
  55. package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -0
  56. package/src/components/PlacesLookup/PlacesLookup.tsx +123 -0
  57. package/src/components/ShowData/ShowData.tsx +38 -0
  58. package/src/components/ShowData/ShowDataCustomElement.tsx +85 -0
  59. package/src/components/{Button.tsx → shared/Button.tsx} +4 -3
  60. package/src/components/{ErrorBoundary.tsx → shared/ErrorBoundary.tsx} +2 -2
  61. package/src/components/{Icon.tsx → shared/Icon.tsx} +11 -11
  62. package/src/components/{IconButton.tsx → shared/IconButton.tsx} +1 -1
  63. package/src/components/{Modal.tsx → shared/Modal.tsx} +5 -1
  64. package/src/components/{NonIdealState.tsx → shared/NonIdealState.tsx} +1 -1
  65. package/src/custom-elements.ts +6 -1
  66. package/src/examples/client-side/index.html +1 -1
  67. package/src/examples/full-stack/index.html +1 -1
  68. package/src/examples/full-stack/server.mjs +1 -0
  69. package/src/examples/just-typescript/src/index.html +2 -2
  70. package/src/examples/just-typescript/src/index.ts +2 -1
  71. package/src/examples/payments-custom-element/README.md +17 -0
  72. package/src/examples/payments-custom-element/index.html +43 -0
  73. package/src/examples/payments-just-typescript/README.md +37 -0
  74. package/src/examples/payments-just-typescript/package.json +16 -0
  75. package/src/examples/payments-just-typescript/src/index.html +23 -0
  76. package/src/examples/payments-just-typescript/src/index.ts +18 -0
  77. package/src/examples/react-app/src/index.tsx +11 -6
  78. package/src/fixtures/offers/off_1.json +1 -10
  79. package/src/index.ts +3 -1
  80. package/src/lib/captureErrorInSentry.ts +2 -20
  81. package/src/lib/fetchFromDuffelAPI.ts +36 -6
  82. package/src/lib/formatDate.ts +3 -4
  83. package/src/lib/getBaggageServiceDescription.ts +1 -6
  84. package/src/lib/getPassengerName.ts +4 -0
  85. package/src/lib/getTotalAmountForServices.ts +1 -1
  86. package/src/lib/hasHighLuminance.ts +9 -0
  87. package/src/lib/logging.ts +52 -32
  88. package/src/lib/retrieveOffer.ts +13 -6
  89. package/src/lib/retrieveSeatMaps.ts +13 -8
  90. package/src/stories/BaggageSelectionModalHeader.stories.tsx +1 -1
  91. package/src/stories/Button.stories.tsx +33 -2
  92. package/src/stories/DuffelAncillaries.stories.tsx +42 -2
  93. package/src/stories/DuffelPayments.stories.tsx +34 -0
  94. package/src/stories/Icon.stories.tsx +3 -2
  95. package/src/stories/IconButton.stories.tsx +1 -1
  96. package/src/stories/PlacesLookup.stories.tsx +22 -0
  97. package/src/stories/ShowData.stories.tsx +16 -0
  98. package/src/styles/components/Button.css +11 -3
  99. package/src/styles/components/Card.css +3 -3
  100. package/src/styles/components/CfarSelectionModal.css +1 -1
  101. package/src/styles/components/DuffelPayments.css +42 -0
  102. package/src/styles/components/Legend.css +10 -6
  103. package/src/styles/components/LoadingState.css +8 -2
  104. package/src/styles/components/Modal.css +2 -1
  105. package/src/styles/components/PassengerSelect.css +8 -2
  106. package/src/styles/components/PlacesLookup.css +36 -0
  107. package/src/styles/components/Seat.css +9 -7
  108. package/src/styles/components/SeatInfo.css +1 -1
  109. package/src/styles/components/Tabs.css +5 -2
  110. package/src/styles/global.css +2 -0
  111. package/src/tests/components/DuffelAncillaries.test.tsx +1 -1
  112. package/src/tests/lib/createPriceFormatters.test.tsx +1 -1
  113. package/src/tests/lib/formatAvailableServices.test.tsx +1 -1
  114. package/src/tests/lib/formatSeatMaps.test.tsx +2 -2
  115. package/src/tests/lib/getCurrencyForServices.test.tsx +1 -1
  116. package/src/tests/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.test.ts +1 -1
  117. package/src/tests/lib/logging.test.tsx +14 -14
  118. package/src/tests/lib/moneyStringFormatter.test.tsx +1 -1
  119. package/src/tests/lib/validateProps.test.tsx +1 -1
  120. package/src/types/DuffelAncillariesProps.ts +1 -1
  121. package/react-dist/components/AnimatedLoaderEllipsis.d.ts +0 -2
  122. package/react-dist/components/Button.d.ts +0 -23
  123. package/react-dist/components/Card.d.ts +0 -14
  124. package/react-dist/components/Counter.d.ts +0 -10
  125. package/react-dist/components/DuffelAncillaries.d.ts +0 -3
  126. package/react-dist/components/DuffelAncillariesCustomElement.d.ts +0 -13
  127. package/react-dist/components/ErrorBoundary.d.ts +0 -13
  128. package/react-dist/components/FetchOfferErrorState.d.ts +0 -5
  129. package/react-dist/components/Icon.d.ts +0 -44
  130. package/react-dist/components/IconButton.d.ts +0 -16
  131. package/react-dist/components/Modal.d.ts +0 -11
  132. package/react-dist/components/NonIdealState.d.ts +0 -4
  133. package/react-dist/components/Stamp.d.ts +0 -7
  134. package/react-dist/components/Tabs.d.ts +0 -16
  135. package/react-dist/components/bags/BaggageSelectionCard.d.ts +0 -11
  136. package/react-dist/components/bags/BaggageSelectionController.d.ts +0 -13
  137. package/react-dist/components/bags/BaggageSelectionModal.d.ts +0 -11
  138. package/react-dist/components/bags/BaggageSelectionModalBody.d.ts +0 -11
  139. package/react-dist/components/bags/BaggageSelectionModalBodyPassenger.d.ts +0 -13
  140. package/react-dist/components/bags/BaggageSelectionModalFooter.d.ts +0 -14
  141. package/react-dist/components/bags/BaggageSelectionModalHeader.d.ts +0 -9
  142. package/react-dist/components/bags/IncludedBaggageBanner.d.ts +0 -7
  143. package/react-dist/components/cancel_for_any_reason/CfarSelectionCard.d.ts +0 -10
  144. package/react-dist/components/cancel_for_any_reason/CfarSelectionModal.d.ts +0 -11
  145. package/react-dist/components/cancel_for_any_reason/CfarSelectionModalBody.d.ts +0 -7
  146. package/react-dist/components/cancel_for_any_reason/CfarSelectionModalBodyListItem.d.ts +0 -4
  147. package/react-dist/components/cancel_for_any_reason/CfarSelectionModalFooter.d.ts +0 -11
  148. package/react-dist/components/cancel_for_any_reason/CfarSelectionModalHeader.d.ts +0 -2
  149. package/react-dist/components/seats/Amenity.d.ts +0 -6
  150. package/react-dist/components/seats/DeckSelect.d.ts +0 -15
  151. package/react-dist/components/seats/Element.d.ts +0 -15
  152. package/react-dist/components/seats/EmptyElement.d.ts +0 -2
  153. package/react-dist/components/seats/ExitElement.d.ts +0 -6
  154. package/react-dist/components/seats/Legend.d.ts +0 -12
  155. package/react-dist/components/seats/Row.d.ts +0 -13
  156. package/react-dist/components/seats/RowSection.d.ts +0 -17
  157. package/react-dist/components/seats/SeatElement.d.ts +0 -13
  158. package/react-dist/components/seats/SeatInfo.d.ts +0 -7
  159. package/react-dist/components/seats/SeatMap.d.ts +0 -12
  160. package/react-dist/components/seats/SeatMapUnavailable.d.ts +0 -2
  161. package/react-dist/components/seats/SeatSelectionCard.d.ts +0 -13
  162. package/react-dist/components/seats/SeatSelectionModal.d.ts +0 -13
  163. package/react-dist/components/seats/SeatSelectionModalBody.d.ts +0 -4
  164. package/react-dist/components/seats/SeatSelectionModalFooter.d.ts +0 -16
  165. package/react-dist/components/seats/SeatSelectionModalHeader.d.ts +0 -10
  166. package/react-dist/components/seats/SeatUnavailable.d.ts +0 -5
  167. package/react-dist/custom-elements.d.ts +0 -5
  168. package/react-dist/custom-elements.js +0 -36
  169. package/react-dist/custom-elements.js.map +0 -7
  170. package/react-dist/index.d.ts +0 -6
  171. package/react-dist/index.js.map +0 -7
  172. package/react-dist/lib/captureErrorInSentry.d.ts +0 -1
  173. package/react-dist/lib/compileCreateOrderPayload.d.ts +0 -14
  174. package/react-dist/lib/createPriceFormatters.d.ts +0 -12
  175. package/react-dist/lib/fetchFromDuffelAPI.d.ts +0 -1
  176. package/react-dist/lib/fetchFromFixtures.d.ts +0 -4
  177. package/react-dist/lib/formatAvailableServices.d.ts +0 -12
  178. package/react-dist/lib/formatDate.d.ts +0 -2
  179. package/react-dist/lib/formatSeatMaps.d.ts +0 -4
  180. package/react-dist/lib/getBaggageServiceDescription.d.ts +0 -2
  181. package/react-dist/lib/getCabinsForSegmentAndDeck.d.ts +0 -2
  182. package/react-dist/lib/getCurrencyForSeatMaps.d.ts +0 -10
  183. package/react-dist/lib/getCurrencyForServices.d.ts +0 -11
  184. package/react-dist/lib/getFirstSeatElementMatchingCriteria.d.ts +0 -3
  185. package/react-dist/lib/getPassengerBySegmentList.d.ts +0 -6
  186. package/react-dist/lib/getPassengerInitials.d.ts +0 -1
  187. package/react-dist/lib/getPassengerMapById.d.ts +0 -3
  188. package/react-dist/lib/getPassengerName.d.ts +0 -3
  189. package/react-dist/lib/getRowNumber.d.ts +0 -2
  190. package/react-dist/lib/getSegmentList.d.ts +0 -2
  191. package/react-dist/lib/getServicePriceMapById.d.ts +0 -3
  192. package/react-dist/lib/getSymbols.d.ts +0 -2
  193. package/react-dist/lib/getTotalAmountForServices.d.ts +0 -6
  194. package/react-dist/lib/getTotalQuantity.d.ts +0 -2
  195. package/react-dist/lib/hasService.d.ts +0 -2
  196. package/react-dist/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.d.ts +0 -3
  197. package/react-dist/lib/hasWings.d.ts +0 -2
  198. package/react-dist/lib/isBaggageService.d.ts +0 -2
  199. package/react-dist/lib/isCancelForAnyReasonService.d.ts +0 -2
  200. package/react-dist/lib/isFixtureOfferId.d.ts +0 -2
  201. package/react-dist/lib/isPayloadComplete.d.ts +0 -2
  202. package/react-dist/lib/isSeatElement.d.ts +0 -2
  203. package/react-dist/lib/logging.d.ts +0 -53
  204. package/react-dist/lib/moneyStringFormatter.d.ts +0 -8
  205. package/react-dist/lib/offerIsExpired.d.ts +0 -2
  206. package/react-dist/lib/retrieveOffer.d.ts +0 -2
  207. package/react-dist/lib/retrieveOfferFromDuffelAPI.d.ts +0 -1
  208. package/react-dist/lib/retrieveSeatMaps.d.ts +0 -2
  209. package/react-dist/lib/retrieveSeatMapsFromDuffelAPI.d.ts +0 -1
  210. package/react-dist/lib/setBodyScrollability.d.ts +0 -1
  211. package/react-dist/lib/validateProps.d.ts +0 -7
  212. package/react-dist/lib/withPlural.d.ts +0 -1
  213. package/react-dist/types/Aircraft.d.ts +0 -14
  214. package/react-dist/types/Airline.d.ts +0 -14
  215. package/react-dist/types/Airport.d.ts +0 -44
  216. package/react-dist/types/City.d.ts +0 -18
  217. package/react-dist/types/CreateOrderPayload.d.ts +0 -72
  218. package/react-dist/types/CurrencyConversion.d.ts +0 -10
  219. package/react-dist/types/DuffelAncillariesProps.d.ts +0 -70
  220. package/react-dist/types/Offer.d.ts +0 -711
  221. package/react-dist/types/Order.d.ts +0 -8
  222. package/react-dist/types/Place.d.ts +0 -8
  223. package/react-dist/types/SeatMap.d.ts +0 -190
  224. package/react-dist/types/index.d.ts +0 -11
  225. package/src/examples/just-typescript/yarn.lock +0 -154
  226. package/src/examples/react-app/yarn.lock +0 -219
  227. /package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalHeader.tsx +0 -0
  228. /package/src/components/{seats → DuffelAncillaries/seats}/EmptyElement.tsx +0 -0
  229. /package/src/components/{AnimatedLoaderEllipsis.tsx → shared/AnimatedLoaderEllipsis.tsx} +0 -0
  230. /package/src/components/{FetchOfferErrorState.tsx → shared/FetchOfferErrorState.tsx} +0 -0
  231. /package/src/components/{Stamp.tsx → shared/Stamp.tsx} +0 -0
  232. /package/src/components/{Tabs.tsx → shared/Tabs.tsx} +0 -0
@@ -10,6 +10,9 @@ const DUFFEL_API_TOKEN = process.env.DUFFEL_API_TOKEN || "";
10
10
  const DUFFEL_API_URL = process.env.DUFFEL_API_URL || "";
11
11
  const VERBOSE = process.env.VERBOSE === "true";
12
12
 
13
+ // eslint-disable-next-line
14
+ const log = console.log;
15
+
13
16
  if (DUFFEL_API_URL.includes("localhost"))
14
17
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
15
18
 
@@ -89,7 +92,8 @@ const main = async () => {
89
92
  const sliceInput = new Array<[string, string]>(sliceCount);
90
93
  for (let i = 0; i < sliceCount; i++) {
91
94
  let origin = sliceInput[i - 1]?.[0];
92
- console.log(`\nSlice #${i + 1}`);
95
+
96
+ log(`\nSlice #${i + 1}`);
93
97
  const { value } = await prompts({
94
98
  type: "text",
95
99
  name: "value",
@@ -108,7 +112,7 @@ const main = async () => {
108
112
  }
109
113
 
110
114
  // ask how many adults
111
- console.log(`\n`);
115
+ log(`\n`);
112
116
  const { adultCount } = await prompts({
113
117
  type: "number",
114
118
  name: "adultCount",
@@ -117,7 +121,7 @@ const main = async () => {
117
121
  });
118
122
 
119
123
  // ask for requested sources
120
- console.log(`\n`);
124
+ log(`\n`);
121
125
  const { requestedSources } = await prompts({
122
126
  type: "text",
123
127
  name: "requestedSources",
@@ -135,7 +139,7 @@ const main = async () => {
135
139
  const airlines = new Set(
136
140
  offerRequest.offers.map((offer) => offer.owner.iata_code)
137
141
  );
138
- console.log(
142
+ log(
139
143
  `Received ${withPlural(
140
144
  offerRequest.offers.length,
141
145
  "offer",
@@ -147,7 +151,7 @@ const main = async () => {
147
151
  "airlines"
148
152
  )}(${Array.from(airlines.values()).join(",")})`
149
153
  );
150
- console.log(
154
+ log(
151
155
  `Search completed, offer request ID: ${offerRequest.id}.\nUsing first offer to get services: ${offerRequest.offers[0].id}\n`
152
156
  );
153
157
  }
@@ -183,10 +187,11 @@ const main = async () => {
183
187
  JSON.stringify(seatMaps, null, 2)
184
188
  );
185
189
 
186
- console.log(`\n🐄 Fixtures saved for ${firstOffer.id}`);
187
- console.log(` ↳ /src/fixtures/offer/${firstOffer.id}.json`);
188
- console.log(` ↳ /src/fixtures/seat-maps/${firstOffer.id}.json\n`);
190
+ log(`\n🐄 Fixtures saved for ${firstOffer.id}`);
191
+ log(` ↳ /src/fixtures/offer/${firstOffer.id}.json`);
192
+ log(` ↳ /src/fixtures/seat-maps/${firstOffer.id}.json\n`);
189
193
  } catch (err) {
194
+ // eslint-disable-next-line
190
195
  console.error(err);
191
196
  process.exit(1);
192
197
  }
@@ -0,0 +1,100 @@
1
+ import fs from "fs";
2
+ import readline from "readline";
3
+
4
+ async function readCSVIntoMatrix(
5
+ filePath: string,
6
+ linePrefix: string,
7
+ keys: string[]
8
+ ): Promise<string[]> {
9
+ const rl = readline.createInterface({
10
+ input: fs.createReadStream(filePath),
11
+ crlfDelay: Infinity,
12
+ });
13
+
14
+ // this will map a key into the index
15
+ let keyNameToIndexMap: Record<string, number> = {};
16
+ const matrix = new Array<string>();
17
+
18
+ rl.on("line", (line) => {
19
+ if (Object.keys(keyNameToIndexMap).length === 0) {
20
+ // it's the header line
21
+ keyNameToIndexMap = line
22
+ .split(",")
23
+ .reduce((acc, key, index) => ({ ...acc, [key]: index }), {});
24
+ } else {
25
+ // it's a data line
26
+ const row = line.split(",");
27
+ const rowObject =
28
+ linePrefix +
29
+ keys
30
+ .map((key) => {
31
+ const index = keyNameToIndexMap[key];
32
+ return row[index];
33
+ })
34
+ .join(`,`);
35
+ matrix.push(rowObject);
36
+ }
37
+ });
38
+
39
+ await new Promise((res) => rl.once("close", res));
40
+ return matrix;
41
+ }
42
+
43
+ export interface CityCsvRow {
44
+ id: string;
45
+ iata_code: string;
46
+ iata_country_code: string;
47
+ name: string;
48
+ inserted_at: string;
49
+ updated_at: string;
50
+ metropolitan_area: string;
51
+ }
52
+
53
+ async function loadCities() {
54
+ return await readCSVIntoMatrix("./data/cities.csv", "C,", [
55
+ "name",
56
+ "iata_code",
57
+ ]);
58
+ }
59
+
60
+ export interface AirportCsvRow {
61
+ id: string;
62
+ iata_code: string;
63
+ iata_city_code: string;
64
+ iata_country_code: string;
65
+ name: string;
66
+ inserted_at: string;
67
+ updated_at: string;
68
+ source_type: string;
69
+ alternative_names: string;
70
+ icao_code: string;
71
+ disabled_at: string;
72
+ city_name: string;
73
+ latitude: string;
74
+ longitude: string;
75
+ time_zone: string;
76
+ }
77
+
78
+ async function loadAirports() {
79
+ return await readCSVIntoMatrix("./data/airports.csv", "A,", [
80
+ "name",
81
+ "iata_code",
82
+ "latitude",
83
+ "longitude",
84
+ ]);
85
+ }
86
+
87
+ async function main() {
88
+ // load airports csv
89
+ const airports = await loadAirports();
90
+
91
+ // load cities csv
92
+ const cities = await loadCities();
93
+
94
+ fs.writeFileSync(
95
+ "./data/__generated__iata-lookup.json",
96
+ JSON.stringify([...cities, ...airports])
97
+ );
98
+ }
99
+
100
+ export default main();
@@ -16,7 +16,7 @@ if [ -z "$VERSION" ]; then
16
16
  fi
17
17
 
18
18
  # Check if folder exists
19
- if gsutil -q stat "$GCP_PREFIX/$VERSION/index.js"; then
19
+ if gsutil -q stat "$GCP_PREFIX/$VERSION/duffel-ancillaries.js"; then
20
20
  # Confirm with user before overriding
21
21
  read -p "Version \`$VERSION\` already exists. Do you want to override it? (\`Y\` to continue) " -n 1 -r
22
22
  echo
@@ -1,6 +1,6 @@
1
+ import { Icon, IconName } from "@components/shared/Icon";
1
2
  import classNames from "classnames";
2
3
  import * as React from "react";
3
- import { Icon, IconName } from "./Icon";
4
4
 
5
5
  export interface CardProps {
6
6
  buttonTitle: string;
@@ -1,5 +1,5 @@
1
+ import { IconButton } from "@components/shared/IconButton";
1
2
  import * as React from "react";
2
- import { IconButton } from "./IconButton";
3
3
 
4
4
  interface CounterProps {
5
5
  id: string;
@@ -1,9 +1,12 @@
1
+ import { ErrorBoundary } from "@components/shared/ErrorBoundary";
2
+ import { FetchOfferErrorState } from "@components/shared/FetchOfferErrorState";
1
3
  import { compileCreateOrderPayload } from "@lib/compileCreateOrderPayload";
2
4
  import { createPriceFormatters } from "@lib/createPriceFormatters";
3
5
  import { formatAvailableServices } from "@lib/formatAvailableServices";
4
6
  import { formatSeatMaps } from "@lib/formatSeatMaps";
7
+ import { hasHighLuminance } from "@lib/hasHighLuminance";
5
8
  import { isPayloadComplete } from "@lib/isPayloadComplete";
6
- import { LogContext, initializeLogger } from "@lib/logging";
9
+ import { initializeLogger, logGroup } from "@lib/logging";
7
10
  import { offerIsExpired } from "@lib/offerIsExpired";
8
11
  import { retrieveOffer } from "@lib/retrieveOffer";
9
12
  import { retrieveSeatMaps } from "@lib/retrieveSeatMaps";
@@ -19,12 +22,10 @@ import * as React from "react";
19
22
  import {
20
23
  CreateOrderPayloadPassengers,
21
24
  CreateOrderPayloadService,
22
- } from "../types/CreateOrderPayload";
23
- import { DuffelAncillariesProps } from "../types/DuffelAncillariesProps";
24
- import { Offer } from "../types/Offer";
25
- import { SeatMap } from "../types/SeatMap";
26
- import { ErrorBoundary } from "./ErrorBoundary";
27
- import { FetchOfferErrorState } from "./FetchOfferErrorState";
25
+ } from "../../types/CreateOrderPayload";
26
+ import { DuffelAncillariesProps } from "../../types/DuffelAncillariesProps";
27
+ import { Offer } from "../../types/Offer";
28
+ import { SeatMap } from "../../types/SeatMap";
28
29
  import { BaggageSelectionCard } from "./bags/BaggageSelectionCard";
29
30
  import { CfarSelectionCard } from "./cancel_for_any_reason/CfarSelectionCard";
30
31
  import { SeatSelectionCard } from "./seats/SeatSelectionCard";
@@ -33,9 +34,9 @@ const COMPONENT_CDN = process.env.COMPONENT_CDN || "";
33
34
  const hrefToComponentStyles = `${COMPONENT_CDN}/global.css`;
34
35
 
35
36
  export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
36
- const logger = initializeLogger(props.debug || false);
37
+ initializeLogger(props.debug || false);
37
38
 
38
- logger.logGroup("Properties passed into the component:", props);
39
+ logGroup("Properties passed into the component:", props);
39
40
 
40
41
  if (!areDuffelAncillariesPropsValid(props)) {
41
42
  throw new Error(
@@ -115,7 +116,7 @@ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
115
116
  setError(expiryErrorMessage);
116
117
  return;
117
118
  } else {
118
- const msUntilExpiry = new Date(offer.expires_at).getTime() - Date.now();
119
+ const msUntilExpiry = new Date(offer?.expires_at)?.getTime() - Date.now();
119
120
 
120
121
  // Only show the expiry error message if the offer expires in less than a day,
121
122
  // to prevent buffer overflows when showing offers for fixtures, which expire in
@@ -186,7 +187,7 @@ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
186
187
  ? props.offer_id
187
188
  : props.offer.id,
188
189
  !isPropsWithOfferIdForFixture ? props.client_key : null,
189
- setError,
190
+ () => updateSeatMaps([]),
190
191
  setIsSeatMapLoading,
191
192
  updateSeatMaps
192
193
  );
@@ -235,7 +236,7 @@ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
235
236
  cancel_for_any_reason_services: cfarSelectedServices,
236
237
  };
237
238
 
238
- logger.logGroup("Payload ready", {
239
+ logGroup("Payload ready", {
239
240
  "Order creation payload": createOrderPayload,
240
241
  "Services metadata": metadata,
241
242
  });
@@ -263,6 +264,11 @@ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
263
264
  ...(props.styles?.accentColor && {
264
265
  "--ACCENT": props.styles.accentColor,
265
266
  }),
267
+ ...(props.styles?.accentColor &&
268
+ hasHighLuminance(props.styles.accentColor) && {
269
+ "--SECONDARY": "var(--GREY-900)",
270
+ "--TERTIARY": "var(--GREY-400)",
271
+ }),
266
272
  ...(props.styles?.fontFamily && {
267
273
  "--FONT-FAMILY": props.styles.fontFamily,
268
274
  }),
@@ -284,63 +290,61 @@ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
284
290
  error,
285
291
  };
286
292
 
287
- logger.logGroup("Component's internal state:", state);
293
+ logGroup("Component's internal state:", state);
288
294
 
289
295
  return (
290
296
  <>
291
297
  <link rel="stylesheet" href={hrefToComponentStyles}></link>
292
298
 
293
- <LogContext.Provider value={logger}>
294
- <div className="duffel-components" style={duffelComponentsStyle}>
295
- <ErrorBoundary>
296
- {error && (
297
- <FetchOfferErrorState
298
- height={nonIdealStateHeight}
299
- message={error}
300
- />
301
- )}
302
-
303
- {!error &&
304
- props.services.map((ancillaryName) => {
305
- if (ancillaryName === "bags")
306
- return (
307
- <BaggageSelectionCard
308
- key="bags"
309
- isLoading={isOfferLoading}
310
- offer={offer}
311
- passengers={passengers}
312
- selectedServices={baggageSelectedServices}
313
- setSelectedServices={setBaggageSelectedServices}
314
- />
315
- );
316
-
317
- if (ancillaryName === "seats")
318
- return (
319
- <SeatSelectionCard
320
- key="seats"
321
- isLoading={isOfferLoading || isSeatMapLoading}
322
- seatMaps={seatMaps}
323
- offer={offer}
324
- passengers={passengers}
325
- selectedServices={seatSelectedServices}
326
- setSelectedServices={setSeatSelectedServices}
327
- />
328
- );
329
-
330
- if (ancillaryName === "cancel_for_any_reason")
331
- return (
332
- <CfarSelectionCard
333
- key="cancel_for_any_reason"
334
- isLoading={isOfferLoading}
335
- offer={offer}
336
- selectedServices={cfarSelectedServices}
337
- setSelectedServices={setCfarSelectedServices}
338
- />
339
- );
340
- })}
341
- </ErrorBoundary>
342
- </div>
343
- </LogContext.Provider>
299
+ <div className="duffel-components" style={duffelComponentsStyle}>
300
+ <ErrorBoundary>
301
+ {error && (
302
+ <FetchOfferErrorState
303
+ height={nonIdealStateHeight}
304
+ message={error}
305
+ />
306
+ )}
307
+
308
+ {!error &&
309
+ props.services.map((ancillaryName) => {
310
+ if (ancillaryName === "bags")
311
+ return (
312
+ <BaggageSelectionCard
313
+ key="bags"
314
+ isLoading={isOfferLoading}
315
+ offer={offer}
316
+ passengers={passengers}
317
+ selectedServices={baggageSelectedServices}
318
+ setSelectedServices={setBaggageSelectedServices}
319
+ />
320
+ );
321
+
322
+ if (ancillaryName === "seats")
323
+ return (
324
+ <SeatSelectionCard
325
+ key="seats"
326
+ isLoading={isOfferLoading || isSeatMapLoading}
327
+ seatMaps={seatMaps}
328
+ offer={offer}
329
+ passengers={passengers}
330
+ selectedServices={seatSelectedServices}
331
+ setSelectedServices={setSeatSelectedServices}
332
+ />
333
+ );
334
+
335
+ if (ancillaryName === "cancel_for_any_reason")
336
+ return (
337
+ <CfarSelectionCard
338
+ key="cancel_for_any_reason"
339
+ isLoading={isOfferLoading}
340
+ offer={offer}
341
+ selectedServices={cfarSelectedServices}
342
+ setSelectedServices={setCfarSelectedServices}
343
+ />
344
+ );
345
+ })}
346
+ </ErrorBoundary>
347
+ </div>
344
348
  </>
345
349
  );
346
350
  };
@@ -1,5 +1,5 @@
1
1
  import { createRoot, Root } from "react-dom/client";
2
- import { CreateOrderPayload } from "../types/CreateOrderPayload";
2
+ import { CreateOrderPayload } from "../../types/CreateOrderPayload";
3
3
  import {
4
4
  DuffelAncillariesPropsWithClientKeyAndOfferId,
5
5
  DuffelAncillariesPropsWithOfferIdForFixture,
@@ -7,7 +7,7 @@ import {
7
7
  DuffelAncillariesPropWithOfferAndClientKey,
8
8
  OnPayloadReady,
9
9
  OnPayloadReadyMetadata,
10
- } from "../types/DuffelAncillariesProps";
10
+ } from "../../types/DuffelAncillariesProps";
11
11
  import { DuffelAncillaries } from "./DuffelAncillaries";
12
12
 
13
13
  declare global {
@@ -1,3 +1,5 @@
1
+ import { AnimatedLoaderEllipsis } from "@components/shared/AnimatedLoaderEllipsis";
2
+ import { Stamp } from "@components/shared/Stamp";
1
3
  import { getCurrencyForServices } from "@lib/getCurrencyForServices";
2
4
  import { getTotalAmountForServices } from "@lib/getTotalAmountForServices";
3
5
  import { getTotalQuantity } from "@lib/getTotalQuantity";
@@ -8,11 +10,9 @@ import React from "react";
8
10
  import {
9
11
  CreateOrderPayload,
10
12
  CreateOrderPayloadServices,
11
- } from "../../types/CreateOrderPayload";
12
- import { Offer } from "../../types/Offer";
13
- import { AnimatedLoaderEllipsis } from "../AnimatedLoaderEllipsis";
13
+ } from "../../../types/CreateOrderPayload";
14
+ import { Offer } from "../../../types/Offer";
14
15
  import { Card } from "../Card";
15
- import { Stamp } from "../Stamp";
16
16
  import { BaggageSelectionModal } from "./BaggageSelectionModal";
17
17
 
18
18
  export interface BaggageSelectionCardProps {
@@ -86,7 +86,12 @@ export const BaggageSelectionCard: React.FC<BaggageSelectionCardProps> = ({
86
86
  offer={offer}
87
87
  passengers={passengers}
88
88
  onClose={(newSelectedServices) => {
89
- setSelectedServices(newSelectedServices);
89
+ // We need to do a deep copy here because otherwise the modal changing the quantity
90
+ // will affect the selected services regardless of whether it's saved or not
91
+ const newSelectedServicesDeepCopy = JSON.parse(
92
+ JSON.stringify(newSelectedServices)
93
+ );
94
+ setSelectedServices(newSelectedServicesDeepCopy);
90
95
  setIsOpen(false);
91
96
  }}
92
97
  selectedServices={selectedServices}
@@ -2,8 +2,8 @@ import { getBaggageServiceDescription } from "@lib/getBaggageServiceDescription"
2
2
  import { hasServiceOfSameMetadataTypeAlreadyBeenSelected } from "@lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected";
3
3
  import { moneyStringFormatter } from "@lib/moneyStringFormatter";
4
4
  import React from "react";
5
- import { CreateOrderPayloadServices } from "../../types/CreateOrderPayload";
6
- import { OfferAvailableServiceBaggage } from "../../types/Offer";
5
+ import { CreateOrderPayloadServices } from "../../../types/CreateOrderPayload";
6
+ import { OfferAvailableServiceBaggage } from "../../../types/Offer";
7
7
  import { Counter } from "../Counter";
8
8
 
9
9
  interface BaggageSelectionControllerProps {
@@ -1,3 +1,4 @@
1
+ import { Modal } from "@components/shared/Modal";
1
2
  import { getCurrencyForServices } from "@lib/getCurrencyForServices";
2
3
  import { getPassengerMapById } from "@lib/getPassengerMapById";
3
4
  import { getSegmentList } from "@lib/getSegmentList";
@@ -7,9 +8,8 @@ import React, { useState } from "react";
7
8
  import {
8
9
  CreateOrderPayload,
9
10
  CreateOrderPayloadServices,
10
- } from "../../types/CreateOrderPayload";
11
- import { Offer } from "../../types/Offer";
12
- import { Modal } from "../Modal";
11
+ } from "../../../types/CreateOrderPayload";
12
+ import { Offer } from "../../../types/Offer";
13
13
  import { BaggageSelectionModalBody } from "./BaggageSelectionModalBody";
14
14
  import { BaggageSelectionModalFooter } from "./BaggageSelectionModalFooter";
15
15
  import { BaggageSelectionModalHeader } from "./BaggageSelectionModalHeader";
@@ -48,7 +48,7 @@ export const BaggageSelectionModal: React.FC<BaggageSelectionModalProps> = ({
48
48
  }
49
49
 
50
50
  return (
51
- <Modal isOpen={isOpen} onClose={() => onClose(selectedServicesState)}>
51
+ <Modal isOpen={isOpen} onClose={() => onClose(selectedServices)}>
52
52
  <BaggageSelectionModalHeader
53
53
  segmentCount={segments.length}
54
54
  currentSegment={currentSegment}
@@ -1,15 +1,15 @@
1
- import { ModalBody } from "@components/Modal";
1
+ import { ModalBody } from "@components/shared/Modal";
2
2
  import { getPassengerName } from "@lib/getPassengerName";
3
3
  import React from "react";
4
4
  import {
5
5
  CreateOrderPayloadPassenger,
6
6
  CreateOrderPayloadServices,
7
- } from "../../types/CreateOrderPayload";
7
+ } from "../../../types/CreateOrderPayload";
8
8
  import {
9
9
  Offer,
10
10
  OfferAvailableServiceBaggage,
11
11
  OfferSliceSegment,
12
- } from "../../types/Offer";
12
+ } from "../../../types/Offer";
13
13
  import { BaggageSelectionModalBodyPassenger } from "./BaggageSelectionModalBodyPassenger";
14
14
 
15
15
  export interface BaggageSelectionModalBodyProps {
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
- import { CreateOrderPayloadServices } from "../../types/CreateOrderPayload";
2
+ import { CreateOrderPayloadServices } from "../../../types/CreateOrderPayload";
3
3
  import {
4
4
  OfferAvailableServiceBaggage,
5
5
  OfferSliceSegmentPassengerBaggage,
6
- } from "../../types/Offer";
6
+ } from "../../../types/Offer";
7
7
  import { BaggageSelectionController } from "./BaggageSelectionController";
8
8
  import { IncludedBaggageBanner } from "./IncludedBaggageBanner";
9
9
 
@@ -38,9 +38,9 @@ export const BaggageSelectionModalBodyPassenger: React.FC<
38
38
  <h3 style={{ margin: 0 }} className="p1--semibold">
39
39
  {passengerName}
40
40
  </h3>
41
- {hasIncludedBaggage && (
41
+ {hasIncludedBaggage ? (
42
42
  <IncludedBaggageBanner includedBaggage={includedBaggage} />
43
- )}
43
+ ) : null}
44
44
 
45
45
  <div style={{ display: "flex", rowGap: "8px", flexDirection: "column" }}>
46
46
  {passengerServicesForSegment.map((availableService) => (
@@ -1,11 +1,11 @@
1
+ import { Button } from "@components/shared/Button";
1
2
  import { ServicePriceMapById } from "@lib/getServicePriceMapById";
2
3
  import { getTotalAmountForServicesWithPriceMap } from "@lib/getTotalAmountForServices";
3
4
  import { getTotalQuantity } from "@lib/getTotalQuantity";
4
5
  import { moneyStringFormatter } from "@lib/moneyStringFormatter";
5
6
  import { withPlural } from "@lib/withPlural";
6
7
  import React from "react";
7
- import { CreateOrderPayloadServices } from "../../types/CreateOrderPayload";
8
- import { Button } from "../Button";
8
+ import { CreateOrderPayloadServices } from "../../../types/CreateOrderPayload";
9
9
 
10
10
  export interface BaggageSelectionModalFooterProps {
11
11
  currency: string;
@@ -38,6 +38,7 @@ export const BaggageSelectionModalFooter: React.FC<
38
38
  selectedServices
39
39
  );
40
40
  const totalAmountLabel = moneyStringFormatter(currency)(totalAmount);
41
+ const isOneWay = isFirstSegment && isFirstSegment;
41
42
 
42
43
  return (
43
44
  <div style={{ padding: "16px 24px 24px" }}>
@@ -51,21 +52,27 @@ export const BaggageSelectionModalFooter: React.FC<
51
52
  </div>
52
53
 
53
54
  <div
54
- style={{
55
- marginTop: "16px",
56
- display: "grid",
57
- columnGap: "12px",
58
- gridTemplateColumns: "repeat(2, 1fr)",
59
- }}
55
+ style={
56
+ isOneWay
57
+ ? { marginTop: "16px", display: "grid" }
58
+ : {
59
+ marginTop: "16px",
60
+ display: "grid",
61
+ columnGap: "12px",
62
+ gridTemplateColumns: "repeat(2, 1fr)",
63
+ }
64
+ }
60
65
  >
61
- <Button
62
- size={48}
63
- variant="outlined"
64
- disabled={isFirstSegment}
65
- onClick={() => onPreviousSegmentButtonClicked()}
66
- >
67
- Back
68
- </Button>
66
+ {!isOneWay && (
67
+ <Button
68
+ size={48}
69
+ variant="outlined"
70
+ disabled={isFirstSegment}
71
+ onClick={() => onPreviousSegmentButtonClicked()}
72
+ >
73
+ Back
74
+ </Button>
75
+ )}
69
76
  <Button
70
77
  size={48}
71
78
  data-testid="confirm-selection-for-baggage"
@@ -1,6 +1,6 @@
1
1
  import { formatDateString } from "@lib/formatDate";
2
2
  import React from "react";
3
- import { OfferSliceSegment } from "../../types/Offer";
3
+ import { OfferSliceSegment } from "../../../types/Offer";
4
4
 
5
5
  export interface BaggageSelectionModalHeaderProps {
6
6
  segmentCount: number;
@@ -18,21 +18,26 @@ export const BaggageSelectionModalHeader: React.FC<
18
18
  setCurrentSegmentIndex,
19
19
  }) => (
20
20
  <div style={{ padding: "24px 24px 16px" }}>
21
- <div style={{ display: "flex", columnGap: "4px" }}>
22
- {Array(segmentCount)
23
- .fill(0)
24
- .map((_, index) =>
25
- index === currentSegmentIndex ? (
26
- <ActiveSegment key={`segment_${index}`} />
27
- ) : (
28
- <InactiveSegment
29
- key={`segment_${index}`}
30
- onClick={() => setCurrentSegmentIndex(index)}
31
- />
32
- )
33
- )}
34
- </div>
35
- <h2 className="h3--semibold" style={{ marginTop: "12px" }}>
21
+ {segmentCount > 1 && (
22
+ <div style={{ display: "flex", columnGap: "4px" }}>
23
+ {Array(segmentCount)
24
+ .fill(0)
25
+ .map((_, index) =>
26
+ index === currentSegmentIndex ? (
27
+ <ActiveSegment key={`segment_${index}`} />
28
+ ) : (
29
+ <InactiveSegment
30
+ key={`segment_${index}`}
31
+ onClick={() => setCurrentSegmentIndex(index)}
32
+ />
33
+ )
34
+ )}
35
+ </div>
36
+ )}
37
+ <h2
38
+ className="h3--semibold"
39
+ style={segmentCount > 1 ? { marginTop: "12px" } : {}}
40
+ >
36
41
  Flight to {currentSegment.destination.iata_code}
37
42
  <span
38
43
  className="p2--regular"
@@ -59,7 +64,8 @@ const InactiveSegment: React.FC<{
59
64
  height: "4px",
60
65
  padding: "0",
61
66
  borderRadius: "4px",
62
- backgroundColor: "rgba(var(--ACCENT), var(--ACCENT-LIGHT-200))",
67
+ backgroundColor:
68
+ "var(--TERTIARY, rgba(var(--ACCENT), var(--ACCENT-LIGHT-200)))",
63
69
  transition: "background-color 0.3s var(--TRANSITION-CUBIC-BEZIER)",
64
70
  ...style,
65
71
  }}
@@ -70,7 +76,7 @@ const ActiveSegment = () => (
70
76
  <InactiveSegment
71
77
  onClick={undefined}
72
78
  style={{
73
- backgroundColor: "rgb(var(--ACCENT))",
79
+ backgroundColor: "var(--SECONDARY, rgb(var(--ACCENT)))",
74
80
  }}
75
81
  />
76
82
  );