@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.
- package/.circleci/config.yml +67 -0
- package/.eslintrc.js +47 -0
- package/.github/renovate.json +16 -0
- package/.github/workflows/autoapprove.yml +18 -0
- package/.github/workflows/release.yml +89 -0
- package/.husky/post-commit +4 -0
- package/.husky/pre-commit +4 -0
- package/.nvmrc +1 -0
- package/.prettierignore +2 -0
- package/.storybook/Storyshots.test.js +3 -0
- package/.storybook/__snapshots__/Storyshots.test.js.snap +48318 -0
- package/.storybook/main.ts +33 -0
- package/.storybook/preview.tsx +28 -0
- package/.tool-versions +1 -0
- package/CONTRIBUTING.md +83 -0
- package/README.md +53 -15
- package/__mocks__/styleMock.js +6 -0
- package/babel.config.js +20 -0
- package/commitlint.config.js +4 -0
- package/config/esbuild.base.config.js +18 -0
- package/config/esbuild.cdn.config.js +51 -0
- package/config/esbuild.dev.config.js +46 -0
- package/config/esbuild.react.config.js +42 -0
- package/jest.config.ts +14 -0
- package/package.json +135 -193
- package/react-dist/components/DuffelAncillaries/Card.d.ts +14 -0
- package/react-dist/components/DuffelAncillaries/Counter.d.ts +10 -0
- package/react-dist/components/DuffelAncillaries/DuffelAncillaries.d.ts +3 -0
- package/react-dist/components/DuffelAncillaries/DuffelAncillariesCustomElement.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionCard.d.ts +11 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionController.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModal.d.ts +11 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalBody.d.ts +11 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalBodyPassenger.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalFooter.d.ts +14 -0
- package/react-dist/components/DuffelAncillaries/bags/BaggageSelectionModalHeader.d.ts +9 -0
- package/react-dist/components/DuffelAncillaries/bags/IncludedBaggageBanner.d.ts +7 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionCard.d.ts +10 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModal.d.ts +11 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBody.d.ts +7 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBodyListItem.d.ts +4 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalFooter.d.ts +11 -0
- package/react-dist/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalHeader.d.ts +2 -0
- package/react-dist/components/DuffelAncillaries/seats/Amenity.d.ts +6 -0
- package/react-dist/components/DuffelAncillaries/seats/DeckSelect.d.ts +15 -0
- package/react-dist/components/DuffelAncillaries/seats/Element.d.ts +15 -0
- package/react-dist/components/DuffelAncillaries/seats/EmptyElement.d.ts +2 -0
- package/react-dist/components/DuffelAncillaries/seats/ExitElement.d.ts +6 -0
- package/react-dist/components/DuffelAncillaries/seats/Legend.d.ts +12 -0
- package/react-dist/components/DuffelAncillaries/seats/Row.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/seats/RowSection.d.ts +17 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatElement.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatInfo.d.ts +7 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatMap.d.ts +12 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatMapUnavailable.d.ts +2 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatSelectionCard.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModal.d.ts +13 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalBody.d.ts +4 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalFooter.d.ts +16 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatSelectionModalHeader.d.ts +10 -0
- package/react-dist/components/DuffelAncillaries/seats/SeatUnavailable.d.ts +5 -0
- package/react-dist/components/DuffelPayments/DuffelPayments.d.ts +11 -0
- package/react-dist/components/DuffelPayments/DuffelPaymentsCustomElement.d.ts +14 -0
- package/react-dist/components/shared/AnimatedLoaderEllipsis.d.ts +2 -0
- package/react-dist/components/shared/Button.d.ts +23 -0
- package/react-dist/components/shared/ErrorBoundary.d.ts +13 -0
- package/react-dist/components/shared/FetchOfferErrorState.d.ts +5 -0
- package/react-dist/components/shared/Icon.d.ts +44 -0
- package/react-dist/components/shared/IconButton.d.ts +16 -0
- package/react-dist/components/shared/Modal.d.ts +11 -0
- package/react-dist/components/shared/NonIdealState.d.ts +4 -0
- package/react-dist/components/shared/Stamp.d.ts +7 -0
- package/react-dist/components/shared/Tabs.d.ts +16 -0
- package/react-dist/custom-elements.d.ts +6 -0
- package/react-dist/custom-elements.js +37 -0
- package/react-dist/custom-elements.js.map +7 -0
- package/react-dist/index.d.ts +7 -0
- package/react-dist/index.js +37 -0
- package/react-dist/index.js.map +7 -0
- package/react-dist/lib/captureErrorInSentry.d.ts +1 -0
- package/react-dist/lib/compileCreateOrderPayload.d.ts +14 -0
- package/react-dist/lib/createPriceFormatters.d.ts +12 -0
- package/react-dist/lib/fetchFromDuffelAPI.d.ts +8 -0
- package/react-dist/lib/fetchFromFixtures.d.ts +4 -0
- package/react-dist/lib/formatAvailableServices.d.ts +12 -0
- package/react-dist/lib/formatDate.d.ts +2 -0
- package/react-dist/lib/formatSeatMaps.d.ts +4 -0
- package/react-dist/lib/getBaggageServiceDescription.d.ts +2 -0
- package/react-dist/lib/getCabinsForSegmentAndDeck.d.ts +2 -0
- package/react-dist/lib/getCurrencyForSeatMaps.d.ts +10 -0
- package/react-dist/lib/getCurrencyForServices.d.ts +11 -0
- package/react-dist/lib/getFirstSeatElementMatchingCriteria.d.ts +3 -0
- package/react-dist/lib/getPassengerBySegmentList.d.ts +6 -0
- package/react-dist/lib/getPassengerInitials.d.ts +1 -0
- package/react-dist/lib/getPassengerMapById.d.ts +3 -0
- package/react-dist/lib/getPassengerName.d.ts +3 -0
- package/react-dist/lib/getRowNumber.d.ts +2 -0
- package/react-dist/lib/getSegmentList.d.ts +2 -0
- package/react-dist/lib/getServicePriceMapById.d.ts +3 -0
- package/react-dist/lib/getSymbols.d.ts +2 -0
- package/react-dist/lib/getTotalAmountForServices.d.ts +6 -0
- package/react-dist/lib/getTotalQuantity.d.ts +2 -0
- package/react-dist/lib/hasService.d.ts +2 -0
- package/react-dist/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.d.ts +3 -0
- package/react-dist/lib/hasWings.d.ts +2 -0
- package/react-dist/lib/isBaggageService.d.ts +2 -0
- package/react-dist/lib/isCancelForAnyReasonService.d.ts +2 -0
- package/react-dist/lib/isFixtureOfferId.d.ts +2 -0
- package/react-dist/lib/isPayloadComplete.d.ts +2 -0
- package/react-dist/lib/isSeatElement.d.ts +2 -0
- package/react-dist/lib/logging.d.ts +46 -0
- package/react-dist/lib/moneyStringFormatter.d.ts +8 -0
- package/react-dist/lib/offerIsExpired.d.ts +2 -0
- package/react-dist/lib/retrieveOffer.d.ts +2 -0
- package/react-dist/lib/retrieveOfferFromDuffelAPI.d.ts +1 -0
- package/react-dist/lib/retrieveSeatMaps.d.ts +2 -0
- package/react-dist/lib/retrieveSeatMapsFromDuffelAPI.d.ts +1 -0
- package/react-dist/lib/setBodyScrollability.d.ts +1 -0
- package/react-dist/lib/validateProps.d.ts +7 -0
- package/react-dist/lib/withPlural.d.ts +1 -0
- package/react-dist/types/Aircraft.d.ts +14 -0
- package/react-dist/types/Airline.d.ts +14 -0
- package/react-dist/types/Airport.d.ts +44 -0
- package/react-dist/types/City.d.ts +18 -0
- package/react-dist/types/CreateOrderPayload.d.ts +72 -0
- package/react-dist/types/CurrencyConversion.d.ts +10 -0
- package/react-dist/types/DuffelAncillariesProps.d.ts +70 -0
- package/react-dist/types/Offer.d.ts +711 -0
- package/react-dist/types/Order.d.ts +8 -0
- package/react-dist/types/Place.d.ts +8 -0
- package/react-dist/types/SeatMap.d.ts +190 -0
- package/react-dist/types/index.d.ts +11 -0
- package/scripts/generate-fixture.ts +200 -0
- package/scripts/upload-to-cdn.sh +34 -0
- package/scripts.tsconfig.json +11 -0
- package/src/components/DuffelAncillaries/Card.tsx +126 -0
- package/src/components/DuffelAncillaries/Counter.tsx +40 -0
- package/src/components/DuffelAncillaries/DuffelAncillaries.tsx +344 -0
- package/src/components/DuffelAncillaries/DuffelAncillariesCustomElement.tsx +124 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionCard.tsx +96 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionController.tsx +88 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionModal.tsx +81 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionModalBody.tsx +60 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionModalBodyPassenger.tsx +122 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionModalFooter.tsx +81 -0
- package/src/components/DuffelAncillaries/bags/BaggageSelectionModalHeader.tsx +76 -0
- package/src/components/DuffelAncillaries/bags/IncludedBaggageBanner.tsx +51 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionCard.tsx +90 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModal.tsx +63 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBody.tsx +56 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalBodyListItem.tsx +11 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalFooter.tsx +74 -0
- package/src/components/DuffelAncillaries/cancel_for_any_reason/CfarSelectionModalHeader.tsx +9 -0
- package/src/components/DuffelAncillaries/seats/Amenity.tsx +21 -0
- package/src/components/DuffelAncillaries/seats/DeckSelect.tsx +27 -0
- package/src/components/DuffelAncillaries/seats/Element.tsx +52 -0
- package/src/components/DuffelAncillaries/seats/EmptyElement.tsx +5 -0
- package/src/components/DuffelAncillaries/seats/ExitElement.tsx +17 -0
- package/src/components/DuffelAncillaries/seats/Legend.tsx +60 -0
- package/src/components/DuffelAncillaries/seats/Row.tsx +47 -0
- package/src/components/DuffelAncillaries/seats/RowSection.tsx +78 -0
- package/src/components/DuffelAncillaries/seats/SeatElement.tsx +120 -0
- package/src/components/DuffelAncillaries/seats/SeatInfo.tsx +32 -0
- package/src/components/DuffelAncillaries/seats/SeatMap.tsx +81 -0
- package/src/components/DuffelAncillaries/seats/SeatMapUnavailable.tsx +21 -0
- package/src/components/DuffelAncillaries/seats/SeatSelectionCard.tsx +103 -0
- package/src/components/DuffelAncillaries/seats/SeatSelectionModal.tsx +142 -0
- package/src/components/DuffelAncillaries/seats/SeatSelectionModalBody.tsx +13 -0
- package/src/components/DuffelAncillaries/seats/SeatSelectionModalFooter.tsx +82 -0
- package/src/components/DuffelAncillaries/seats/SeatSelectionModalHeader.tsx +87 -0
- package/src/components/DuffelAncillaries/seats/SeatUnavailable.tsx +14 -0
- package/src/components/DuffelPayments/DuffelPayments.tsx +218 -0
- package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -0
- package/src/components/shared/AnimatedLoaderEllipsis.tsx +5 -0
- package/src/components/shared/Button.tsx +63 -0
- package/src/components/shared/ErrorBoundary.tsx +54 -0
- package/src/components/shared/FetchOfferErrorState.tsx +35 -0
- package/src/components/shared/Icon.tsx +152 -0
- package/src/components/shared/IconButton.tsx +42 -0
- package/src/components/shared/Modal.tsx +40 -0
- package/src/components/shared/NonIdealState.tsx +28 -0
- package/src/components/shared/Stamp.tsx +29 -0
- package/src/components/shared/Tabs.tsx +36 -0
- package/src/custom-elements.ts +13 -0
- package/src/examples/client-side/README.md +30 -0
- package/src/examples/client-side/index.html +57 -0
- package/src/examples/full-stack/README.md +34 -0
- package/src/examples/full-stack/index.html +48 -0
- package/src/examples/full-stack/server.mjs +158 -0
- package/src/examples/just-typescript/README.md +37 -0
- package/src/examples/just-typescript/package.json +16 -0
- package/src/examples/just-typescript/src/index.html +23 -0
- package/src/examples/just-typescript/src/index.ts +36 -0
- package/src/examples/just-typescript/yarn.lock +154 -0
- package/src/examples/payments-custom-element/README.md +17 -0
- package/src/examples/payments-custom-element/index.html +43 -0
- package/src/examples/payments-just-typescript/README.md +37 -0
- package/src/examples/payments-just-typescript/package.json +16 -0
- package/src/examples/payments-just-typescript/src/index.html +23 -0
- package/src/examples/payments-just-typescript/src/index.ts +18 -0
- package/src/examples/payments-just-typescript/yarn.lock +154 -0
- package/src/examples/react-app/README.md +37 -0
- package/src/examples/react-app/package.json +20 -0
- package/src/examples/react-app/src/index.html +19 -0
- package/src/examples/react-app/src/index.tsx +48 -0
- package/src/examples/react-app/yarn.lock +219 -0
- package/src/fixtures/offers/off_0000AUde3KwTztSRK1cznH.json +497 -0
- package/src/fixtures/offers/off_0000AVx4lUFFKW8PsPeQeQ.json +307 -0
- package/src/fixtures/offers/off_1.json +497 -0
- package/src/fixtures/passengers/mock_passengers.ts +26 -0
- package/src/fixtures/seat-maps/off_0000AUde3KwTztSRK1cznH.json +6852 -0
- package/src/fixtures/seat-maps/off_0000AVx4lUFFKW8PsPeQeQ.json +1 -0
- package/src/fixtures/seat-maps/off_1.json +6852 -0
- package/src/index.ts +7 -0
- package/src/lib/captureErrorInSentry.ts +42 -0
- package/src/lib/compileCreateOrderPayload.ts +63 -0
- package/src/lib/createPriceFormatters.ts +73 -0
- package/src/lib/fetchFromDuffelAPI.ts +54 -0
- package/src/lib/fetchFromFixtures.ts +18 -0
- package/src/lib/formatAvailableServices.ts +91 -0
- package/src/lib/formatDate.ts +20 -0
- package/src/lib/formatSeatMaps.ts +81 -0
- package/src/lib/getBaggageServiceDescription.ts +42 -0
- package/src/lib/getCabinsForSegmentAndDeck.ts +4 -0
- package/src/lib/getCurrencyForSeatMaps.ts +22 -0
- package/src/lib/getCurrencyForServices.ts +24 -0
- package/src/lib/getFirstSeatElementMatchingCriteria.ts +22 -0
- package/src/lib/getPassengerBySegmentList.ts +10 -0
- package/src/lib/getPassengerInitials.ts +6 -0
- package/src/lib/getPassengerMapById.ts +17 -0
- package/src/lib/getPassengerName.ts +37 -0
- package/src/lib/getRowNumber.ts +16 -0
- package/src/lib/getSegmentList.ts +7 -0
- package/src/lib/getServicePriceMapById.ts +20 -0
- package/src/lib/getSymbols.ts +22 -0
- package/src/lib/getTotalAmountForServices.ts +72 -0
- package/src/lib/getTotalQuantity.ts +5 -0
- package/src/lib/hasService.ts +24 -0
- package/src/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.ts +35 -0
- package/src/lib/hasWings.ts +8 -0
- package/src/lib/isBaggageService.ts +8 -0
- package/src/lib/isCancelForAnyReasonService.ts +9 -0
- package/src/lib/isFixtureOfferId.ts +4 -0
- package/src/lib/isPayloadComplete.ts +11 -0
- package/src/lib/isSeatElement.ts +10 -0
- package/src/lib/logging.ts +120 -0
- package/src/lib/moneyStringFormatter.ts +34 -0
- package/src/lib/offerIsExpired.ts +5 -0
- package/src/lib/retrieveOffer.ts +54 -0
- package/src/lib/retrieveOfferFromDuffelAPI.ts +13 -0
- package/src/lib/retrieveSeatMaps.ts +55 -0
- package/src/lib/retrieveSeatMapsFromDuffelAPI.ts +13 -0
- package/src/lib/setBodyScrollability.ts +7 -0
- package/src/lib/validateProps.ts +37 -0
- package/src/lib/withPlural.ts +8 -0
- package/src/stories/BaggageSelectionModalHeader.stories.tsx +21 -0
- package/src/stories/Button.stories.tsx +69 -0
- package/src/stories/DuffelAncillaries.stories.tsx +126 -0
- package/src/stories/DuffelPayments.stories.tsx +34 -0
- package/src/stories/Icon.stories.tsx +35 -0
- package/src/stories/IconButton.stories.tsx +25 -0
- package/src/styles/colors.css +22 -0
- package/src/styles/components/Amenity.css +23 -0
- package/src/styles/components/BaggageDisplay.css +25 -0
- package/src/styles/components/Button.css +161 -0
- package/src/styles/components/Card.css +52 -0
- package/src/styles/components/CfarSelectionModal.css +34 -0
- package/src/styles/components/Counter.css +18 -0
- package/src/styles/components/DuffelPayments.css +42 -0
- package/src/styles/components/IconButton.css +63 -0
- package/src/styles/components/Legend.css +58 -0
- package/src/styles/components/Loader.css +37 -0
- package/src/styles/components/LoadingState.css +81 -0
- package/src/styles/components/Modal.css +84 -0
- package/src/styles/components/PassengerSelect.css +93 -0
- package/src/styles/components/PassengersLayout.css +90 -0
- package/src/styles/components/Row.css +70 -0
- package/src/styles/components/Seat.css +57 -0
- package/src/styles/components/SeatInfo.css +61 -0
- package/src/styles/components/SeatMap.css +24 -0
- package/src/styles/components/SeatSelect.css +92 -0
- package/src/styles/components/Segment.css +17 -0
- package/src/styles/components/SelectionSegment.css +10 -0
- package/src/styles/components/Summary.css +70 -0
- package/src/styles/components/Tabs.css +49 -0
- package/src/styles/flex.css +5 -0
- package/src/styles/font-families.css +47 -0
- package/src/styles/global.css +51 -0
- package/src/styles/margin.css +3 -0
- package/src/styles/spacing.css +18 -0
- package/src/styles/transitions.css +3 -0
- package/src/styles/typography.css +13 -0
- package/src/tests/components/DuffelAncillaries.test.tsx +342 -0
- package/src/tests/lib/createPriceFormatters.test.tsx +152 -0
- package/src/tests/lib/formatAvailableServices.test.tsx +79 -0
- package/src/tests/lib/formatSeatMaps.test.tsx +49 -0
- package/src/tests/lib/getCurrencyForServices.test.tsx +44 -0
- package/src/tests/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.test.ts +86 -0
- package/src/tests/lib/logging.test.tsx +32 -0
- package/src/tests/lib/moneyStringFormatter.test.tsx +12 -0
- package/src/tests/lib/validateProps.test.tsx +57 -0
- package/src/types/Aircraft.ts +16 -0
- package/src/types/Airline.ts +16 -0
- package/src/types/Airport.ts +54 -0
- package/src/types/City.ts +21 -0
- package/src/types/CreateOrderPayload.ts +99 -0
- package/src/types/CurrencyConversion.ts +10 -0
- package/src/types/DuffelAncillariesProps.ts +108 -0
- package/src/types/Offer.ts +851 -0
- package/src/types/Order.ts +6 -0
- package/src/types/Place.ts +6 -0
- package/src/types/SeatMap.ts +231 -0
- package/src/types/index.ts +11 -0
- package/tsconfig.json +52 -0
- package/LICENSE +0 -21
- package/dist/AdditionalBaggage.esm.js +0 -1
- package/dist/AdditionalBaggage.js +0 -1
- package/dist/AdditionalBaggage.min.css +0 -408
- package/dist/AdditionalBaggage.umd.min.js +0 -2
- package/dist/AdditionalBaggage.umd.min.js.LICENSE.txt +0 -60
- package/dist/AdditionalBaggageSelection.esm.js +0 -1
- package/dist/AdditionalBaggageSelection.js +0 -1
- package/dist/AdditionalBaggageSelection.min.css +0 -744
- package/dist/AdditionalBaggageSelection.umd.min.js +0 -2
- package/dist/AdditionalBaggageSelection.umd.min.js.LICENSE.txt +0 -93
- package/dist/CardPayment.esm.js +0 -2
- package/dist/CardPayment.esm.js.LICENSE.txt +0 -6
- package/dist/CardPayment.js +0 -2
- package/dist/CardPayment.js.LICENSE.txt +0 -6
- package/dist/CardPayment.min.css +0 -233
- package/dist/CardPayment.umd.min.js +0 -2
- package/dist/CardPayment.umd.min.js.LICENSE.txt +0 -61
- package/dist/SeatSelection.esm.js +0 -1
- package/dist/SeatSelection.js +0 -1
- package/dist/SeatSelection.min.css +0 -1127
- package/dist/SeatSelection.umd.min.js +0 -2
- package/dist/SeatSelection.umd.min.js.LICENSE.txt +0 -60
- package/dist/duffel-components.d.ts +0 -1614
- package/dist/duffel-components.esm.js +0 -2
- package/dist/duffel-components.esm.js.LICENSE.txt +0 -6
- package/dist/duffel-components.js +0 -2
- package/dist/duffel-components.js.LICENSE.txt +0 -6
- package/dist/duffel-components.min.css +0 -1280
- package/dist/duffel-components.umd.min.js +0 -2
- package/dist/duffel-components.umd.min.js.LICENSE.txt +0 -102
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OfferAvailableService } from "../types/Offer";
|
|
2
|
+
|
|
3
|
+
export type ServicePriceMapById = Record<
|
|
4
|
+
OfferAvailableService["id"],
|
|
5
|
+
Pick<OfferAvailableService, "total_amount" | "total_currency">
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export const getServicePriceMapById = (
|
|
9
|
+
availableServices: OfferAvailableService[]
|
|
10
|
+
): ServicePriceMapById =>
|
|
11
|
+
availableServices.reduce(
|
|
12
|
+
(all, { id, total_amount, total_currency }) => ({
|
|
13
|
+
[id]: {
|
|
14
|
+
total_amount,
|
|
15
|
+
total_currency,
|
|
16
|
+
},
|
|
17
|
+
...all,
|
|
18
|
+
}),
|
|
19
|
+
{} as ServicePriceMapById
|
|
20
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SeatMapCabin,
|
|
3
|
+
SeatMapCabinRowSectionElementAmenity,
|
|
4
|
+
} from "../types/SeatMap";
|
|
5
|
+
|
|
6
|
+
export const getSymbols = (
|
|
7
|
+
cabins: SeatMapCabin[]
|
|
8
|
+
): Set<SeatMapCabinRowSectionElementAmenity> => {
|
|
9
|
+
const results: Set<SeatMapCabinRowSectionElementAmenity> = new Set();
|
|
10
|
+
for (const cabin of cabins) {
|
|
11
|
+
for (const row of cabin.rows) {
|
|
12
|
+
for (const section of row.sections) {
|
|
13
|
+
for (const element of section.elements) {
|
|
14
|
+
if (element.type !== "seat" && element.type !== "empty") {
|
|
15
|
+
results.add(element.type);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return results;
|
|
22
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CreateOrderPayloadServices } from "../types/CreateOrderPayload";
|
|
2
|
+
import { Offer } from "../types/Offer";
|
|
3
|
+
import { SeatMap } from "../types/SeatMap";
|
|
4
|
+
import { captureErrorInSentry } from "./captureErrorInSentry";
|
|
5
|
+
import {
|
|
6
|
+
ServicePriceMapById,
|
|
7
|
+
getServicePriceMapById,
|
|
8
|
+
} from "./getServicePriceMapById";
|
|
9
|
+
|
|
10
|
+
export const getTotalAmountForServices = (
|
|
11
|
+
offer: Offer,
|
|
12
|
+
selectedServices: CreateOrderPayloadServices,
|
|
13
|
+
seatMaps?: SeatMap[]
|
|
14
|
+
): number => {
|
|
15
|
+
if (!offer || selectedServices.length === 0) return 0;
|
|
16
|
+
const servicePriceMap = getServicePriceMapById(offer.available_services);
|
|
17
|
+
return getTotalAmountForServicesWithPriceMap(
|
|
18
|
+
servicePriceMap,
|
|
19
|
+
selectedServices,
|
|
20
|
+
seatMaps
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getTotalAmountForServicesWithPriceMap = (
|
|
25
|
+
servicePriceMap: ServicePriceMapById,
|
|
26
|
+
selectedServices: CreateOrderPayloadServices,
|
|
27
|
+
seatMaps?: SeatMap[]
|
|
28
|
+
) =>
|
|
29
|
+
selectedServices.reduce(
|
|
30
|
+
(total, { quantity, id }) => {
|
|
31
|
+
let newTotal = total;
|
|
32
|
+
|
|
33
|
+
if (id in servicePriceMap) {
|
|
34
|
+
newTotal += quantity * +servicePriceMap[id].total_amount;
|
|
35
|
+
} else if (seatMaps) {
|
|
36
|
+
newTotal += quantity * getTotalAmountFromSeatMaps(id, seatMaps);
|
|
37
|
+
} else {
|
|
38
|
+
captureErrorInSentry(
|
|
39
|
+
new Error(
|
|
40
|
+
`The service id (${id}) provided could not be found in neither the offer nor the seat maps.`
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return newTotal;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
0
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const getTotalAmountFromSeatMaps = (serviceId: string, seatMaps: SeatMap[]) => {
|
|
52
|
+
for (const seatMap of seatMaps) {
|
|
53
|
+
for (const cabin of seatMap.cabins) {
|
|
54
|
+
for (const row of cabin.rows) {
|
|
55
|
+
for (const section of row.sections) {
|
|
56
|
+
for (const element of section.elements) {
|
|
57
|
+
if (
|
|
58
|
+
element.type === "seat" &&
|
|
59
|
+
Array.isArray(element.available_services)
|
|
60
|
+
) {
|
|
61
|
+
const serviceMatch = element.available_services.find(
|
|
62
|
+
(service) => service.id === serviceId
|
|
63
|
+
);
|
|
64
|
+
if (serviceMatch) return +serviceMatch.total_amount;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Offer, OfferAvailableServiceType } from "../types/Offer";
|
|
2
|
+
import { isBaggageService } from "./isBaggageService";
|
|
3
|
+
import { isCancelForAnyReasonService } from "./isCancelForAnyReasonService";
|
|
4
|
+
|
|
5
|
+
const checkFunctionsMap = {
|
|
6
|
+
baggage: isBaggageService,
|
|
7
|
+
cancel_for_any_reason: isCancelForAnyReasonService,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const hasService = (
|
|
11
|
+
offer: Offer | undefined,
|
|
12
|
+
type: OfferAvailableServiceType
|
|
13
|
+
) => {
|
|
14
|
+
const checkFunction = checkFunctionsMap[type];
|
|
15
|
+
if (!checkFunction) throw new Error(`Unknown service type: ${type}`);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
offer &&
|
|
19
|
+
Array.isArray(offer.available_services) &&
|
|
20
|
+
offer.available_services.some(
|
|
21
|
+
(service) => checkFunction(service) && service.maximum_quantity > 0
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CreateOrderPayloadServices } from "../types/CreateOrderPayload";
|
|
2
|
+
import { OfferAvailableServiceBaggage } from "../types/Offer";
|
|
3
|
+
|
|
4
|
+
export const hasServiceOfSameMetadataTypeAlreadyBeenSelected = (
|
|
5
|
+
selectedServices: CreateOrderPayloadServices,
|
|
6
|
+
segmentId: string,
|
|
7
|
+
passengerId: string,
|
|
8
|
+
availableService: OfferAvailableServiceBaggage
|
|
9
|
+
) =>
|
|
10
|
+
selectedServices.some((selectedService) => {
|
|
11
|
+
if (selectedService.id === availableService.id) {
|
|
12
|
+
// if the selected service is the one on the counter, don't disable it
|
|
13
|
+
// the max quantity will be availableService.maximum_quantity
|
|
14
|
+
return false;
|
|
15
|
+
} else if (
|
|
16
|
+
selectedService.serviceInformation?.type !== "carry_on" &&
|
|
17
|
+
selectedService.serviceInformation?.type !== "checked"
|
|
18
|
+
) {
|
|
19
|
+
return false;
|
|
20
|
+
} else if (selectedService.serviceInformation?.segmentId !== segmentId) {
|
|
21
|
+
// if the selected service doesn't belong to the same segment, don't disable it
|
|
22
|
+
return false;
|
|
23
|
+
} else if (
|
|
24
|
+
// if the selected service doesn't belong to the same passenger, don't disable it
|
|
25
|
+
selectedService.serviceInformation?.passengerId !== passengerId
|
|
26
|
+
) {
|
|
27
|
+
return false;
|
|
28
|
+
} else {
|
|
29
|
+
// if this service selection controller is for the same passenger and segment, disable it
|
|
30
|
+
return (
|
|
31
|
+
selectedService.serviceInformation?.type ===
|
|
32
|
+
availableService.metadata.type
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OfferAvailableService,
|
|
3
|
+
OfferAvailableServiceCancelForAnyReason,
|
|
4
|
+
} from "../types/Offer";
|
|
5
|
+
|
|
6
|
+
export const isCancelForAnyReasonService = (
|
|
7
|
+
service: OfferAvailableService
|
|
8
|
+
): service is OfferAvailableServiceCancelForAnyReason =>
|
|
9
|
+
service.type === "cancel_for_any_reason";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CreateOrderPayload } from "../types/CreateOrderPayload";
|
|
2
|
+
|
|
3
|
+
export const isPayloadComplete = (
|
|
4
|
+
payload: Partial<CreateOrderPayload>
|
|
5
|
+
): payload is CreateOrderPayload =>
|
|
6
|
+
"selected_offers" in payload &&
|
|
7
|
+
"passengers" in payload &&
|
|
8
|
+
"services" in payload &&
|
|
9
|
+
"payments" in payload &&
|
|
10
|
+
"type" in payload &&
|
|
11
|
+
"metadata" in payload;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SeatMapCabinRowSectionElement,
|
|
3
|
+
SeatMapCabinRowSectionElementSeat,
|
|
4
|
+
} from "../types/SeatMap";
|
|
5
|
+
|
|
6
|
+
export function isSeatElement(
|
|
7
|
+
element: SeatMapCabinRowSectionElement
|
|
8
|
+
): element is SeatMapCabinRowSectionElementSeat {
|
|
9
|
+
return element.type === "seat";
|
|
10
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/browser";
|
|
2
|
+
|
|
3
|
+
const MESSAGE_PREFIX = "[Duffel Ancillaries] ";
|
|
4
|
+
const LOCAL_STORAGE_KEY = "duffel-ancillaries-logger-state";
|
|
5
|
+
let LOG_INITIALISED = false;
|
|
6
|
+
|
|
7
|
+
const storeLoggerState = (shouldLog: boolean) => {
|
|
8
|
+
localStorage.setItem(LOCAL_STORAGE_KEY, shouldLog.toString());
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const shouldLog = () => localStorage.getItem(LOCAL_STORAGE_KEY) === "true";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The functions in this file are used to enable logging.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
*
|
|
18
|
+
* In your app's outermost container, import the LogContext and wrap your app in it:
|
|
19
|
+
*
|
|
20
|
+
* ```jsx
|
|
21
|
+
* import { LogContext, initializeLogger } from '@lib/logging'
|
|
22
|
+
*
|
|
23
|
+
* const logger = initializeLogger(props.debugMode || false)
|
|
24
|
+
*
|
|
25
|
+
* <LogContext.Provider value={logger}>
|
|
26
|
+
* ...
|
|
27
|
+
* </LogContext.Provider>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Then in your components nested within the above container, import the useLog hook and use it:
|
|
31
|
+
*
|
|
32
|
+
* import { useLog } from '@lib/logging'
|
|
33
|
+
*
|
|
34
|
+
* const { log, logGroup } = useLog()
|
|
35
|
+
* log('This is a log message')
|
|
36
|
+
* logGroup('These messages will be grouped together', ['This is a log message', 'This is another log message'])
|
|
37
|
+
*/
|
|
38
|
+
const initializeLogger = (debugMode: boolean): void => {
|
|
39
|
+
storeLoggerState(debugMode);
|
|
40
|
+
if (debugMode && !LOG_INITIALISED) {
|
|
41
|
+
// eslint-disable-next-line
|
|
42
|
+
console.info(
|
|
43
|
+
MESSAGE_PREFIX,
|
|
44
|
+
`\n\nDebug mode is enabled. Information about your setup will be printed to the console.
|
|
45
|
+
|
|
46
|
+
If you do not want to enable debug mode (for example in a production environment), pass "debug: false" when initializing this component.
|
|
47
|
+
|
|
48
|
+
Learn more about the Ancillaries component:
|
|
49
|
+
http://duffel.com/docs/guides/ancillaries-component`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
LOG_INITIALISED = true;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Log a message to the console. Messages will be prefixed with "[Duffel Ancillaries]".
|
|
57
|
+
* @param message The message to print to the console.
|
|
58
|
+
*/
|
|
59
|
+
const log = (message: any) => {
|
|
60
|
+
if (shouldLog()) {
|
|
61
|
+
// eslint-disable-next-line
|
|
62
|
+
console.info(MESSAGE_PREFIX, message);
|
|
63
|
+
} else {
|
|
64
|
+
Sentry.addBreadcrumb({
|
|
65
|
+
category: "log",
|
|
66
|
+
message,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Log a series of messages to the console inside a collapsible group.
|
|
73
|
+
* @param groupName The name of the group of messages. This will be prefixed with "[Duffel Ancillaries]".
|
|
74
|
+
* @param messages An array of messages to print to the console, inside the group.
|
|
75
|
+
*/
|
|
76
|
+
function logGroup(groupName: string, messages: any[]): void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Log a series of messages to the console inside a collapsible group.
|
|
80
|
+
* @param groupName The name of the group of messages. This will be prefixed with "[Duffel Ancillaries]".
|
|
81
|
+
* @param object An object to print to the console, inside the group.
|
|
82
|
+
*/
|
|
83
|
+
function logGroup(groupName: string, object: { [key: string]: any }): void;
|
|
84
|
+
|
|
85
|
+
// Overloaded function implementation.
|
|
86
|
+
// https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads
|
|
87
|
+
function logGroup(
|
|
88
|
+
groupName: string,
|
|
89
|
+
messagesOrObject: any[] | { [key: string]: any }
|
|
90
|
+
): void {
|
|
91
|
+
let transformedMessagesOrObject = [];
|
|
92
|
+
if (Array.isArray(messagesOrObject)) {
|
|
93
|
+
transformedMessagesOrObject = messagesOrObject;
|
|
94
|
+
} else {
|
|
95
|
+
transformedMessagesOrObject = Object.entries(messagesOrObject).map(
|
|
96
|
+
([key, value]) => ({ property: key, value })
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (shouldLog()) {
|
|
101
|
+
// eslint-disable-next-line
|
|
102
|
+
console.groupCollapsed(MESSAGE_PREFIX, groupName);
|
|
103
|
+
|
|
104
|
+
transformedMessagesOrObject.forEach((message) => {
|
|
105
|
+
// eslint-disable-next-line
|
|
106
|
+
console.info(message);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// eslint-disable-next-line
|
|
110
|
+
console.groupEnd();
|
|
111
|
+
} else {
|
|
112
|
+
Sentry.addBreadcrumb({
|
|
113
|
+
category: "log.group",
|
|
114
|
+
message: groupName,
|
|
115
|
+
data: messagesOrObject,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { initializeLogger, logGroup, log };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a function to format a number into a money amount for a given currency
|
|
3
|
+
*
|
|
4
|
+
* @param currency The ISO-4217 currency code to be used by the formatter
|
|
5
|
+
*/
|
|
6
|
+
export const moneyStringFormatter = (
|
|
7
|
+
currency: string,
|
|
8
|
+
locale = "en-GB",
|
|
9
|
+
options: { [option: string]: string } = {}
|
|
10
|
+
) => {
|
|
11
|
+
return (value: number) => {
|
|
12
|
+
try {
|
|
13
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
14
|
+
style: "currency",
|
|
15
|
+
currency,
|
|
16
|
+
...options,
|
|
17
|
+
});
|
|
18
|
+
const numberFormatted = formatter.format(value);
|
|
19
|
+
const signFormatted =
|
|
20
|
+
options &&
|
|
21
|
+
options["signDisplay"] &&
|
|
22
|
+
(options["signDisplay"] === "always" ||
|
|
23
|
+
(options["signDisplay"] === "exceptZero" && value !== 0))
|
|
24
|
+
? numberFormatted.replace(/^([+-])/, "$1 ")
|
|
25
|
+
: numberFormatted;
|
|
26
|
+
|
|
27
|
+
return signFormatted;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// If the currency is not supported by the browser, we return the value with the currency code.
|
|
30
|
+
// This allows us to support made-up currencies, like "1000 Duffel Points"
|
|
31
|
+
return `${value} ${currency}`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Offer } from "../types/Offer";
|
|
2
|
+
import { captureErrorInSentry } from "./captureErrorInSentry";
|
|
3
|
+
import { isErrorResponse } from "./fetchFromDuffelAPI";
|
|
4
|
+
import { importFromOfferFixtures } from "./fetchFromFixtures";
|
|
5
|
+
import { isFixtureOfferId } from "./isFixtureOfferId";
|
|
6
|
+
import { retrieveOfferFromDuffelAPI } from "./retrieveOfferFromDuffelAPI";
|
|
7
|
+
|
|
8
|
+
export async function retrieveOffer(
|
|
9
|
+
offer_id: string,
|
|
10
|
+
client_key: string | null,
|
|
11
|
+
onError: (error: string) => void,
|
|
12
|
+
setIsLoading: (isLoading: boolean) => void,
|
|
13
|
+
onOfferReady: (offer: Offer) => void
|
|
14
|
+
) {
|
|
15
|
+
setIsLoading(true);
|
|
16
|
+
const useFixture = isFixtureOfferId(offer_id);
|
|
17
|
+
offer_id = useFixture ? offer_id.replace("fixture_", "") : offer_id;
|
|
18
|
+
|
|
19
|
+
if (useFixture) {
|
|
20
|
+
return importFromOfferFixtures(offer_id).then((offer) => {
|
|
21
|
+
setIsLoading(false);
|
|
22
|
+
onOfferReady(offer);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!client_key) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Attemptted to retrieve seat maps but the client key is missing."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const data = await retrieveOfferFromDuffelAPI(offer_id, client_key);
|
|
34
|
+
onOfferReady(data);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
let message = "An unknown error occurred while retrieving the offer.";
|
|
37
|
+
if (error instanceof Error) {
|
|
38
|
+
message = error.message;
|
|
39
|
+
if (error.message.includes("Load failed")) {
|
|
40
|
+
message = "The Duffel API is not available. Please try again later.";
|
|
41
|
+
}
|
|
42
|
+
} else if (isErrorResponse(error)) {
|
|
43
|
+
if (error.status === 404) {
|
|
44
|
+
message =
|
|
45
|
+
"The offer you are looking for does not exist or has expired.";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
captureErrorInSentry(new Error(message));
|
|
50
|
+
onError(message);
|
|
51
|
+
} finally {
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fetchFromDuffelAPI } from "./fetchFromDuffelAPI";
|
|
2
|
+
|
|
3
|
+
export async function retrieveOfferFromDuffelAPI(
|
|
4
|
+
offer_id: string,
|
|
5
|
+
client_key: string
|
|
6
|
+
) {
|
|
7
|
+
const getOfferResponse = await fetchFromDuffelAPI(
|
|
8
|
+
client_key,
|
|
9
|
+
`offers/${offer_id}?return_available_services=true`
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return getOfferResponse.data;
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SeatMap } from "../types/SeatMap";
|
|
2
|
+
import { captureErrorInSentry } from "./captureErrorInSentry";
|
|
3
|
+
import { isErrorResponse } from "./fetchFromDuffelAPI";
|
|
4
|
+
import { importFromSeatMapsFixtures } from "./fetchFromFixtures";
|
|
5
|
+
import { isFixtureOfferId } from "./isFixtureOfferId";
|
|
6
|
+
import { retrieveSeatMapsFromDuffelAPI } from "./retrieveSeatMapsFromDuffelAPI";
|
|
7
|
+
|
|
8
|
+
export async function retrieveSeatMaps(
|
|
9
|
+
offer_id: string,
|
|
10
|
+
client_key: string | null,
|
|
11
|
+
onError: () => void,
|
|
12
|
+
setIsLoading: (isLoading: boolean) => void,
|
|
13
|
+
onSeatMapReady: (seatMaps: SeatMap[]) => void
|
|
14
|
+
) {
|
|
15
|
+
setIsLoading(true);
|
|
16
|
+
|
|
17
|
+
const useFixture = isFixtureOfferId(offer_id);
|
|
18
|
+
offer_id = useFixture ? offer_id.replace("fixture_", "") : offer_id;
|
|
19
|
+
|
|
20
|
+
if (useFixture) {
|
|
21
|
+
return importFromSeatMapsFixtures(offer_id).then((seatMaps) => {
|
|
22
|
+
setIsLoading(false);
|
|
23
|
+
onSeatMapReady(seatMaps);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!client_key) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Attemptted to retrieve seat maps but the client key is missing."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const data = await retrieveSeatMapsFromDuffelAPI(offer_id, client_key);
|
|
35
|
+
onSeatMapReady(data);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
let message = "An unknown error occurred while retrieving the seat maps.";
|
|
38
|
+
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
message = error.message;
|
|
41
|
+
if (error.message.includes("Load failed")) {
|
|
42
|
+
message = "The Duffel API is not available. Please try again later.";
|
|
43
|
+
}
|
|
44
|
+
} else if (isErrorResponse(error)) {
|
|
45
|
+
message =
|
|
46
|
+
error.data.errors[0]?.message ||
|
|
47
|
+
"Received an unknown error from the Duffel API.";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
captureErrorInSentry(new Error(message));
|
|
51
|
+
onError();
|
|
52
|
+
} finally {
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fetchFromDuffelAPI } from "./fetchFromDuffelAPI";
|
|
2
|
+
|
|
3
|
+
export async function retrieveSeatMapsFromDuffelAPI(
|
|
4
|
+
offer_id: string,
|
|
5
|
+
client_key: string
|
|
6
|
+
) {
|
|
7
|
+
const getSeatMapResponse = await fetchFromDuffelAPI(
|
|
8
|
+
client_key,
|
|
9
|
+
`offers/${offer_id}/seat_maps`
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return getSeatMapResponse.data;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DuffelAncillariesProps,
|
|
3
|
+
DuffelAncillariesPropsWithClientKeyAndOfferId,
|
|
4
|
+
DuffelAncillariesPropsWithOfferIdForFixture,
|
|
5
|
+
DuffelAncillariesPropsWithOffersAndSeatMaps,
|
|
6
|
+
DuffelAncillariesPropWithOfferAndClientKey,
|
|
7
|
+
} from "../types/DuffelAncillariesProps";
|
|
8
|
+
|
|
9
|
+
export const areDuffelAncillariesPropsValid = (props: DuffelAncillariesProps) =>
|
|
10
|
+
hasCommonRequiredProps(props) &&
|
|
11
|
+
(isDuffelAncillariesPropsWithOfferIdForFixture(props) ||
|
|
12
|
+
isDuffelAncillariesPropsWithClientKeyAndOfferId(props) ||
|
|
13
|
+
isDuffelAncillariesPropsWithOfferAndSeatMaps(props) ||
|
|
14
|
+
isDuffelAncillariesPropsWithOfferAndClientKey(props));
|
|
15
|
+
|
|
16
|
+
export const hasCommonRequiredProps = (props: DuffelAncillariesProps) =>
|
|
17
|
+
"onPayloadReady" in props && "passengers" in props && "services" in props;
|
|
18
|
+
|
|
19
|
+
export const isDuffelAncillariesPropsWithOfferIdForFixture = (
|
|
20
|
+
props: DuffelAncillariesProps
|
|
21
|
+
): props is DuffelAncillariesPropsWithOfferIdForFixture =>
|
|
22
|
+
"offer_id" in props && props.offer_id.startsWith("fixture_");
|
|
23
|
+
|
|
24
|
+
export const isDuffelAncillariesPropsWithClientKeyAndOfferId = (
|
|
25
|
+
props: DuffelAncillariesProps
|
|
26
|
+
): props is DuffelAncillariesPropsWithClientKeyAndOfferId =>
|
|
27
|
+
"offer_id" in props && "client_key" in props;
|
|
28
|
+
|
|
29
|
+
export const isDuffelAncillariesPropsWithOfferAndSeatMaps = (
|
|
30
|
+
props: DuffelAncillariesProps
|
|
31
|
+
): props is DuffelAncillariesPropsWithOffersAndSeatMaps =>
|
|
32
|
+
"offer" in props && "seat_maps" in props;
|
|
33
|
+
|
|
34
|
+
export const isDuffelAncillariesPropsWithOfferAndClientKey = (
|
|
35
|
+
props: DuffelAncillariesProps
|
|
36
|
+
): props is DuffelAncillariesPropWithOfferAndClientKey =>
|
|
37
|
+
"offer" in props && "client_key" in props;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { BaggageSelectionModalHeader } from "../components/DuffelAncillaries/bags/BaggageSelectionModalHeader";
|
|
3
|
+
import { OfferSliceSegment } from "../types/Offer";
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
|
+
const offer = require("../fixtures/offers/off_0000AUde3KwTztSRK1cznH.json");
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: "BaggageSelectionModalHeader",
|
|
9
|
+
component: BaggageSelectionModalHeader,
|
|
10
|
+
} as Meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof BaggageSelectionModalHeader>;
|
|
13
|
+
|
|
14
|
+
export const FirstStory: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
segmentCount: 2,
|
|
17
|
+
currentSegmentIndex: 0,
|
|
18
|
+
currentSegment: offer.slices[0].segments[0] as OfferSliceSegment,
|
|
19
|
+
setCurrentSegmentIndex: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
20
|
+
},
|
|
21
|
+
};
|