@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.
- package/.eslintrc.js +8 -0
- package/.github/CODEOWNERS +4 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/renovate.json +1 -5
- package/.github/workflows/release.yml +3 -0
- package/.storybook/__snapshots__/Storyshots.test.js.snap +20384 -533
- package/.tool-versions +1 -1
- package/README.md +16 -2
- package/config/esbuild.base.config.js +6 -2
- package/config/esbuild.cdn.config.js +2 -1
- package/config/esbuild.dev.config.js +2 -1
- package/config/esbuild.react.config.js +1 -1
- package/data/airports.csv +9084 -0
- package/data/cities.csv +256 -0
- package/package.json +11 -1
- package/react-dist/index.js +51 -21
- package/scripts/generate-fixture.ts +13 -8
- package/scripts/setup-suggestion-data.ts +100 -0
- package/scripts/upload-to-cdn.sh +1 -1
- package/src/components/{Card.tsx → DuffelAncillaries/Card.tsx} +1 -1
- package/src/components/{Counter.tsx → DuffelAncillaries/Counter.tsx} +1 -1
- package/src/components/{DuffelAncillaries.tsx → DuffelAncillaries/DuffelAncillaries.tsx} +68 -64
- package/src/components/{DuffelAncillariesCustomElement.tsx → DuffelAncillaries/DuffelAncillariesCustomElement.tsx} +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.tsx +10 -5
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.tsx +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.tsx +4 -4
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.tsx +3 -3
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.tsx +4 -4
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.tsx +23 -16
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.tsx +24 -18
- package/src/components/{bags → DuffelAncillaries/bags}/IncludedBaggageBanner.tsx +1 -1
- package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionCard.tsx +4 -4
- package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModal.tsx +3 -3
- package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBody.tsx +3 -3
- package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBodyListItem.tsx +1 -1
- package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalFooter.tsx +5 -5
- package/src/components/{seats → DuffelAncillaries/seats}/Amenity.tsx +2 -2
- package/src/components/{seats → DuffelAncillaries/seats}/DeckSelect.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/Element.tsx +2 -2
- package/src/components/{seats → DuffelAncillaries/seats}/ExitElement.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/Legend.tsx +2 -2
- package/src/components/{seats → DuffelAncillaries/seats}/Row.tsx +2 -2
- package/src/components/{seats → DuffelAncillaries/seats}/RowSection.tsx +5 -2
- package/src/components/{seats → DuffelAncillaries/seats}/SeatElement.tsx +3 -3
- package/src/components/{seats → DuffelAncillaries/seats}/SeatInfo.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/SeatMap.tsx +6 -2
- package/src/components/{seats → DuffelAncillaries/seats}/SeatMapUnavailable.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionCard.tsx +5 -5
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModal.tsx +5 -5
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.tsx +24 -17
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.tsx +30 -20
- package/src/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.tsx +2 -2
- package/src/components/DuffelPayments/DuffelPayments.tsx +224 -0
- package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -0
- package/src/components/PlacesLookup/PlacesLookup.tsx +123 -0
- package/src/components/ShowData/ShowData.tsx +38 -0
- package/src/components/ShowData/ShowDataCustomElement.tsx +85 -0
- package/src/components/{Button.tsx → shared/Button.tsx} +4 -3
- package/src/components/{ErrorBoundary.tsx → shared/ErrorBoundary.tsx} +2 -2
- package/src/components/{Icon.tsx → shared/Icon.tsx} +11 -11
- package/src/components/{IconButton.tsx → shared/IconButton.tsx} +1 -1
- package/src/components/{Modal.tsx → shared/Modal.tsx} +5 -1
- package/src/components/{NonIdealState.tsx → shared/NonIdealState.tsx} +1 -1
- package/src/custom-elements.ts +6 -1
- package/src/examples/client-side/index.html +1 -1
- package/src/examples/full-stack/index.html +1 -1
- package/src/examples/full-stack/server.mjs +1 -0
- package/src/examples/just-typescript/src/index.html +2 -2
- package/src/examples/just-typescript/src/index.ts +2 -1
- 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/react-app/src/index.tsx +11 -6
- package/src/fixtures/offers/off_1.json +1 -10
- package/src/index.ts +3 -1
- package/src/lib/captureErrorInSentry.ts +2 -20
- package/src/lib/fetchFromDuffelAPI.ts +36 -6
- package/src/lib/formatDate.ts +3 -4
- package/src/lib/getBaggageServiceDescription.ts +1 -6
- package/src/lib/getPassengerName.ts +4 -0
- package/src/lib/getTotalAmountForServices.ts +1 -1
- package/src/lib/hasHighLuminance.ts +9 -0
- package/src/lib/logging.ts +52 -32
- package/src/lib/retrieveOffer.ts +13 -6
- package/src/lib/retrieveSeatMaps.ts +13 -8
- package/src/stories/BaggageSelectionModalHeader.stories.tsx +1 -1
- package/src/stories/Button.stories.tsx +33 -2
- package/src/stories/DuffelAncillaries.stories.tsx +42 -2
- package/src/stories/DuffelPayments.stories.tsx +34 -0
- package/src/stories/Icon.stories.tsx +3 -2
- package/src/stories/IconButton.stories.tsx +1 -1
- package/src/stories/PlacesLookup.stories.tsx +22 -0
- package/src/stories/ShowData.stories.tsx +16 -0
- package/src/styles/components/Button.css +11 -3
- package/src/styles/components/Card.css +3 -3
- package/src/styles/components/CfarSelectionModal.css +1 -1
- package/src/styles/components/DuffelPayments.css +42 -0
- package/src/styles/components/Legend.css +10 -6
- package/src/styles/components/LoadingState.css +8 -2
- package/src/styles/components/Modal.css +2 -1
- package/src/styles/components/PassengerSelect.css +8 -2
- package/src/styles/components/PlacesLookup.css +36 -0
- package/src/styles/components/Seat.css +9 -7
- package/src/styles/components/SeatInfo.css +1 -1
- package/src/styles/components/Tabs.css +5 -2
- package/src/styles/global.css +2 -0
- package/src/tests/components/DuffelAncillaries.test.tsx +1 -1
- package/src/tests/lib/createPriceFormatters.test.tsx +1 -1
- package/src/tests/lib/formatAvailableServices.test.tsx +1 -1
- package/src/tests/lib/formatSeatMaps.test.tsx +2 -2
- package/src/tests/lib/getCurrencyForServices.test.tsx +1 -1
- package/src/tests/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.test.ts +1 -1
- package/src/tests/lib/logging.test.tsx +14 -14
- package/src/tests/lib/moneyStringFormatter.test.tsx +1 -1
- package/src/tests/lib/validateProps.test.tsx +1 -1
- package/src/types/DuffelAncillariesProps.ts +1 -1
- package/react-dist/components/AnimatedLoaderEllipsis.d.ts +0 -2
- package/react-dist/components/Button.d.ts +0 -23
- package/react-dist/components/Card.d.ts +0 -14
- package/react-dist/components/Counter.d.ts +0 -10
- package/react-dist/components/DuffelAncillaries.d.ts +0 -3
- package/react-dist/components/DuffelAncillariesCustomElement.d.ts +0 -13
- package/react-dist/components/ErrorBoundary.d.ts +0 -13
- package/react-dist/components/FetchOfferErrorState.d.ts +0 -5
- package/react-dist/components/Icon.d.ts +0 -44
- package/react-dist/components/IconButton.d.ts +0 -16
- package/react-dist/components/Modal.d.ts +0 -11
- package/react-dist/components/NonIdealState.d.ts +0 -4
- package/react-dist/components/Stamp.d.ts +0 -7
- package/react-dist/components/Tabs.d.ts +0 -16
- package/react-dist/components/bags/BaggageSelectionCard.d.ts +0 -11
- package/react-dist/components/bags/BaggageSelectionController.d.ts +0 -13
- package/react-dist/components/bags/BaggageSelectionModal.d.ts +0 -11
- package/react-dist/components/bags/BaggageSelectionModalBody.d.ts +0 -11
- package/react-dist/components/bags/BaggageSelectionModalBodyPassenger.d.ts +0 -13
- package/react-dist/components/bags/BaggageSelectionModalFooter.d.ts +0 -14
- package/react-dist/components/bags/BaggageSelectionModalHeader.d.ts +0 -9
- package/react-dist/components/bags/IncludedBaggageBanner.d.ts +0 -7
- package/react-dist/components/cancel_for_any_reason/CfarSelectionCard.d.ts +0 -10
- package/react-dist/components/cancel_for_any_reason/CfarSelectionModal.d.ts +0 -11
- package/react-dist/components/cancel_for_any_reason/CfarSelectionModalBody.d.ts +0 -7
- package/react-dist/components/cancel_for_any_reason/CfarSelectionModalBodyListItem.d.ts +0 -4
- package/react-dist/components/cancel_for_any_reason/CfarSelectionModalFooter.d.ts +0 -11
- package/react-dist/components/cancel_for_any_reason/CfarSelectionModalHeader.d.ts +0 -2
- package/react-dist/components/seats/Amenity.d.ts +0 -6
- package/react-dist/components/seats/DeckSelect.d.ts +0 -15
- package/react-dist/components/seats/Element.d.ts +0 -15
- package/react-dist/components/seats/EmptyElement.d.ts +0 -2
- package/react-dist/components/seats/ExitElement.d.ts +0 -6
- package/react-dist/components/seats/Legend.d.ts +0 -12
- package/react-dist/components/seats/Row.d.ts +0 -13
- package/react-dist/components/seats/RowSection.d.ts +0 -17
- package/react-dist/components/seats/SeatElement.d.ts +0 -13
- package/react-dist/components/seats/SeatInfo.d.ts +0 -7
- package/react-dist/components/seats/SeatMap.d.ts +0 -12
- package/react-dist/components/seats/SeatMapUnavailable.d.ts +0 -2
- package/react-dist/components/seats/SeatSelectionCard.d.ts +0 -13
- package/react-dist/components/seats/SeatSelectionModal.d.ts +0 -13
- package/react-dist/components/seats/SeatSelectionModalBody.d.ts +0 -4
- package/react-dist/components/seats/SeatSelectionModalFooter.d.ts +0 -16
- package/react-dist/components/seats/SeatSelectionModalHeader.d.ts +0 -10
- package/react-dist/components/seats/SeatUnavailable.d.ts +0 -5
- package/react-dist/custom-elements.d.ts +0 -5
- package/react-dist/custom-elements.js +0 -36
- package/react-dist/custom-elements.js.map +0 -7
- package/react-dist/index.d.ts +0 -6
- package/react-dist/index.js.map +0 -7
- package/react-dist/lib/captureErrorInSentry.d.ts +0 -1
- package/react-dist/lib/compileCreateOrderPayload.d.ts +0 -14
- package/react-dist/lib/createPriceFormatters.d.ts +0 -12
- package/react-dist/lib/fetchFromDuffelAPI.d.ts +0 -1
- package/react-dist/lib/fetchFromFixtures.d.ts +0 -4
- package/react-dist/lib/formatAvailableServices.d.ts +0 -12
- package/react-dist/lib/formatDate.d.ts +0 -2
- package/react-dist/lib/formatSeatMaps.d.ts +0 -4
- package/react-dist/lib/getBaggageServiceDescription.d.ts +0 -2
- package/react-dist/lib/getCabinsForSegmentAndDeck.d.ts +0 -2
- package/react-dist/lib/getCurrencyForSeatMaps.d.ts +0 -10
- package/react-dist/lib/getCurrencyForServices.d.ts +0 -11
- package/react-dist/lib/getFirstSeatElementMatchingCriteria.d.ts +0 -3
- package/react-dist/lib/getPassengerBySegmentList.d.ts +0 -6
- package/react-dist/lib/getPassengerInitials.d.ts +0 -1
- package/react-dist/lib/getPassengerMapById.d.ts +0 -3
- package/react-dist/lib/getPassengerName.d.ts +0 -3
- package/react-dist/lib/getRowNumber.d.ts +0 -2
- package/react-dist/lib/getSegmentList.d.ts +0 -2
- package/react-dist/lib/getServicePriceMapById.d.ts +0 -3
- package/react-dist/lib/getSymbols.d.ts +0 -2
- package/react-dist/lib/getTotalAmountForServices.d.ts +0 -6
- package/react-dist/lib/getTotalQuantity.d.ts +0 -2
- package/react-dist/lib/hasService.d.ts +0 -2
- package/react-dist/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.d.ts +0 -3
- package/react-dist/lib/hasWings.d.ts +0 -2
- package/react-dist/lib/isBaggageService.d.ts +0 -2
- package/react-dist/lib/isCancelForAnyReasonService.d.ts +0 -2
- package/react-dist/lib/isFixtureOfferId.d.ts +0 -2
- package/react-dist/lib/isPayloadComplete.d.ts +0 -2
- package/react-dist/lib/isSeatElement.d.ts +0 -2
- package/react-dist/lib/logging.d.ts +0 -53
- package/react-dist/lib/moneyStringFormatter.d.ts +0 -8
- package/react-dist/lib/offerIsExpired.d.ts +0 -2
- package/react-dist/lib/retrieveOffer.d.ts +0 -2
- package/react-dist/lib/retrieveOfferFromDuffelAPI.d.ts +0 -1
- package/react-dist/lib/retrieveSeatMaps.d.ts +0 -2
- package/react-dist/lib/retrieveSeatMapsFromDuffelAPI.d.ts +0 -1
- package/react-dist/lib/setBodyScrollability.d.ts +0 -1
- package/react-dist/lib/validateProps.d.ts +0 -7
- package/react-dist/lib/withPlural.d.ts +0 -1
- package/react-dist/types/Aircraft.d.ts +0 -14
- package/react-dist/types/Airline.d.ts +0 -14
- package/react-dist/types/Airport.d.ts +0 -44
- package/react-dist/types/City.d.ts +0 -18
- package/react-dist/types/CreateOrderPayload.d.ts +0 -72
- package/react-dist/types/CurrencyConversion.d.ts +0 -10
- package/react-dist/types/DuffelAncillariesProps.d.ts +0 -70
- package/react-dist/types/Offer.d.ts +0 -711
- package/react-dist/types/Order.d.ts +0 -8
- package/react-dist/types/Place.d.ts +0 -8
- package/react-dist/types/SeatMap.d.ts +0 -190
- package/react-dist/types/index.d.ts +0 -11
- package/src/examples/just-typescript/yarn.lock +0 -154
- package/src/examples/react-app/yarn.lock +0 -219
- /package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalHeader.tsx +0 -0
- /package/src/components/{seats → DuffelAncillaries/seats}/EmptyElement.tsx +0 -0
- /package/src/components/{AnimatedLoaderEllipsis.tsx → shared/AnimatedLoaderEllipsis.tsx} +0 -0
- /package/src/components/{FetchOfferErrorState.tsx → shared/FetchOfferErrorState.tsx} +0 -0
- /package/src/components/{Stamp.tsx → shared/Stamp.tsx} +0 -0
- /package/src/components/{Tabs.tsx → shared/Tabs.tsx} +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { StripeError } from "@stripe/stripe-js";
|
|
2
|
+
import { createRoot, Root } from "react-dom/client";
|
|
3
|
+
import { DuffelPayments, DuffelPaymentsProps } from "./DuffelPayments";
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
7
|
+
namespace JSX {
|
|
8
|
+
interface IntrinsicElements {
|
|
9
|
+
"duffel-payments": React.DetailedHTMLProps<
|
|
10
|
+
React.HTMLAttributes<HTMLElement>,
|
|
11
|
+
HTMLElement
|
|
12
|
+
>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CUSTOM_ELEMENT_TAG = "duffel-payments";
|
|
18
|
+
|
|
19
|
+
type DuffelPaymentsCustomElementRenderArguments = Pick<
|
|
20
|
+
DuffelPaymentsProps,
|
|
21
|
+
"paymentIntentClientToken" | "styles"
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
class DuffelPaymentsCustomElement extends HTMLElement {
|
|
25
|
+
/**
|
|
26
|
+
* The React root for displaying content inside a browser DOM element.
|
|
27
|
+
*/
|
|
28
|
+
private root!: Root;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `connectedCallback` is called to initialise the custom element
|
|
32
|
+
*/
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
const container = document.createElement("div");
|
|
35
|
+
this.appendChild(container);
|
|
36
|
+
|
|
37
|
+
this.root = createRoot(container);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* When this function is called, it will render/re-render
|
|
42
|
+
* the `DuffelPayments` component with the given props.
|
|
43
|
+
*/
|
|
44
|
+
public render(withProps: DuffelPaymentsCustomElementRenderArguments) {
|
|
45
|
+
if (!this.root) {
|
|
46
|
+
throw "It was not possible to render `duffel-payments` because `this.root` is missing.";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.root.render(
|
|
50
|
+
<DuffelPayments
|
|
51
|
+
{...withProps}
|
|
52
|
+
onSuccessfulPayment={() => {
|
|
53
|
+
this.dispatchEvent(
|
|
54
|
+
new CustomEvent("onSuccessfulPayment", {
|
|
55
|
+
composed: true,
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
59
|
+
onFailedPayment={(error: StripeError) => {
|
|
60
|
+
this.dispatchEvent(
|
|
61
|
+
new CustomEvent("onFailedPayment", {
|
|
62
|
+
detail: { error },
|
|
63
|
+
composed: true,
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
window.customElements.get(CUSTOM_ELEMENT_TAG) ||
|
|
73
|
+
window.customElements.define(CUSTOM_ELEMENT_TAG, DuffelPaymentsCustomElement);
|
|
74
|
+
|
|
75
|
+
function tryToGetDuffelPaymentsCustomElement(
|
|
76
|
+
caller: string
|
|
77
|
+
): DuffelPaymentsCustomElement {
|
|
78
|
+
const element =
|
|
79
|
+
document.querySelector<DuffelPaymentsCustomElement>(CUSTOM_ELEMENT_TAG);
|
|
80
|
+
if (!element) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Could not find duffel-payments element in the DOM. Maybe you need to call ${caller} after 'window.onload'?`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return element;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function renderDuffelPaymentsCustomElement(
|
|
89
|
+
props: DuffelPaymentsCustomElementRenderArguments
|
|
90
|
+
) {
|
|
91
|
+
const element = tryToGetDuffelPaymentsCustomElement(
|
|
92
|
+
"renderDuffelPaymentsCustomElement"
|
|
93
|
+
);
|
|
94
|
+
element.render(props);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function onDuffelPaymentsSuccessfulPayment(
|
|
98
|
+
onSuccessfulPayment: DuffelPaymentsProps["onSuccessfulPayment"]
|
|
99
|
+
) {
|
|
100
|
+
const element = tryToGetDuffelPaymentsCustomElement(
|
|
101
|
+
"onDuffelPaymentsPayloadReady"
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// using `as EventListener` here because typescript doesn't know the event type for `onPayloadReady`
|
|
105
|
+
// There's a few different suggestions to resolve this seemed good enough
|
|
106
|
+
// You can learn more here: https://github.com/microsoft/TypeScript/issues/28357
|
|
107
|
+
element.addEventListener(
|
|
108
|
+
"onPayloadReady",
|
|
109
|
+
onSuccessfulPayment as EventListener
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type OnFailedPaymentCustomEvent = CustomEvent<{
|
|
114
|
+
error: StripeError;
|
|
115
|
+
}>;
|
|
116
|
+
export function onDuffelPaymentsFailedPayment(
|
|
117
|
+
onFailedPayment: DuffelPaymentsProps["onFailedPayment"]
|
|
118
|
+
) {
|
|
119
|
+
const element = tryToGetDuffelPaymentsCustomElement(
|
|
120
|
+
"onDuffelPaymentsPayloadReady"
|
|
121
|
+
);
|
|
122
|
+
const eventListener = (event: OnFailedPaymentCustomEvent) => {
|
|
123
|
+
onFailedPayment(event.detail.error);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// using `as EventListener` here because typescript doesn't know the event type for `onPayloadReady`
|
|
127
|
+
// There's a few different suggestions to resolve this seemed good enough
|
|
128
|
+
// You can learn more here: https://github.com/microsoft/TypeScript/issues/28357
|
|
129
|
+
element.addEventListener("onPayloadReady", eventListener as EventListener);
|
|
130
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Fuse from "fuse.js";
|
|
2
|
+
import { debounce } from "lodash";
|
|
3
|
+
import fetch from "node-fetch";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Icon } from "../shared/Icon";
|
|
6
|
+
|
|
7
|
+
const DATA_SOURCE = "https://assets.duffel.com/data/iata-lookup-v2.json";
|
|
8
|
+
|
|
9
|
+
interface City {
|
|
10
|
+
type: "city";
|
|
11
|
+
name: string;
|
|
12
|
+
iata_code: string;
|
|
13
|
+
}
|
|
14
|
+
interface Airport {
|
|
15
|
+
type: "airport";
|
|
16
|
+
name: string;
|
|
17
|
+
iata_code: string;
|
|
18
|
+
latitude: string;
|
|
19
|
+
longitude: string;
|
|
20
|
+
}
|
|
21
|
+
type Place = City | Airport;
|
|
22
|
+
|
|
23
|
+
const mapDataRowsIntoObjects = (data: string[]): Place[] =>
|
|
24
|
+
data.map((row) => {
|
|
25
|
+
const [type, name, iata_code, latitude, longitude] = row.split(",");
|
|
26
|
+
if (type === "C") return { type: "city", name, iata_code } as City;
|
|
27
|
+
else
|
|
28
|
+
return {
|
|
29
|
+
type: "airport",
|
|
30
|
+
name,
|
|
31
|
+
iata_code,
|
|
32
|
+
latitude,
|
|
33
|
+
longitude,
|
|
34
|
+
} as Airport;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export interface PlacesLookupProps {
|
|
38
|
+
onPlaceSelected: (selection: Place) => void;
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const PlacesLookup: React.FC<PlacesLookupProps> = ({
|
|
43
|
+
onPlaceSelected,
|
|
44
|
+
placeholder = "Lookup city or airport",
|
|
45
|
+
}) => {
|
|
46
|
+
const [shouldShowPopover, setShouldShowPopover] =
|
|
47
|
+
React.useState<boolean>(true);
|
|
48
|
+
const [inputValue, setInputValue] = React.useState<string>("");
|
|
49
|
+
const [lookupData, setLookupData] = React.useState<Fuse<Place>>(new Fuse([]));
|
|
50
|
+
const [lookupResults, setLookupResults] = React.useState<
|
|
51
|
+
Fuse.FuseResult<Place>[]
|
|
52
|
+
>([]);
|
|
53
|
+
|
|
54
|
+
const runLookup = debounce((newInputValue: string) => {
|
|
55
|
+
setLookupResults(
|
|
56
|
+
lookupData.search(newInputValue, {
|
|
57
|
+
limit: 10,
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
}, 300);
|
|
61
|
+
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
fetch(DATA_SOURCE)
|
|
64
|
+
.then((response) => response.json())
|
|
65
|
+
.then((data) =>
|
|
66
|
+
setLookupData(
|
|
67
|
+
new Fuse<Place>(mapDataRowsIntoObjects(data), {
|
|
68
|
+
threshold: 0.2,
|
|
69
|
+
keys: ["name", "iata_code"],
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="places-lookup">
|
|
77
|
+
<input
|
|
78
|
+
className="places-lookup-input"
|
|
79
|
+
placeholder={placeholder}
|
|
80
|
+
type="text"
|
|
81
|
+
value={inputValue}
|
|
82
|
+
onChange={(e) => {
|
|
83
|
+
if (!shouldShowPopover) setShouldShowPopover(true);
|
|
84
|
+
setInputValue(e.target.value);
|
|
85
|
+
runLookup(e.target.value);
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
{shouldShowPopover &&
|
|
89
|
+
inputValue.length > 0 &&
|
|
90
|
+
lookupResults.length > 0 && (
|
|
91
|
+
<div
|
|
92
|
+
className="places-lookup-popover"
|
|
93
|
+
style={{ display: "flex", flexDirection: "column" }}
|
|
94
|
+
>
|
|
95
|
+
{lookupResults.map(({ item }, index) => (
|
|
96
|
+
<button
|
|
97
|
+
className="places-lookup-popover__item"
|
|
98
|
+
key={item.iata_code + index}
|
|
99
|
+
onClick={() => {
|
|
100
|
+
setShouldShowPopover(false);
|
|
101
|
+
onPlaceSelected(item);
|
|
102
|
+
setInputValue(item.name);
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<span className="places-lookup-popover__icon-and-name-container">
|
|
106
|
+
<Icon
|
|
107
|
+
style={{ fill: "var(--GREY-400)", marginTop: "-2px" }}
|
|
108
|
+
name={
|
|
109
|
+
item.type === "airport" ? "flight_takeoff" : "apartment"
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
{item.name}
|
|
113
|
+
</span>{" "}
|
|
114
|
+
<span style={{ color: "var(--GREY-600)", fontSize: "12px" }}>
|
|
115
|
+
{item.iata_code}
|
|
116
|
+
</span>
|
|
117
|
+
</button>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Button } from "@components/shared/Button";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
function isJsonString(dataString: string) {
|
|
5
|
+
try {
|
|
6
|
+
JSON.parse(dataString);
|
|
7
|
+
} catch (e) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ShowDataProps {
|
|
14
|
+
data: any;
|
|
15
|
+
onFinished: (newData: any) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ShowData: React.FC<ShowDataProps> = ({ data, onFinished }) => {
|
|
19
|
+
const [textareaData, setTextareaData] = React.useState("{}");
|
|
20
|
+
const isValid = isJsonString(textareaData);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
25
|
+
<textarea
|
|
26
|
+
value={textareaData}
|
|
27
|
+
onChange={(e) => setTextareaData(e.target.value)}
|
|
28
|
+
/>
|
|
29
|
+
<Button
|
|
30
|
+
disabled={!isValid}
|
|
31
|
+
onClick={() => onFinished(JSON.parse(textareaData))}
|
|
32
|
+
>
|
|
33
|
+
Click me to trigger `onFinished` event
|
|
34
|
+
</Button>
|
|
35
|
+
{!isValid && <p style={{ color: "tomato" }}>Invalid JSON</p>}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createRoot, Root } from "react-dom/client";
|
|
2
|
+
import { ShowData, ShowDataProps } from "./ShowData";
|
|
3
|
+
declare global {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
5
|
+
namespace JSX {
|
|
6
|
+
interface IntrinsicElements {
|
|
7
|
+
"show-data": React.DetailedHTMLProps<
|
|
8
|
+
React.HTMLAttributes<HTMLElement>,
|
|
9
|
+
HTMLElement
|
|
10
|
+
>;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CUSTOM_ELEMENT_TAG = "show-data";
|
|
16
|
+
|
|
17
|
+
type ShowDataRenderArguments = Pick<ShowDataProps, "data">;
|
|
18
|
+
|
|
19
|
+
class ShowDataCustomElement extends HTMLElement {
|
|
20
|
+
/**
|
|
21
|
+
* The React root for displaying content inside a browser DOM element.
|
|
22
|
+
*/
|
|
23
|
+
private root!: Root;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `connectedCallback` is called to initialise the custom element
|
|
27
|
+
*/
|
|
28
|
+
connectedCallback() {
|
|
29
|
+
const container = document.createElement("div");
|
|
30
|
+
this.appendChild(container);
|
|
31
|
+
|
|
32
|
+
this.root = createRoot(container);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* When this function is called, it will render/re-render
|
|
37
|
+
* the `DuffelPayments` component with the given props.
|
|
38
|
+
*/
|
|
39
|
+
public render(withProps: ShowDataRenderArguments) {
|
|
40
|
+
if (!this.root) {
|
|
41
|
+
throw "It was not possible to render `duffel-payments` because `this.root` is missing.";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.root.render(
|
|
45
|
+
<ShowData
|
|
46
|
+
{...withProps}
|
|
47
|
+
onFinished={() => {
|
|
48
|
+
this.dispatchEvent(
|
|
49
|
+
new CustomEvent("onFinished", {
|
|
50
|
+
composed: true,
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window.customElements.get(CUSTOM_ELEMENT_TAG) ||
|
|
60
|
+
window.customElements.define(CUSTOM_ELEMENT_TAG, ShowDataCustomElement);
|
|
61
|
+
|
|
62
|
+
function tryToGetShowData(caller: string): ShowDataCustomElement {
|
|
63
|
+
const element =
|
|
64
|
+
document.querySelector<ShowDataCustomElement>(CUSTOM_ELEMENT_TAG);
|
|
65
|
+
if (!element) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Could not find duffel-payments element in the DOM. Maybe you need to call ${caller} after 'window.onload'?`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return element;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function renderShowData(props: ShowDataRenderArguments) {
|
|
74
|
+
const element = tryToGetShowData("renderShowData");
|
|
75
|
+
element.render(props);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function onShowDataFinished(onFinished: ShowDataProps["onFinished"]) {
|
|
79
|
+
const element = tryToGetShowData("onShowDataFinished");
|
|
80
|
+
|
|
81
|
+
// using `as EventListener` here because typescript doesn't know the event type for `onPayloadReady`
|
|
82
|
+
// There's a few different suggestions to resolve this seemed good enough
|
|
83
|
+
// You can learn more here: https://github.com/microsoft/TypeScript/issues/28357
|
|
84
|
+
element.addEventListener("onFinished", onFinished as EventListener);
|
|
85
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Icon, IconName } from "@components/Icon";
|
|
1
|
+
import { Icon, IconName } from "@components/shared/Icon";
|
|
2
2
|
import classNames from "classnames";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
@@ -25,7 +25,7 @@ type NativeButtonProps = React.DetailedHTMLProps<
|
|
|
25
25
|
export interface ButtonProps
|
|
26
26
|
extends Pick<
|
|
27
27
|
NativeButtonProps,
|
|
28
|
-
"id" | "onClick" | "disabled" | "children" | "className"
|
|
28
|
+
"id" | "onClick" | "disabled" | "children" | "className" | "type"
|
|
29
29
|
> {
|
|
30
30
|
"data-testid"?: string;
|
|
31
31
|
iconBefore?: IconName;
|
|
@@ -39,10 +39,11 @@ export const Button: React.FC<ButtonProps> = ({
|
|
|
39
39
|
size = 40,
|
|
40
40
|
children,
|
|
41
41
|
className,
|
|
42
|
+
type = "button",
|
|
42
43
|
...nativeButtonProps
|
|
43
44
|
}) => (
|
|
44
45
|
<button
|
|
45
|
-
type=
|
|
46
|
+
type={type}
|
|
46
47
|
className={classNames(
|
|
47
48
|
"button",
|
|
48
49
|
BUTTON_VARIANTS[variant],
|
|
@@ -13,9 +13,9 @@ export class ErrorBoundary extends React.Component<{
|
|
|
13
13
|
return { hasError: true };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
componentDidCatch(error: Error
|
|
16
|
+
componentDidCatch(error: Error) {
|
|
17
17
|
// You can also log the error to an error reporting service
|
|
18
|
-
captureErrorInSentry(error
|
|
18
|
+
captureErrorInSentry(error);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
render() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from "@lib/logging";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
|
|
3
4
|
/* eslint-disable react/no-unknown-property */
|
|
@@ -6,6 +7,9 @@ export const ICON_MAP = {
|
|
|
6
7
|
add: (
|
|
7
8
|
<path d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z" />
|
|
8
9
|
),
|
|
10
|
+
apartment: (
|
|
11
|
+
<path d="M3.8496 19.9752V7.2744H7.4496V3.6744H15.9504V10.8744H20.1504V19.9752H13.4496V16.3752H10.5504V19.9752H3.8496ZM5.1504 18.6744H7.4496V16.3752H5.1504V18.6744ZM5.1504 14.7744H7.4496V12.4752H5.1504V14.7744ZM5.1504 10.8744H7.4496V8.5752H5.1504V10.8744ZM8.7504 14.7744H11.0496V12.4752H8.7504V14.7744ZM8.7504 10.8744H11.0496V8.5752H8.7504V10.8744ZM8.7504 7.2744H11.0496V4.9752H8.7504V7.2744ZM12.3504 14.7744H14.6496V12.4752H12.3504V14.7744ZM12.3504 10.8744H14.6496V8.5752H12.3504V10.8744ZM12.3504 7.2744H14.6496V4.9752H12.3504V7.2744ZM16.5504 18.6744H18.8496V16.3752H16.5504V18.6744ZM16.5504 14.7744H18.8496V12.4752H16.5504V14.7744Z" />
|
|
12
|
+
),
|
|
9
13
|
arrow_forward: (
|
|
10
14
|
<path d="M5 13h11.17l-4.88 4.88c-.39.39-.39 1.03 0 1.42.39.39 1.02.39 1.41 0l6.59-6.59c.39-.39.39-1.02 0-1.41l-6.58-6.6c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L16.17 11H5c-.55 0-1 .45-1 1s.45 1 1 1z" />
|
|
11
15
|
),
|
|
@@ -15,6 +19,7 @@ export const ICON_MAP = {
|
|
|
15
19
|
arrow_right: (
|
|
16
20
|
<path d="M15 19L13.6 17.6L18.2 13H2V11H18.2L13.6 6.4L15 5L22 12L15 19Z" />
|
|
17
21
|
),
|
|
22
|
+
|
|
18
23
|
autorenew: (
|
|
19
24
|
<path d="M5.69728 14.4104C5.49326 13.9833 5.32604 13.5257 5.19562 13.0374C5.06521 12.5492 5 12.0367 5 11.5C5 9.56481 5.69083 7.91679 7.07249 6.55591C8.45416 5.19504 10.147 4.53946 12.1511 4.58918H12.7613L11.0742 2.92359L12.0097 2L15.2869 5.23549L12.0097 8.47099L11.0742 7.54739L12.7613 5.88181H12.1511C10.5021 5.84866 9.11624 6.38632 7.99347 7.49479C6.87069 8.60326 6.30931 9.93833 6.30931 11.5C6.30931 11.8455 6.33804 12.1824 6.3955 12.5106C6.45296 12.8389 6.53915 13.1573 6.65407 13.4658L5.69728 14.4104ZM11.9903 21L8.71309 17.7645L11.9903 14.529L12.9258 15.4526L11.2387 17.1182H11.8489C13.4979 17.1513 14.8838 16.6137 16.0065 15.5052C17.1293 14.3967 17.6907 13.0617 17.6907 11.5C17.6907 11.1545 17.662 10.8176 17.6045 10.4894C17.547 10.1611 17.4609 9.84273 17.3459 9.53421L18.3027 8.58959C18.5067 9.01665 18.674 9.47431 18.8044 9.96256C18.9348 10.4508 19 10.9633 19 11.5C19 13.4186 18.3092 15.0625 16.9275 16.4317C15.5458 17.8008 13.853 18.4605 11.8489 18.4108H11.2387L12.9258 20.0764L11.9903 21Z" />
|
|
20
25
|
),
|
|
@@ -52,6 +57,9 @@ export const ICON_MAP = {
|
|
|
52
57
|
flight_class: (
|
|
53
58
|
<path d="M14.2596 12.5C13.7737 12.5 13.3606 12.3298 13.0202 11.9894C12.6798 11.649 12.5096 11.2359 12.5096 10.75V6.25C12.5096 5.7641 12.6798 5.35096 13.0202 5.01058C13.3606 4.67019 13.7737 4.5 14.2596 4.5H15.75C16.2359 4.5 16.649 4.67019 16.9894 5.01058C17.3298 5.35096 17.5 5.7641 17.5 6.25V10.75C17.5 11.2359 17.3298 11.649 16.9894 11.9894C16.649 12.3298 16.2359 12.5 15.75 12.5H14.2596ZM14.2596 11H15.75C15.8205 11 15.8798 10.9759 15.9278 10.9279C15.9759 10.8798 16 10.8205 16 10.75V6.25C16 6.17948 15.9759 6.12018 15.9278 6.0721C15.8798 6.02402 15.8205 5.99998 15.75 5.99998H14.2596C14.1891 5.99998 14.1298 6.02402 14.0817 6.0721C14.0336 6.12018 14.0096 6.17948 14.0096 6.25V10.75C14.0096 10.8205 14.0336 10.8798 14.0817 10.9279C14.1298 10.9759 14.1891 11 14.2596 11ZM9.5673 17.5C9.21345 17.5 8.89678 17.398 8.6173 17.1942C8.33782 16.9903 8.14423 16.7198 8.03655 16.3827L5.5 8.02883V4.5H6.99997V7.99998L9.49997 16H17.7596V17.5H9.5673ZM8.25 20.5V19H17.75V20.5H8.25ZM14.2596 5.99998H16H14.0096H14.2596Z" />
|
|
54
59
|
),
|
|
60
|
+
flight_takeoff: (
|
|
61
|
+
<path d="M3.77521 20.1498V18.849H20.0748V20.1498H3.77521ZM5.45041 15.2742L2.94961 11.0994L4.05001 10.8246L5.90041 12.399L10.05 11.2998L6.27481 4.89902L7.72561 4.62422L14.1 10.1994L19.4004 8.77382C19.75 8.67462 20.0832 8.72062 20.4 8.91182C20.7168 9.10382 20.9252 9.37462 21.0252 9.72422C21.1084 10.0746 21.0624 10.4038 20.8872 10.7118C20.7128 11.0198 20.4504 11.2242 20.1 11.325L5.45041 15.2742Z" />
|
|
62
|
+
),
|
|
55
63
|
galley: (
|
|
56
64
|
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z" />
|
|
57
65
|
),
|
|
@@ -68,20 +76,12 @@ export const ICON_MAP = {
|
|
|
68
76
|
<path d="M14.6674 10.0001V2.66675C14.6674 1.56008 13.7741 0.666748 12.6674 0.666748C11.5607 0.666748 10.6674 1.56008 10.6674 2.66675V7.57341L21.1074 18.0134L25.3341 19.3334V16.6667L14.6674 10.0001ZM1.33407 5.02675L7.9874 11.6801L0.000732422 16.6667V19.3334L10.6674 16.0001V23.3334L8.00073 25.3334V27.3334L12.6674 26.0001L17.3341 27.3334V25.3334L14.6674 23.3334V18.3601L22.3074 26.0001L24.0007 24.3067L3.0274 3.33341L1.33407 5.02675Z" />
|
|
69
77
|
),
|
|
70
78
|
no_bag: (
|
|
71
|
-
<path
|
|
72
|
-
fill-rule="evenodd"
|
|
73
|
-
clip-rule="evenodd"
|
|
74
|
-
d="M16.8992 6.12037H14.9392V3.18101C14.9385 2.92148 14.8351 2.67278 14.6516 2.48926C14.4681 2.30574 14.2194 2.20234 13.9599 2.20166H10.0399C9.78034 2.20234 9.53163 2.30574 9.34811 2.48926C9.16459 2.67278 9.06119 2.92148 9.06051 3.18101V6.12037H8.20711L8.34715 6.25804L14.3973 12.0871V9.06102H15.9676V13.6L17.3022 14.8859L18.8592 16.4748V8.08037C18.8579 7.56096 18.6509 7.06322 18.2836 6.69594C17.9164 6.32867 17.4186 6.12173 16.8992 6.12037ZM5.14051 8.09559L3.77173 6.71825L5.00821 5.48945L7.16586 7.6606L13.9581 14.4953L15.8932 16.4426L19.7441 20.3175L20.2279 20.8043L18.9914 22.0331L17.635 20.6683C17.4035 20.7625 17.154 20.8126 16.8992 20.8133C16.904 20.945 16.8822 21.0763 16.8351 21.1993C16.788 21.3224 16.7166 21.4347 16.6251 21.5296C16.5336 21.6244 16.424 21.6999 16.3027 21.7514C16.1814 21.8029 16.051 21.8295 15.9192 21.8295C15.7874 21.8295 15.657 21.8029 15.5357 21.7514C15.4145 21.6999 15.3048 21.6244 15.2133 21.5296C15.1219 21.4347 15.0504 21.3224 15.0034 21.1993C14.9563 21.0763 14.9344 20.945 14.9392 20.8133H9.06051C9.06529 20.945 9.04348 21.0763 8.99638 21.1993C8.94929 21.3224 8.87787 21.4347 8.7864 21.5296C8.69492 21.6244 8.58527 21.6999 8.46399 21.7514C8.34271 21.8029 8.21229 21.8295 8.08051 21.8295C7.94874 21.8295 7.81832 21.8029 7.69703 21.7514C7.57575 21.6999 7.4661 21.6244 7.37463 21.5296C7.28316 21.4347 7.21174 21.3224 7.16464 21.1993C7.11754 21.0763 7.09573 20.945 7.10051 20.8133C6.58222 20.8119 6.08545 20.6059 5.71835 20.24C5.35126 19.8741 5.14357 19.378 5.14051 18.8597V8.09559ZM14.8632 17.8791L14.3973 17.4103V17.8791H14.8632ZM9.596 12.579V17.8791H8.03083V11.004L9.596 12.579ZM10.5302 3.67134H13.4695V6.12295H10.5302V3.67134Z"
|
|
75
|
-
/>
|
|
79
|
+
<path d="M16.8992 6.12037H14.9392V3.18101C14.9385 2.92148 14.8351 2.67278 14.6516 2.48926C14.4681 2.30574 14.2194 2.20234 13.9599 2.20166H10.0399C9.78034 2.20234 9.53163 2.30574 9.34811 2.48926C9.16459 2.67278 9.06119 2.92148 9.06051 3.18101V6.12037H8.20711L8.34715 6.25804L14.3973 12.0871V9.06102H15.9676V13.6L17.3022 14.8859L18.8592 16.4748V8.08037C18.8579 7.56096 18.6509 7.06322 18.2836 6.69594C17.9164 6.32867 17.4186 6.12173 16.8992 6.12037ZM5.14051 8.09559L3.77173 6.71825L5.00821 5.48945L7.16586 7.6606L13.9581 14.4953L15.8932 16.4426L19.7441 20.3175L20.2279 20.8043L18.9914 22.0331L17.635 20.6683C17.4035 20.7625 17.154 20.8126 16.8992 20.8133C16.904 20.945 16.8822 21.0763 16.8351 21.1993C16.788 21.3224 16.7166 21.4347 16.6251 21.5296C16.5336 21.6244 16.424 21.6999 16.3027 21.7514C16.1814 21.8029 16.051 21.8295 15.9192 21.8295C15.7874 21.8295 15.657 21.8029 15.5357 21.7514C15.4145 21.6999 15.3048 21.6244 15.2133 21.5296C15.1219 21.4347 15.0504 21.3224 15.0034 21.1993C14.9563 21.0763 14.9344 20.945 14.9392 20.8133H9.06051C9.06529 20.945 9.04348 21.0763 8.99638 21.1993C8.94929 21.3224 8.87787 21.4347 8.7864 21.5296C8.69492 21.6244 8.58527 21.6999 8.46399 21.7514C8.34271 21.8029 8.21229 21.8295 8.08051 21.8295C7.94874 21.8295 7.81832 21.8029 7.69703 21.7514C7.57575 21.6999 7.4661 21.6244 7.37463 21.5296C7.28316 21.4347 7.21174 21.3224 7.16464 21.1993C7.11754 21.0763 7.09573 20.945 7.10051 20.8133C6.58222 20.8119 6.08545 20.6059 5.71835 20.24C5.35126 19.8741 5.14357 19.378 5.14051 18.8597V8.09559ZM14.8632 17.8791L14.3973 17.4103V17.8791H14.8632ZM9.596 12.579V17.8791H8.03083V11.004L9.596 12.579ZM10.5302 3.67134H13.4695V6.12295H10.5302V3.67134Z" />
|
|
76
80
|
),
|
|
77
81
|
no_seat: (
|
|
78
82
|
<>
|
|
79
83
|
<path d="M25.1667 21.9733L23.4733 23.6667L2.5 2.69333L4.19333 1L7.16667 3.97333L16.5267 13.3333L19.1933 16L24.5 21.3067L25.1667 21.9733Z" />
|
|
80
|
-
<path
|
|
81
|
-
fillRule="evenodd"
|
|
82
|
-
clipRule="evenodd"
|
|
83
|
-
d="M7.16667 24H3.16667V16H19.1933L24.5 21.3067V24H20.5V20H7.16667V24ZM27.1667 9.33333H23.1667V13.3333H27.1667V9.33333ZM4.5 9.33333H0.5V13.3333H4.5V9.33333ZM20.3333 13.3333H20.5V2.66667C20.5 1.2 19.3 0 17.8333 0H9.83333C9.05784 0 8.3569 0.335483 7.86848 0.868484L20.3333 13.3333ZM16.5267 13.3333L7.16667 3.97333V13.3333H16.5267Z"
|
|
84
|
-
/>
|
|
84
|
+
<path d="M7.16667 24H3.16667V16H19.1933L24.5 21.3067V24H20.5V20H7.16667V24ZM27.1667 9.33333H23.1667V13.3333H27.1667V9.33333ZM4.5 9.33333H0.5V13.3333H4.5V9.33333ZM20.3333 13.3333H20.5V2.66667C20.5 1.2 19.3 0 17.8333 0H9.83333C9.05784 0 8.3569 0.335483 7.86848 0.868484L20.3333 13.3333ZM16.5267 13.3333L7.16667 3.97333V13.3333H16.5267Z" />
|
|
85
85
|
</>
|
|
86
86
|
),
|
|
87
87
|
north_east: (
|
|
@@ -109,7 +109,7 @@ export type IconName = keyof typeof ICON_MAP;
|
|
|
109
109
|
|
|
110
110
|
const getIconPath = (name: IconName) => {
|
|
111
111
|
if (!(name in ICON_MAP)) {
|
|
112
|
-
|
|
112
|
+
log(`The icon "${name}" is missing from ICON_MAP`);
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
return ICON_MAP[name];
|
|
@@ -16,7 +16,11 @@ export const Modal: React.FC<ModalProps> = ({ children, onClose, isOpen }) => {
|
|
|
16
16
|
}, [isOpen]);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
className={classNames("modal", isOpen && "modal--open")}
|
|
21
|
+
// setting inline style to avoid modal content to flash unstyled before stylesheet is loaded
|
|
22
|
+
style={{ opacity: 0 }}
|
|
23
|
+
>
|
|
20
24
|
<div role="presentation" className={"modal--content"}>
|
|
21
25
|
{children}
|
|
22
26
|
|
package/src/custom-elements.ts
CHANGED
|
@@ -5,4 +5,9 @@
|
|
|
5
5
|
export {
|
|
6
6
|
onDuffelAncillariesPayloadReady,
|
|
7
7
|
renderDuffelAncillariesCustomElement,
|
|
8
|
-
} from "./components/DuffelAncillariesCustomElement";
|
|
8
|
+
} from "./components/DuffelAncillaries/DuffelAncillariesCustomElement";
|
|
9
|
+
export {
|
|
10
|
+
onDuffelPaymentsFailedPayment,
|
|
11
|
+
onDuffelPaymentsSuccessfulPayment,
|
|
12
|
+
renderDuffelPaymentsCustomElement,
|
|
13
|
+
} from "./components/DuffelPayments/DuffelPaymentsCustomElement";
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
/>
|
|
12
12
|
|
|
13
13
|
<script src="../dist/index.js" defer></script>
|
|
14
|
-
<title>
|
|
14
|
+
<title>ancillaries component just typescript example</title>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<h1 style="margin-bottom: 2rem">
|
|
18
|
-
Duffel ancillaries component
|
|
18
|
+
Duffel ancillaries component just typescript example
|
|
19
19
|
</h1>
|
|
20
20
|
|
|
21
21
|
<duffel-ancillaries></duffel-ancillaries>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
onDuffelAncillariesPayloadReady,
|
|
3
3
|
renderDuffelAncillariesCustomElement,
|
|
4
|
-
} from "duffel-components";
|
|
4
|
+
} from "duffel-components/custom-elements";
|
|
5
5
|
|
|
6
6
|
window.onload = () => {
|
|
7
7
|
renderDuffelAncillariesCustomElement({
|
|
@@ -29,6 +29,7 @@ window.onload = () => {
|
|
|
29
29
|
],
|
|
30
30
|
});
|
|
31
31
|
onDuffelAncillariesPayloadReady((data, metadata) => {
|
|
32
|
+
/* eslint-disable */
|
|
32
33
|
console.table(data);
|
|
33
34
|
console.table(metadata);
|
|
34
35
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Duffel payments custom element example
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
# .env.local
|
|
7
|
+
|
|
8
|
+
# The url for the component CDN.
|
|
9
|
+
# This is used to load both the styles an
|
|
10
|
+
# COMPONENT_CDN=https://assets.duffel.com/components/ancillaries/VERSION # production
|
|
11
|
+
COMPONENT_CDN=http://localhost:8000 # development
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Run the example
|
|
15
|
+
|
|
16
|
+
1. Serve the Duffel component bundle and watch for changes to rebuild on port `8000` using `yarn dev`.
|
|
17
|
+
2. Open `src/examples/payments-custom-element/index.html` on your browser.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Payment page example</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link
|
|
7
|
+
rel="icon"
|
|
8
|
+
type="image/png"
|
|
9
|
+
sizes="96x96"
|
|
10
|
+
href="https://duffel.com/images/favicon/favicon-96x96.png"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<!-- 1. This script loads duffel-components -->
|
|
14
|
+
<script src="http://localhost:8000/duffel-payments.js"></script>
|
|
15
|
+
</head>
|
|
16
|
+
|
|
17
|
+
<body style="font-family: sans-serif">
|
|
18
|
+
<h1 style="margin-bottom: 2rem">Payment page example</h1>
|
|
19
|
+
|
|
20
|
+
<!-- 2. Add the custom element to your markup where you want to render the payments card -->
|
|
21
|
+
<duffel-payments></duffel-payments>
|
|
22
|
+
</body>
|
|
23
|
+
<script>
|
|
24
|
+
const duffelpaymentsElement = document.querySelector("duffel-payments");
|
|
25
|
+
|
|
26
|
+
// 3. Render the component with the required data, you can safely call this function as many times as you want.
|
|
27
|
+
duffelpaymentsElement.render({
|
|
28
|
+
paymentIntentClientToken:
|
|
29
|
+
"eyJjbGllbnRfc2VjcmV0IjoicGlfM0psczlVQWcySmhFeTh2WTBSTm1MU0JkX3NlY3JldF9QUW9yZXNuU3laeWJadGRiejZwNzBCbUdPIiwicHVibGlzaGFibGVfa2V5IjoicGtfdGVzdF9EQUJLY0E2Vzh6OTc0cTdPSWY0YmJ2MVQwMEpwRmMyOUpWIn0=",
|
|
30
|
+
debug: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 4. Listen to 'onSuccessfulPayment' event on the component:
|
|
34
|
+
duffelpaymentsElement.addEventListener("onSuccessfulPayment", () =>
|
|
35
|
+
console.log("onPayloadReady\n")
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// 5. If we run into an issue with the payment, you can listen to 'onFailedPayment' event:
|
|
39
|
+
duffelpaymentsElement.addEventListener("onFailedPayment", (event) =>
|
|
40
|
+
console.log("onPayloadReady\n", event.detail)
|
|
41
|
+
);
|
|
42
|
+
</script>
|
|
43
|
+
</html>
|