@duffel/components 2.7.20 → 3.0.0

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 (345) hide show
  1. package/.circleci/config.yml +67 -0
  2. package/.eslintrc.js +47 -0
  3. package/.github/renovate.json +16 -0
  4. package/.github/workflows/autoapprove.yml +18 -0
  5. package/.github/workflows/release.yml +89 -0
  6. package/.husky/post-commit +4 -0
  7. package/.husky/pre-commit +4 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierignore +2 -0
  10. package/.storybook/Storyshots.test.js +3 -0
  11. package/.storybook/__snapshots__/Storyshots.test.js.snap +48318 -0
  12. package/.storybook/main.ts +33 -0
  13. package/.storybook/preview.tsx +28 -0
  14. package/.tool-versions +1 -0
  15. package/CONTRIBUTING.md +83 -0
  16. package/README.md +53 -15
  17. package/__mocks__/styleMock.js +6 -0
  18. package/babel.config.js +20 -0
  19. package/commitlint.config.js +4 -0
  20. package/config/esbuild.base.config.js +18 -0
  21. package/config/esbuild.cdn.config.js +51 -0
  22. package/config/esbuild.dev.config.js +46 -0
  23. package/config/esbuild.react.config.js +42 -0
  24. package/jest.config.ts +14 -0
  25. package/package.json +135 -193
  26. package/react-dist/components/DuffelAncillaries/Card.d.ts +14 -0
  27. package/react-dist/components/DuffelAncillaries/Counter.d.ts +10 -0
  28. package/react-dist/components/DuffelAncillaries/DuffelAncillaries.d.ts +3 -0
  29. package/react-dist/components/DuffelAncillaries/DuffelAncillariesCustomElement.d.ts +13 -0
  30. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionCard.d.ts +11 -0
  31. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionController.d.ts +13 -0
  32. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModal.d.ts +11 -0
  33. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalBody.d.ts +11 -0
  34. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalBodyPassenger.d.ts +13 -0
  35. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalFooter.d.ts +14 -0
  36. package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalHeader.d.ts +9 -0
  37. package/react-dist/components/DuffelAncillaries/bags/IncludedBaggageBanner.d.ts +7 -0
  38. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionCard.d.ts +10 -0
  39. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModal.d.ts +11 -0
  40. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBody.d.ts +7 -0
  41. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBodyListItem.d.ts +4 -0
  42. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalFooter.d.ts +11 -0
  43. package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalHeader.d.ts +2 -0
  44. package/react-dist/components/DuffelAncillaries/seats/Amenity.d.ts +6 -0
  45. package/react-dist/components/DuffelAncillaries/seats/DeckSelect.d.ts +15 -0
  46. package/react-dist/components/DuffelAncillaries/seats/Element.d.ts +15 -0
  47. package/react-dist/components/DuffelAncillaries/seats/EmptyElement.d.ts +2 -0
  48. package/react-dist/components/DuffelAncillaries/seats/ExitElement.d.ts +6 -0
  49. package/react-dist/components/DuffelAncillaries/seats/Legend.d.ts +12 -0
  50. package/react-dist/components/DuffelAncillaries/seats/Row.d.ts +13 -0
  51. package/react-dist/components/DuffelAncillaries/seats/RowSection.d.ts +17 -0
  52. package/react-dist/components/DuffelAncillaries/seats/SeatElement.d.ts +13 -0
  53. package/react-dist/components/DuffelAncillaries/seats/SeatInfo.d.ts +7 -0
  54. package/react-dist/components/DuffelAncillaries/seats/SeatMap.d.ts +12 -0
  55. package/react-dist/components/DuffelAncillaries/seats/SeatMapUnavailable.d.ts +2 -0
  56. package/react-dist/components/DuffelAncillaries/seats/SeatSelectionCard.d.ts +13 -0
  57. package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModal.d.ts +13 -0
  58. package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalBody.d.ts +4 -0
  59. package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalFooter.d.ts +16 -0
  60. package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalHeader.d.ts +10 -0
  61. package/react-dist/components/DuffelAncillaries/seats/SeatUnavailable.d.ts +5 -0
  62. package/react-dist/components/DuffelPayments/DuffelPayments.d.ts +11 -0
  63. package/react-dist/components/DuffelPayments/DuffelPaymentsCustomElement.d.ts +14 -0
  64. package/react-dist/components/shared/AnimatedLoaderEllipsis.d.ts +2 -0
  65. package/react-dist/components/shared/Button.d.ts +23 -0
  66. package/react-dist/components/shared/ErrorBoundary.d.ts +13 -0
  67. package/react-dist/components/shared/FetchOfferErrorState.d.ts +5 -0
  68. package/react-dist/components/shared/Icon.d.ts +44 -0
  69. package/react-dist/components/shared/IconButton.d.ts +16 -0
  70. package/react-dist/components/shared/Modal.d.ts +11 -0
  71. package/react-dist/components/shared/NonIdealState.d.ts +4 -0
  72. package/react-dist/components/shared/Stamp.d.ts +7 -0
  73. package/react-dist/components/shared/Tabs.d.ts +16 -0
  74. package/react-dist/custom-elements.d.ts +6 -0
  75. package/react-dist/custom-elements.js +37 -0
  76. package/react-dist/custom-elements.js.map +7 -0
  77. package/react-dist/index.d.ts +7 -0
  78. package/react-dist/index.js +37 -0
  79. package/react-dist/index.js.map +7 -0
  80. package/react-dist/lib/captureErrorInSentry.d.ts +1 -0
  81. package/react-dist/lib/compileCreateOrderPayload.d.ts +14 -0
  82. package/react-dist/lib/createPriceFormatters.d.ts +12 -0
  83. package/react-dist/lib/fetchFromDuffelAPI.d.ts +8 -0
  84. package/react-dist/lib/fetchFromFixtures.d.ts +4 -0
  85. package/react-dist/lib/formatAvailableServices.d.ts +12 -0
  86. package/react-dist/lib/formatDate.d.ts +2 -0
  87. package/react-dist/lib/formatSeatMaps.d.ts +4 -0
  88. package/react-dist/lib/getBaggageServiceDescription.d.ts +2 -0
  89. package/react-dist/lib/getCabinsForSegmentAndDeck.d.ts +2 -0
  90. package/react-dist/lib/getCurrencyForSeatMaps.d.ts +10 -0
  91. package/react-dist/lib/getCurrencyForServices.d.ts +11 -0
  92. package/react-dist/lib/getFirstSeatElementMatchingCriteria.d.ts +3 -0
  93. package/react-dist/lib/getPassengerBySegmentList.d.ts +6 -0
  94. package/react-dist/lib/getPassengerInitials.d.ts +1 -0
  95. package/react-dist/lib/getPassengerMapById.d.ts +3 -0
  96. package/react-dist/lib/getPassengerName.d.ts +3 -0
  97. package/react-dist/lib/getRowNumber.d.ts +2 -0
  98. package/react-dist/lib/getSegmentList.d.ts +2 -0
  99. package/react-dist/lib/getServicePriceMapById.d.ts +3 -0
  100. package/react-dist/lib/getSymbols.d.ts +2 -0
  101. package/react-dist/lib/getTotalAmountForServices.d.ts +6 -0
  102. package/react-dist/lib/getTotalQuantity.d.ts +2 -0
  103. package/react-dist/lib/hasService.d.ts +2 -0
  104. package/react-dist/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.d.ts +3 -0
  105. package/react-dist/lib/hasWings.d.ts +2 -0
  106. package/react-dist/lib/isBaggageService.d.ts +2 -0
  107. package/react-dist/lib/isCancelForAnyReasonService.d.ts +2 -0
  108. package/react-dist/lib/isFixtureOfferId.d.ts +2 -0
  109. package/react-dist/lib/isPayloadComplete.d.ts +2 -0
  110. package/react-dist/lib/isSeatElement.d.ts +2 -0
  111. package/react-dist/lib/logging.d.ts +46 -0
  112. package/react-dist/lib/moneyStringFormatter.d.ts +8 -0
  113. package/react-dist/lib/offerIsExpired.d.ts +2 -0
  114. package/react-dist/lib/retrieveOffer.d.ts +2 -0
  115. package/react-dist/lib/retrieveOfferFromDuffelAPI.d.ts +1 -0
  116. package/react-dist/lib/retrieveSeatMaps.d.ts +2 -0
  117. package/react-dist/lib/retrieveSeatMapsFromDuffelAPI.d.ts +1 -0
  118. package/react-dist/lib/setBodyScrollability.d.ts +1 -0
  119. package/react-dist/lib/validateProps.d.ts +7 -0
  120. package/react-dist/lib/withPlural.d.ts +1 -0
  121. package/react-dist/types/Aircraft.d.ts +14 -0
  122. package/react-dist/types/Airline.d.ts +14 -0
  123. package/react-dist/types/Airport.d.ts +44 -0
  124. package/react-dist/types/City.d.ts +18 -0
  125. package/react-dist/types/CreateOrderPayload.d.ts +72 -0
  126. package/react-dist/types/CurrencyConversion.d.ts +10 -0
  127. package/react-dist/types/DuffelAncillariesProps.d.ts +70 -0
  128. package/react-dist/types/Offer.d.ts +711 -0
  129. package/react-dist/types/Order.d.ts +8 -0
  130. package/react-dist/types/Place.d.ts +8 -0
  131. package/react-dist/types/SeatMap.d.ts +190 -0
  132. package/react-dist/types/index.d.ts +11 -0
  133. package/scripts/generate-fixture.ts +200 -0
  134. package/scripts/upload-to-cdn.sh +34 -0
  135. package/scripts.tsconfig.json +11 -0
  136. package/src/components/DuffelAncillaries/Card.tsx +126 -0
  137. package/src/components/DuffelAncillaries/Counter.tsx +40 -0
  138. package/src/components/DuffelAncillaries/DuffelAncillaries.tsx +344 -0
  139. package/src/components/DuffelAncillaries/DuffelAncillariesCustomElement.tsx +124 -0
  140. package/src/components/DuffelAncillaries/bags/BaggageSelectionCard.tsx +96 -0
  141. package/src/components/DuffelAncillaries/bags/BaggageSelectionController.tsx +88 -0
  142. package/src/components/DuffelAncillaries/bags/BaggageSelectionModal.tsx +81 -0
  143. package/src/components/DuffelAncillaries/bags/BaggageSelectionModalBody.tsx +60 -0
  144. package/src/components/DuffelAncillaries/bags/BaggageSelectionModalBodyPassenger.tsx +122 -0
  145. package/src/components/DuffelAncillaries/bags/BaggageSelectionModalFooter.tsx +81 -0
  146. package/src/components/DuffelAncillaries/bags/BaggageSelectionModalHeader.tsx +76 -0
  147. package/src/components/DuffelAncillaries/bags/IncludedBaggageBanner.tsx +51 -0
  148. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionCard.tsx +90 -0
  149. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModal.tsx +63 -0
  150. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBody.tsx +56 -0
  151. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBodyListItem.tsx +11 -0
  152. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalFooter.tsx +74 -0
  153. package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalHeader.tsx +9 -0
  154. package/src/components/DuffelAncillaries/seats/Amenity.tsx +21 -0
  155. package/src/components/DuffelAncillaries/seats/DeckSelect.tsx +27 -0
  156. package/src/components/DuffelAncillaries/seats/Element.tsx +52 -0
  157. package/src/components/DuffelAncillaries/seats/EmptyElement.tsx +5 -0
  158. package/src/components/DuffelAncillaries/seats/ExitElement.tsx +17 -0
  159. package/src/components/DuffelAncillaries/seats/Legend.tsx +60 -0
  160. package/src/components/DuffelAncillaries/seats/Row.tsx +47 -0
  161. package/src/components/DuffelAncillaries/seats/RowSection.tsx +78 -0
  162. package/src/components/DuffelAncillaries/seats/SeatElement.tsx +120 -0
  163. package/src/components/DuffelAncillaries/seats/SeatInfo.tsx +32 -0
  164. package/src/components/DuffelAncillaries/seats/SeatMap.tsx +81 -0
  165. package/src/components/DuffelAncillaries/seats/SeatMapUnavailable.tsx +21 -0
  166. package/src/components/DuffelAncillaries/seats/SeatSelectionCard.tsx +103 -0
  167. package/src/components/DuffelAncillaries/seats/SeatSelectionModal.tsx +142 -0
  168. package/src/components/DuffelAncillaries/seats/SeatSelectionModalBody.tsx +13 -0
  169. package/src/components/DuffelAncillaries/seats/SeatSelectionModalFooter.tsx +82 -0
  170. package/src/components/DuffelAncillaries/seats/SeatSelectionModalHeader.tsx +87 -0
  171. package/src/components/DuffelAncillaries/seats/SeatUnavailable.tsx +14 -0
  172. package/src/components/DuffelPayments/DuffelPayments.tsx +218 -0
  173. package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -0
  174. package/src/components/shared/AnimatedLoaderEllipsis.tsx +5 -0
  175. package/src/components/shared/Button.tsx +63 -0
  176. package/src/components/shared/ErrorBoundary.tsx +54 -0
  177. package/src/components/shared/FetchOfferErrorState.tsx +35 -0
  178. package/src/components/shared/Icon.tsx +152 -0
  179. package/src/components/shared/IconButton.tsx +42 -0
  180. package/src/components/shared/Modal.tsx +40 -0
  181. package/src/components/shared/NonIdealState.tsx +28 -0
  182. package/src/components/shared/Stamp.tsx +29 -0
  183. package/src/components/shared/Tabs.tsx +36 -0
  184. package/src/custom-elements.ts +13 -0
  185. package/src/examples/client-side/README.md +30 -0
  186. package/src/examples/client-side/index.html +57 -0
  187. package/src/examples/full-stack/README.md +34 -0
  188. package/src/examples/full-stack/index.html +48 -0
  189. package/src/examples/full-stack/server.mjs +158 -0
  190. package/src/examples/just-typescript/README.md +37 -0
  191. package/src/examples/just-typescript/package.json +16 -0
  192. package/src/examples/just-typescript/src/index.html +23 -0
  193. package/src/examples/just-typescript/src/index.ts +36 -0
  194. package/src/examples/just-typescript/yarn.lock +154 -0
  195. package/src/examples/payments-custom-element/README.md +17 -0
  196. package/src/examples/payments-custom-element/index.html +43 -0
  197. package/src/examples/payments-just-typescript/README.md +37 -0
  198. package/src/examples/payments-just-typescript/package.json +16 -0
  199. package/src/examples/payments-just-typescript/src/index.html +23 -0
  200. package/src/examples/payments-just-typescript/src/index.ts +18 -0
  201. package/src/examples/payments-just-typescript/yarn.lock +154 -0
  202. package/src/examples/react-app/README.md +37 -0
  203. package/src/examples/react-app/package.json +20 -0
  204. package/src/examples/react-app/src/index.html +19 -0
  205. package/src/examples/react-app/src/index.tsx +48 -0
  206. package/src/examples/react-app/yarn.lock +219 -0
  207. package/src/fixtures/offers/off_0000AUde3KwTztSRK1cznH.json +497 -0
  208. package/src/fixtures/offers/off_0000AVx4lUFFKW8PsPeQeQ.json +307 -0
  209. package/src/fixtures/offers/off_1.json +497 -0
  210. package/src/fixtures/passengers/mock_passengers.ts +26 -0
  211. package/src/fixtures/seat-maps/off_0000AUde3KwTztSRK1cznH.json +6852 -0
  212. package/src/fixtures/seat-maps/off_0000AVx4lUFFKW8PsPeQeQ.json +1 -0
  213. package/src/fixtures/seat-maps/off_1.json +6852 -0
  214. package/src/index.ts +7 -0
  215. package/src/lib/captureErrorInSentry.ts +42 -0
  216. package/src/lib/compileCreateOrderPayload.ts +63 -0
  217. package/src/lib/createPriceFormatters.ts +73 -0
  218. package/src/lib/fetchFromDuffelAPI.ts +54 -0
  219. package/src/lib/fetchFromFixtures.ts +18 -0
  220. package/src/lib/formatAvailableServices.ts +91 -0
  221. package/src/lib/formatDate.ts +20 -0
  222. package/src/lib/formatSeatMaps.ts +81 -0
  223. package/src/lib/getBaggageServiceDescription.ts +42 -0
  224. package/src/lib/getCabinsForSegmentAndDeck.ts +4 -0
  225. package/src/lib/getCurrencyForSeatMaps.ts +22 -0
  226. package/src/lib/getCurrencyForServices.ts +24 -0
  227. package/src/lib/getFirstSeatElementMatchingCriteria.ts +22 -0
  228. package/src/lib/getPassengerBySegmentList.ts +10 -0
  229. package/src/lib/getPassengerInitials.ts +6 -0
  230. package/src/lib/getPassengerMapById.ts +17 -0
  231. package/src/lib/getPassengerName.ts +37 -0
  232. package/src/lib/getRowNumber.ts +16 -0
  233. package/src/lib/getSegmentList.ts +7 -0
  234. package/src/lib/getServicePriceMapById.ts +20 -0
  235. package/src/lib/getSymbols.ts +22 -0
  236. package/src/lib/getTotalAmountForServices.ts +72 -0
  237. package/src/lib/getTotalQuantity.ts +5 -0
  238. package/src/lib/hasService.ts +24 -0
  239. package/src/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.ts +35 -0
  240. package/src/lib/hasWings.ts +8 -0
  241. package/src/lib/isBaggageService.ts +8 -0
  242. package/src/lib/isCancelForAnyReasonService.ts +9 -0
  243. package/src/lib/isFixtureOfferId.ts +4 -0
  244. package/src/lib/isPayloadComplete.ts +11 -0
  245. package/src/lib/isSeatElement.ts +10 -0
  246. package/src/lib/logging.ts +120 -0
  247. package/src/lib/moneyStringFormatter.ts +34 -0
  248. package/src/lib/offerIsExpired.ts +5 -0
  249. package/src/lib/retrieveOffer.ts +54 -0
  250. package/src/lib/retrieveOfferFromDuffelAPI.ts +13 -0
  251. package/src/lib/retrieveSeatMaps.ts +55 -0
  252. package/src/lib/retrieveSeatMapsFromDuffelAPI.ts +13 -0
  253. package/src/lib/setBodyScrollability.ts +7 -0
  254. package/src/lib/validateProps.ts +37 -0
  255. package/src/lib/withPlural.ts +8 -0
  256. package/src/stories/BaggageSelectionModalHeader.stories.tsx +21 -0
  257. package/src/stories/Button.stories.tsx +69 -0
  258. package/src/stories/DuffelAncillaries.stories.tsx +126 -0
  259. package/src/stories/DuffelPayments.stories.tsx +34 -0
  260. package/src/stories/Icon.stories.tsx +35 -0
  261. package/src/stories/IconButton.stories.tsx +25 -0
  262. package/src/styles/colors.css +22 -0
  263. package/src/styles/components/Amenity.css +23 -0
  264. package/src/styles/components/BaggageDisplay.css +25 -0
  265. package/src/styles/components/Button.css +161 -0
  266. package/src/styles/components/Card.css +52 -0
  267. package/src/styles/components/CfarSelectionModal.css +34 -0
  268. package/src/styles/components/Counter.css +18 -0
  269. package/src/styles/components/DuffelPayments.css +42 -0
  270. package/src/styles/components/IconButton.css +63 -0
  271. package/src/styles/components/Legend.css +58 -0
  272. package/src/styles/components/Loader.css +37 -0
  273. package/src/styles/components/LoadingState.css +81 -0
  274. package/src/styles/components/Modal.css +84 -0
  275. package/src/styles/components/PassengerSelect.css +93 -0
  276. package/src/styles/components/PassengersLayout.css +90 -0
  277. package/src/styles/components/Row.css +70 -0
  278. package/src/styles/components/Seat.css +57 -0
  279. package/src/styles/components/SeatInfo.css +61 -0
  280. package/src/styles/components/SeatMap.css +24 -0
  281. package/src/styles/components/SeatSelect.css +92 -0
  282. package/src/styles/components/Segment.css +17 -0
  283. package/src/styles/components/SelectionSegment.css +10 -0
  284. package/src/styles/components/Summary.css +70 -0
  285. package/src/styles/components/Tabs.css +49 -0
  286. package/src/styles/flex.css +5 -0
  287. package/src/styles/font-families.css +47 -0
  288. package/src/styles/global.css +51 -0
  289. package/src/styles/margin.css +3 -0
  290. package/src/styles/spacing.css +18 -0
  291. package/src/styles/transitions.css +3 -0
  292. package/src/styles/typography.css +13 -0
  293. package/src/tests/components/DuffelAncillaries.test.tsx +342 -0
  294. package/src/tests/lib/createPriceFormatters.test.tsx +152 -0
  295. package/src/tests/lib/formatAvailableServices.test.tsx +79 -0
  296. package/src/tests/lib/formatSeatMaps.test.tsx +49 -0
  297. package/src/tests/lib/getCurrencyForServices.test.tsx +44 -0
  298. package/src/tests/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.test.ts +86 -0
  299. package/src/tests/lib/logging.test.tsx +32 -0
  300. package/src/tests/lib/moneyStringFormatter.test.tsx +12 -0
  301. package/src/tests/lib/validateProps.test.tsx +57 -0
  302. package/src/types/Aircraft.ts +16 -0
  303. package/src/types/Airline.ts +16 -0
  304. package/src/types/Airport.ts +54 -0
  305. package/src/types/City.ts +21 -0
  306. package/src/types/CreateOrderPayload.ts +99 -0
  307. package/src/types/CurrencyConversion.ts +10 -0
  308. package/src/types/DuffelAncillariesProps.ts +108 -0
  309. package/src/types/Offer.ts +851 -0
  310. package/src/types/Order.ts +6 -0
  311. package/src/types/Place.ts +6 -0
  312. package/src/types/SeatMap.ts +231 -0
  313. package/src/types/index.ts +11 -0
  314. package/tsconfig.json +52 -0
  315. package/LICENSE +0 -21
  316. package/dist/AdditionalBaggage.esm.js +0 -1
  317. package/dist/AdditionalBaggage.js +0 -1
  318. package/dist/AdditionalBaggage.min.css +0 -408
  319. package/dist/AdditionalBaggage.umd.min.js +0 -2
  320. package/dist/AdditionalBaggage.umd.min.js.LICENSE.txt +0 -60
  321. package/dist/AdditionalBaggageSelection.esm.js +0 -1
  322. package/dist/AdditionalBaggageSelection.js +0 -1
  323. package/dist/AdditionalBaggageSelection.min.css +0 -744
  324. package/dist/AdditionalBaggageSelection.umd.min.js +0 -2
  325. package/dist/AdditionalBaggageSelection.umd.min.js.LICENSE.txt +0 -93
  326. package/dist/CardPayment.esm.js +0 -2
  327. package/dist/CardPayment.esm.js.LICENSE.txt +0 -6
  328. package/dist/CardPayment.js +0 -2
  329. package/dist/CardPayment.js.LICENSE.txt +0 -6
  330. package/dist/CardPayment.min.css +0 -233
  331. package/dist/CardPayment.umd.min.js +0 -2
  332. package/dist/CardPayment.umd.min.js.LICENSE.txt +0 -61
  333. package/dist/SeatSelection.esm.js +0 -1
  334. package/dist/SeatSelection.js +0 -1
  335. package/dist/SeatSelection.min.css +0 -1127
  336. package/dist/SeatSelection.umd.min.js +0 -2
  337. package/dist/SeatSelection.umd.min.js.LICENSE.txt +0 -60
  338. package/dist/duffel-components.d.ts +0 -1614
  339. package/dist/duffel-components.esm.js +0 -2
  340. package/dist/duffel-components.esm.js.LICENSE.txt +0 -6
  341. package/dist/duffel-components.js +0 -2
  342. package/dist/duffel-components.js.LICENSE.txt +0 -6
  343. package/dist/duffel-components.min.css +0 -1280
  344. package/dist/duffel-components.umd.min.js +0 -2
  345. package/dist/duffel-components.umd.min.js.LICENSE.txt +0 -102
@@ -0,0 +1,344 @@
1
+ import { ErrorBoundary } from "@components/shared/ErrorBoundary";
2
+ import { FetchOfferErrorState } from "@components/shared/FetchOfferErrorState";
3
+ import { compileCreateOrderPayload } from "@lib/compileCreateOrderPayload";
4
+ import { createPriceFormatters } from "@lib/createPriceFormatters";
5
+ import { formatAvailableServices } from "@lib/formatAvailableServices";
6
+ import { formatSeatMaps } from "@lib/formatSeatMaps";
7
+ import { isPayloadComplete } from "@lib/isPayloadComplete";
8
+ import { initializeLogger, logGroup } from "@lib/logging";
9
+ import { offerIsExpired } from "@lib/offerIsExpired";
10
+ import { retrieveOffer } from "@lib/retrieveOffer";
11
+ import { retrieveSeatMaps } from "@lib/retrieveSeatMaps";
12
+ import {
13
+ areDuffelAncillariesPropsValid,
14
+ isDuffelAncillariesPropsWithClientKeyAndOfferId,
15
+ isDuffelAncillariesPropsWithOfferAndClientKey,
16
+ isDuffelAncillariesPropsWithOfferAndSeatMaps,
17
+ isDuffelAncillariesPropsWithOfferIdForFixture,
18
+ } from "@lib/validateProps";
19
+ import * as Sentry from "@sentry/browser";
20
+ import * as React from "react";
21
+ import {
22
+ CreateOrderPayloadPassengers,
23
+ CreateOrderPayloadService,
24
+ } from "../../types/CreateOrderPayload";
25
+ import { DuffelAncillariesProps } from "../../types/DuffelAncillariesProps";
26
+ import { Offer } from "../../types/Offer";
27
+ import { SeatMap } from "../../types/SeatMap";
28
+ import { BaggageSelectionCard } from "./bags/BaggageSelectionCard";
29
+ import { CfarSelectionCard } from "./cancel_for_any_reason/CfarSelectionCard";
30
+ import { SeatSelectionCard } from "./seats/SeatSelectionCard";
31
+
32
+ const COMPONENT_CDN = process.env.COMPONENT_CDN || "";
33
+ const hrefToComponentStyles = `${COMPONENT_CDN}/global.css`;
34
+
35
+ export const DuffelAncillaries: React.FC<DuffelAncillariesProps> = (props) => {
36
+ initializeLogger(props.debug || false);
37
+
38
+ logGroup("Properties passed into the component:", props);
39
+
40
+ if (!areDuffelAncillariesPropsValid(props)) {
41
+ throw new Error(
42
+ `The props (${Object.keys(
43
+ props
44
+ )}) passed to DuffelAncillaries are invalid. ` +
45
+ "`onPayloadReady`, `passengers` and `services` are always required. " +
46
+ "Then, depending on your use case you may have one of the following combinations of required props: " +
47
+ "`offer_id` and `client_key`, `offer` and `seat_maps` or `offer` and `client_key`." +
48
+ "Please refer to the documentation for more information and working examples: " +
49
+ "https://duffel.com/docs/guides/ancillaries-component"
50
+ );
51
+ }
52
+ if (props.services.length === 0) {
53
+ throw new Error(
54
+ `You must provide at least one service in the "services" prop. Valid services: ["bags", "seats", "cancel_for_any_reason"]`
55
+ );
56
+ }
57
+
58
+ const isPropsWithOfferIdForFixture =
59
+ isDuffelAncillariesPropsWithOfferIdForFixture(props);
60
+
61
+ const isPropsWithClientKeyAndOfferId =
62
+ isDuffelAncillariesPropsWithClientKeyAndOfferId(props);
63
+
64
+ const isPropsWithOfferAndSeatMaps =
65
+ isDuffelAncillariesPropsWithOfferAndSeatMaps(props);
66
+
67
+ const isPropsWithOfferAndClientKey =
68
+ isDuffelAncillariesPropsWithOfferAndClientKey(props);
69
+
70
+ const shouldRetrieveSeatMaps =
71
+ props.services.includes("seats") &&
72
+ !("seat_maps" in props) &&
73
+ (isPropsWithOfferIdForFixture ||
74
+ isPropsWithClientKeyAndOfferId ||
75
+ isPropsWithOfferAndClientKey);
76
+
77
+ const [passengers, setPassengers] =
78
+ React.useState<CreateOrderPayloadPassengers>(props.passengers);
79
+
80
+ const [offer, setOffer] = React.useState<Offer | undefined>(
81
+ (props as any).offer
82
+ );
83
+
84
+ const [isOfferLoading, setIsOfferLoading] = React.useState(
85
+ isPropsWithClientKeyAndOfferId
86
+ );
87
+
88
+ const [seatMaps, setSeatMaps] = React.useState<SeatMap[] | undefined>(
89
+ isPropsWithOfferAndSeatMaps ? props.seat_maps : undefined
90
+ );
91
+ const [isSeatMapLoading, setIsSeatMapLoading] = React.useState(
92
+ shouldRetrieveSeatMaps
93
+ );
94
+
95
+ const [error, setError] = React.useState<null | string>(null);
96
+
97
+ const [baggageSelectedServices, setBaggageSelectedServices] = React.useState(
98
+ new Array<CreateOrderPayloadService>()
99
+ );
100
+ const [seatSelectedServices, setSeatSelectedServices] = React.useState(
101
+ new Array<CreateOrderPayloadService>()
102
+ );
103
+ const [cfarSelectedServices, setCfarSelectedServices] = React.useState(
104
+ new Array<CreateOrderPayloadService>()
105
+ );
106
+
107
+ const priceFormatters = createPriceFormatters(
108
+ props.markup,
109
+ props.priceFormatters
110
+ );
111
+
112
+ const updateOffer = (offer: Offer) => {
113
+ const expiryErrorMessage = "This offer has expired.";
114
+ if (offerIsExpired(offer)) {
115
+ setError(expiryErrorMessage);
116
+ return;
117
+ } else {
118
+ const msUntilExpiry = new Date(offer.expires_at).getTime() - Date.now();
119
+
120
+ // Only show the expiry error message if the offer expires in less than a day,
121
+ // to prevent buffer overflows when showing offers for fixtures, which expire in
122
+ // years.
123
+ const milisecondsInOneDay = 1000 * 60 * 60 * 24;
124
+ if (msUntilExpiry < milisecondsInOneDay) {
125
+ setTimeout(() => setError(expiryErrorMessage), msUntilExpiry);
126
+ }
127
+ }
128
+
129
+ const offerWithFormattedServices = formatAvailableServices(
130
+ offer,
131
+ priceFormatters
132
+ );
133
+ setOffer(offerWithFormattedServices);
134
+ };
135
+
136
+ const updateSeatMaps = (seatMaps: SeatMap[]) => {
137
+ const formattedSeatMaps = formatSeatMaps(seatMaps, priceFormatters.seats);
138
+ setSeatMaps(formattedSeatMaps);
139
+ };
140
+
141
+ React.useEffect(() => {
142
+ // whenever the props change, we'll set the sentry context to thse values
143
+ // so that we can see them in the sentry logs and better support the users of the component library
144
+ Sentry.setContext("props", {
145
+ "props.services": props.services,
146
+ "props.passengers.length": (props as any).passengers.length,
147
+ "props.offer_id": (props as any).offer_id,
148
+ "props.client_key": (props as any).client_key,
149
+ "props.offer?.id": (props as any).offer?.id,
150
+ "props.seat_maps?.[0]?.id": (props as any).seat_maps?.[0]?.id,
151
+ });
152
+
153
+ if (isPropsWithClientKeyAndOfferId || isPropsWithOfferIdForFixture) {
154
+ retrieveOffer(
155
+ props.offer_id,
156
+ !isPropsWithOfferIdForFixture ? props.client_key : null,
157
+ setError,
158
+ setIsOfferLoading,
159
+ (offer) => {
160
+ updateOffer(offer);
161
+
162
+ if (offer.passengers.length !== passengers.length) {
163
+ throw new Error(
164
+ `The number of passengers given to \`duffel-ancillaries\` (${props.passengers.length}) doesn't match ` +
165
+ `the number of passengers on the given offer (${offer.passengers.length}).`
166
+ );
167
+ }
168
+
169
+ if (isPropsWithOfferIdForFixture) {
170
+ // There's no way the component users will know the passenger IDs for the fixture offer
171
+ // so we'll need to add them here
172
+ setPassengers(
173
+ props.passengers.map((passenger, index) => ({
174
+ ...passenger,
175
+ id: offer.passengers[index].id,
176
+ }))
177
+ );
178
+ }
179
+ }
180
+ );
181
+ }
182
+
183
+ if (shouldRetrieveSeatMaps) {
184
+ retrieveSeatMaps(
185
+ isPropsWithClientKeyAndOfferId || isPropsWithOfferIdForFixture
186
+ ? props.offer_id
187
+ : props.offer.id,
188
+ !isPropsWithOfferIdForFixture ? props.client_key : null,
189
+ () => updateSeatMaps([]),
190
+ setIsSeatMapLoading,
191
+ updateSeatMaps
192
+ );
193
+ }
194
+
195
+ if (isPropsWithOfferAndClientKey) {
196
+ updateOffer(props.offer);
197
+ }
198
+
199
+ if (isPropsWithOfferAndSeatMaps) {
200
+ updateOffer(props.offer);
201
+ updateSeatMaps(props.seat_maps);
202
+ }
203
+ }, [
204
+ // `as any` is needed here because the list
205
+ // of dependencies is different for each combination of props.
206
+ // To satisfy typescript, we'd need to conditionally assign
207
+ // the dependencies to the hook after checking its type,
208
+ // however that is not possible in a react hook.
209
+ (props as any).offer_id,
210
+ (props as any).client_key,
211
+ (props as any).offer?.id,
212
+ (props as any).seat_maps?.[0]?.id,
213
+ ]);
214
+
215
+ React.useEffect(() => {
216
+ if (!offer) return;
217
+
218
+ const createOrderPayload = compileCreateOrderPayload({
219
+ baggageSelectedServices,
220
+ seatSelectedServices,
221
+ cfarSelectedServices,
222
+ offer,
223
+ passengers,
224
+ seatMaps,
225
+ });
226
+
227
+ if (isPayloadComplete(createOrderPayload)) {
228
+ const metadata = {
229
+ offer_total_amount: offer.total_amount,
230
+ offer_total_currency: offer.total_currency,
231
+ offer_tax_amount: offer.tax_amount,
232
+ offer_tax_currency: offer.tax_currency,
233
+ baggage_services: baggageSelectedServices,
234
+ seat_services: seatSelectedServices,
235
+ cancel_for_any_reason_services: cfarSelectedServices,
236
+ };
237
+
238
+ logGroup("Payload ready", {
239
+ "Order creation payload": createOrderPayload,
240
+ "Services metadata": metadata,
241
+ });
242
+
243
+ props.onPayloadReady(createOrderPayload, metadata);
244
+ }
245
+ }, [baggageSelectedServices, seatSelectedServices, cfarSelectedServices]);
246
+
247
+ if (!areDuffelAncillariesPropsValid(props)) {
248
+ return null;
249
+ }
250
+
251
+ const nonIdealStateHeight = `${
252
+ // 72 (card height) + 32 gap between cards
253
+ 72 * props.services.length + 32 * (props.services.length - 1)
254
+ }px`;
255
+
256
+ const duffelComponentsStyle = {
257
+ // Adding inline styles here to avoid the cards jumping down
258
+ // before the css is loaded duet to the missing "row gap".
259
+ display: "flex",
260
+ width: "100%",
261
+ flexDirection: "column",
262
+ rowGap: "12px",
263
+ ...(props.styles?.accentColor && {
264
+ "--ACCENT": props.styles.accentColor,
265
+ }),
266
+ ...(props.styles?.fontFamily && {
267
+ "--FONT-FAMILY": props.styles.fontFamily,
268
+ }),
269
+ ...(props.styles?.buttonCornerRadius && {
270
+ "--BUTTON-RADIUS": props.styles.buttonCornerRadius,
271
+ }),
272
+ // `as any` is needed here is needed because we want to set css variables
273
+ // that are not part of the css properties type
274
+ } as any;
275
+
276
+ const state = {
277
+ isOfferLoading,
278
+ isSeatMapLoading,
279
+ baggageSelectedServices,
280
+ seatSelectedServices,
281
+ cfarSelectedServices,
282
+ offer,
283
+ seatMaps,
284
+ error,
285
+ };
286
+
287
+ logGroup("Component's internal state:", state);
288
+
289
+ return (
290
+ <>
291
+ <link rel="stylesheet" href={hrefToComponentStyles}></link>
292
+
293
+ <div className="duffel-components" style={duffelComponentsStyle}>
294
+ <ErrorBoundary>
295
+ {error && (
296
+ <FetchOfferErrorState
297
+ height={nonIdealStateHeight}
298
+ message={error}
299
+ />
300
+ )}
301
+
302
+ {!error &&
303
+ props.services.map((ancillaryName) => {
304
+ if (ancillaryName === "bags")
305
+ return (
306
+ <BaggageSelectionCard
307
+ key="bags"
308
+ isLoading={isOfferLoading}
309
+ offer={offer}
310
+ passengers={passengers}
311
+ selectedServices={baggageSelectedServices}
312
+ setSelectedServices={setBaggageSelectedServices}
313
+ />
314
+ );
315
+
316
+ if (ancillaryName === "seats")
317
+ return (
318
+ <SeatSelectionCard
319
+ key="seats"
320
+ isLoading={isOfferLoading || isSeatMapLoading}
321
+ seatMaps={seatMaps}
322
+ offer={offer}
323
+ passengers={passengers}
324
+ selectedServices={seatSelectedServices}
325
+ setSelectedServices={setSeatSelectedServices}
326
+ />
327
+ );
328
+
329
+ if (ancillaryName === "cancel_for_any_reason")
330
+ return (
331
+ <CfarSelectionCard
332
+ key="cancel_for_any_reason"
333
+ isLoading={isOfferLoading}
334
+ offer={offer}
335
+ selectedServices={cfarSelectedServices}
336
+ setSelectedServices={setCfarSelectedServices}
337
+ />
338
+ );
339
+ })}
340
+ </ErrorBoundary>
341
+ </div>
342
+ </>
343
+ );
344
+ };
@@ -0,0 +1,124 @@
1
+ import { createRoot, Root } from "react-dom/client";
2
+ import { CreateOrderPayload } from "../../types/CreateOrderPayload";
3
+ import {
4
+ DuffelAncillariesPropsWithClientKeyAndOfferId,
5
+ DuffelAncillariesPropsWithOfferIdForFixture,
6
+ DuffelAncillariesPropsWithOffersAndSeatMaps,
7
+ DuffelAncillariesPropWithOfferAndClientKey,
8
+ OnPayloadReady,
9
+ OnPayloadReadyMetadata,
10
+ } from "../../types/DuffelAncillariesProps";
11
+ import { DuffelAncillaries } from "./DuffelAncillaries";
12
+
13
+ declare global {
14
+ // eslint-disable-next-line @typescript-eslint/no-namespace
15
+ namespace JSX {
16
+ interface IntrinsicElements {
17
+ "duffel-ancillaries": React.DetailedHTMLProps<
18
+ React.HTMLAttributes<HTMLElement>,
19
+ HTMLElement
20
+ >;
21
+ }
22
+ }
23
+ }
24
+
25
+ const CUSTOM_ELEMENT_TAG = "duffel-ancillaries";
26
+
27
+ // A bit reptitive but typescript is not clever enough
28
+ // to infer the correct type if we just use
29
+ // `Omit<DuffelAncillariesProps, 'onPayloadReady'>`
30
+ type DuffelAncillariesCustomElementRenderArguments =
31
+ | Omit<DuffelAncillariesPropsWithOfferIdForFixture, "onPayloadReady">
32
+ | Omit<DuffelAncillariesPropsWithClientKeyAndOfferId, "onPayloadReady">
33
+ | Omit<DuffelAncillariesPropWithOfferAndClientKey, "onPayloadReady">
34
+ | Omit<DuffelAncillariesPropsWithOffersAndSeatMaps, "onPayloadReady">;
35
+
36
+ class DuffelAncillariesCustomElement extends HTMLElement {
37
+ /**
38
+ * The React root for displaying content inside a browser DOM element.
39
+ */
40
+ private root!: Root;
41
+
42
+ /**
43
+ * `connectedCallback` is called to initialise the custom element
44
+ */
45
+ connectedCallback() {
46
+ const container = document.createElement("div");
47
+ this.attachShadow({ mode: "open" }).appendChild(container);
48
+
49
+ this.root = createRoot(container);
50
+ }
51
+
52
+ /**
53
+ * When this function is called, it will render/re-render
54
+ * the `DuffelAncillaries` component with the given props.
55
+ */
56
+ public render(withProps: DuffelAncillariesCustomElementRenderArguments) {
57
+ if (!this.root) {
58
+ throw "It was not possible to render `duffel-ancillaries` because `this.root` is missing.";
59
+ }
60
+
61
+ this.root.render(
62
+ <DuffelAncillaries
63
+ {...withProps}
64
+ onPayloadReady={(data, metadata) => {
65
+ this.dispatchEvent(
66
+ new CustomEvent("onPayloadReady", {
67
+ detail: { data, metadata },
68
+ composed: true,
69
+ })
70
+ );
71
+ }}
72
+ />
73
+ );
74
+ }
75
+ }
76
+
77
+ window.customElements.get(CUSTOM_ELEMENT_TAG) ||
78
+ window.customElements.define(
79
+ CUSTOM_ELEMENT_TAG,
80
+ DuffelAncillariesCustomElement
81
+ );
82
+
83
+ function tryToGetDuffelAncillariesCustomElement(
84
+ caller: string
85
+ ): DuffelAncillariesCustomElement {
86
+ const element =
87
+ document.querySelector<DuffelAncillariesCustomElement>(CUSTOM_ELEMENT_TAG);
88
+ if (!element) {
89
+ throw new Error(
90
+ `Could not find duffel-ancillaries element in the DOM. Maybe you need to call ${caller} after 'window.onload'?`
91
+ );
92
+ }
93
+ return element;
94
+ }
95
+
96
+ export function renderDuffelAncillariesCustomElement(
97
+ props: DuffelAncillariesCustomElementRenderArguments
98
+ ) {
99
+ const element = tryToGetDuffelAncillariesCustomElement(
100
+ "renderDuffelAncillariesCustomElement"
101
+ );
102
+ element.render(props);
103
+ }
104
+
105
+ type OnPayloadReadyCustomEvent = CustomEvent<{
106
+ data: CreateOrderPayload;
107
+ metadata: OnPayloadReadyMetadata;
108
+ }>;
109
+
110
+ export function onDuffelAncillariesPayloadReady(
111
+ onPayloadReady: OnPayloadReady
112
+ ) {
113
+ const element = tryToGetDuffelAncillariesCustomElement(
114
+ "onDuffelAncillariesPayloadReady"
115
+ );
116
+ const eventListener = (event: OnPayloadReadyCustomEvent) => {
117
+ onPayloadReady(event.detail.data, event.detail.metadata);
118
+ };
119
+
120
+ // using `as EventListener` here because typescript doesn't know the event type for `onPayloadReady`
121
+ // There's a few different suggestions to resolve this seemed good enough
122
+ // You can learn more here: https://github.com/microsoft/TypeScript/issues/28357
123
+ element.addEventListener("onPayloadReady", eventListener as EventListener);
124
+ }
@@ -0,0 +1,96 @@
1
+ import { AnimatedLoaderEllipsis } from "@components/shared/AnimatedLoaderEllipsis";
2
+ import { Stamp } from "@components/shared/Stamp";
3
+ import { getCurrencyForServices } from "@lib/getCurrencyForServices";
4
+ import { getTotalAmountForServices } from "@lib/getTotalAmountForServices";
5
+ import { getTotalQuantity } from "@lib/getTotalQuantity";
6
+ import { hasService } from "@lib/hasService";
7
+ import { moneyStringFormatter } from "@lib/moneyStringFormatter";
8
+ import { withPlural } from "@lib/withPlural";
9
+ import React from "react";
10
+ import {
11
+ CreateOrderPayload,
12
+ CreateOrderPayloadServices,
13
+ } from "../../../types/CreateOrderPayload";
14
+ import { Offer } from "../../../types/Offer";
15
+ import { Card } from "../Card";
16
+ import { BaggageSelectionModal } from "./BaggageSelectionModal";
17
+
18
+ export interface BaggageSelectionCardProps {
19
+ isLoading: boolean;
20
+ offer?: Offer;
21
+ passengers: CreateOrderPayload["passengers"];
22
+ selectedServices: CreateOrderPayloadServices;
23
+ setSelectedServices: (selectedServices: CreateOrderPayloadServices) => void;
24
+ }
25
+
26
+ export const BaggageSelectionCard: React.FC<BaggageSelectionCardProps> = ({
27
+ isLoading,
28
+ offer,
29
+ passengers,
30
+ selectedServices,
31
+ setSelectedServices,
32
+ }) => {
33
+ const [isOpen, setIsOpen] = React.useState(false);
34
+
35
+ const containsBaggageService = hasService(offer, "baggage");
36
+ const totalQuantity = getTotalQuantity(selectedServices);
37
+ const isBaggageAdded = totalQuantity > 0;
38
+
39
+ const totalAmount = getTotalAmountForServices(offer!, selectedServices);
40
+
41
+ let currencyToUse = offer?.base_currency || "";
42
+ if (containsBaggageService) {
43
+ currencyToUse = getCurrencyForServices(offer!, "baggage");
44
+ }
45
+
46
+ const totalAmountFormatted = offer
47
+ ? moneyStringFormatter(currencyToUse)(totalAmount)
48
+ : "0";
49
+
50
+ const copy =
51
+ containsBaggageService && isBaggageAdded
52
+ ? `${withPlural(
53
+ totalQuantity,
54
+ "bag",
55
+ "bags"
56
+ )} added for ${totalAmountFormatted}`
57
+ : "Add any extra baggage you need for your trip";
58
+
59
+ return (
60
+ <>
61
+ <Card
62
+ buttonTitle="Select extra baggage"
63
+ title="Extra baggage"
64
+ copy={copy}
65
+ icon="cabin_bag"
66
+ onClick={containsBaggageService ? () => setIsOpen(true) : null}
67
+ isLoading={isLoading}
68
+ disabled={!isLoading && !containsBaggageService}
69
+ isSelected={isBaggageAdded}
70
+ >
71
+ {isLoading && (
72
+ <Stamp color="var(--GREY-900)" backgroundColor="var(--GREY-100)">
73
+ Loading
74
+ <AnimatedLoaderEllipsis />
75
+ </Stamp>
76
+ )}
77
+ {!isLoading && !containsBaggageService && (
78
+ <Stamp color="var(--GREY-700)" backgroundColor="var(--GREY-200)">
79
+ Not available
80
+ </Stamp>
81
+ )}
82
+ </Card>
83
+
84
+ <BaggageSelectionModal
85
+ isOpen={Boolean(isOpen && offer)}
86
+ offer={offer}
87
+ passengers={passengers}
88
+ onClose={(newSelectedServices) => {
89
+ setSelectedServices(newSelectedServices);
90
+ setIsOpen(false);
91
+ }}
92
+ selectedServices={selectedServices}
93
+ />
94
+ </>
95
+ );
96
+ };
@@ -0,0 +1,88 @@
1
+ import { getBaggageServiceDescription } from "@lib/getBaggageServiceDescription";
2
+ import { hasServiceOfSameMetadataTypeAlreadyBeenSelected } from "@lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected";
3
+ import { moneyStringFormatter } from "@lib/moneyStringFormatter";
4
+ import React from "react";
5
+ import { CreateOrderPayloadServices } from "../../../types/CreateOrderPayload";
6
+ import { OfferAvailableServiceBaggage } from "../../../types/Offer";
7
+ import { Counter } from "../Counter";
8
+
9
+ interface BaggageSelectionControllerProps {
10
+ segmentId: string;
11
+ passengerId: string;
12
+ availableService: OfferAvailableServiceBaggage;
13
+ selectedServices: CreateOrderPayloadServices;
14
+ quantity: number;
15
+ onQuantityChanged: (quantity: number) => void;
16
+ }
17
+
18
+ export const BaggageSelectionController: React.FC<
19
+ BaggageSelectionControllerProps
20
+ > = ({
21
+ segmentId,
22
+ passengerId,
23
+ availableService,
24
+ quantity,
25
+ onQuantityChanged,
26
+ selectedServices,
27
+ }) => {
28
+ const serviceName =
29
+ availableService.metadata.type === "carry_on" ? "Cabin bag" : "Checked bag";
30
+
31
+ const servicePrice = moneyStringFormatter(availableService.total_currency)(
32
+ +availableService.total_amount
33
+ );
34
+ const serviceDescription = getBaggageServiceDescription(
35
+ availableService.metadata
36
+ );
37
+
38
+ const shouldDisableController =
39
+ hasServiceOfSameMetadataTypeAlreadyBeenSelected(
40
+ selectedServices,
41
+ segmentId,
42
+ passengerId,
43
+ availableService
44
+ );
45
+
46
+ return (
47
+ <div
48
+ style={{
49
+ display: "flex",
50
+ justifyContent: "space-between",
51
+ alignItems: "center",
52
+ }}
53
+ >
54
+ <div>
55
+ <p style={{ margin: 0 }} className="p2--regular">
56
+ {serviceName}
57
+ <span
58
+ style={{
59
+ marginInline: "4px",
60
+ color: "var(--GREY-400)",
61
+ }}
62
+ >
63
+
64
+ </span>
65
+ <span
66
+ className="p2--semibold"
67
+ data-testid={`price-label--${availableService.id}--${passengerId}`}
68
+ >
69
+ {servicePrice}
70
+ </span>
71
+ </p>
72
+ <p
73
+ style={{ margin: 0, color: "var(--GREY-600)" }}
74
+ className="p3--regular"
75
+ >
76
+ {serviceDescription}
77
+ </p>
78
+ </div>
79
+ <Counter
80
+ id={`counter--${availableService.id}--${passengerId}`}
81
+ min={0}
82
+ value={quantity}
83
+ onChange={onQuantityChanged}
84
+ max={shouldDisableController ? 0 : availableService.maximum_quantity}
85
+ />
86
+ </div>
87
+ );
88
+ };