@duffel/components 3.0.0-canary → 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/.eslintrc.js +14 -1
- package/.github/workflows/release.yml +4 -1
- package/.storybook/__snapshots__/Storyshots.test.js.snap +185 -0
- package/CONTRIBUTING.md +83 -0
- package/README.md +34 -109
- package/config/esbuild.base.config.js +7 -3
- package/config/esbuild.cdn.config.js +4 -3
- package/config/esbuild.dev.config.js +4 -3
- package/config/esbuild.react.config.js +3 -3
- package/package.json +16 -5
- package/react-dist/components/{Card.d.ts → DuffelAncillaries/Card.d.ts} +1 -1
- package/react-dist/components/{DuffelAncillaries.d.ts → DuffelAncillaries/DuffelAncillaries.d.ts} +1 -1
- package/react-dist/components/{DuffelAncillariesCustomElement.d.ts → DuffelAncillaries/DuffelAncillariesCustomElement.d.ts} +1 -1
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.d.ts +2 -2
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.d.ts +2 -2
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.d.ts +2 -2
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.d.ts +2 -2
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.d.ts +2 -2
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.d.ts +1 -1
- package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.d.ts +1 -1
- package/react-dist/components/{bags → DuffelAncillaries/bags}/IncludedBaggageBanner.d.ts +1 -1
- package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionCard.d.ts +2 -2
- package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModal.d.ts +2 -2
- package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBody.d.ts +1 -1
- package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalFooter.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/Amenity.d.ts +1 -1
- package/react-dist/components/{seats → DuffelAncillaries/seats}/Element.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/Legend.d.ts +1 -1
- package/react-dist/components/{seats → DuffelAncillaries/seats}/Row.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/RowSection.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatElement.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatInfo.d.ts +1 -1
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatMap.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionCard.d.ts +3 -3
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModal.d.ts +3 -3
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.d.ts +2 -2
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.d.ts +1 -1
- package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.d.ts +1 -1
- package/react-dist/components/DuffelPayments/DuffelPayments.d.ts +11 -0
- package/react-dist/components/DuffelPayments/DuffelPaymentsCustomElement.d.ts +14 -0
- package/react-dist/components/{Button.d.ts → shared/Button.d.ts} +2 -2
- package/react-dist/components/{ErrorBoundary.d.ts → shared/ErrorBoundary.d.ts} +1 -1
- package/react-dist/components/{IconButton.d.ts → shared/IconButton.d.ts} +1 -1
- package/react-dist/components/{NonIdealState.d.ts → shared/NonIdealState.d.ts} +1 -1
- 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 +4 -5
- package/react-dist/index.js +21 -20
- package/react-dist/index.js.map +4 -4
- package/react-dist/lib/captureErrorInSentry.d.ts +1 -1
- package/react-dist/lib/fetchFromDuffelAPI.d.ts +7 -0
- package/react-dist/lib/logging.d.ts +7 -14
- package/react-dist/lib/retrieveSeatMaps.d.ts +1 -1
- package/react-dist/types/DuffelAncillariesProps.d.ts +1 -1
- package/scripts/generate-fixture.ts +13 -8
- package/scripts/upload-to-cdn.sh +4 -9
- 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} +61 -63
- package/src/components/{DuffelAncillariesCustomElement.tsx → DuffelAncillaries/DuffelAncillariesCustomElement.tsx} +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.tsx +4 -4
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.tsx +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.tsx +3 -3
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.tsx +3 -3
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.tsx +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.tsx +2 -2
- package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.tsx +1 -1
- 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 +4 -4
- 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 +2 -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 +4 -4
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.tsx +3 -3
- package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.tsx +1 -1
- package/src/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.tsx +2 -2
- package/src/components/DuffelPayments/DuffelPayments.tsx +218 -0
- package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -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} +2 -1
- 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 +13 -0
- package/src/examples/client-side/README.md +30 -0
- package/src/examples/client-side/index.html +1 -1
- package/src/examples/full-stack/README.md +34 -0
- 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/payments-just-typescript/yarn.lock +154 -0
- package/src/examples/react-app/src/index.tsx +11 -6
- package/src/index.ts +4 -5
- 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 +2 -4
- package/src/lib/getTotalAmountForServices.ts +1 -1
- package/src/lib/logging.ts +52 -32
- package/src/lib/retrieveOffer.ts +11 -6
- package/src/lib/retrieveSeatMaps.ts +13 -8
- package/src/stories/BaggageSelectionModalHeader.stories.tsx +1 -1
- package/src/stories/Button.stories.tsx +12 -3
- package/src/stories/DuffelAncillaries.stories.tsx +1 -1
- 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/styles/components/DuffelPayments.css +42 -0
- package/src/styles/components/Modal.css +2 -1
- package/src/styles/global.css +1 -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/tsconfig.json +1 -1
- /package/react-dist/components/{Counter.d.ts → DuffelAncillaries/Counter.d.ts} +0 -0
- /package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBodyListItem.d.ts +0 -0
- /package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalHeader.d.ts +0 -0
- /package/react-dist/components/{seats → DuffelAncillaries/seats}/DeckSelect.d.ts +0 -0
- /package/react-dist/components/{seats → DuffelAncillaries/seats}/EmptyElement.d.ts +0 -0
- /package/react-dist/components/{seats → DuffelAncillaries/seats}/ExitElement.d.ts +0 -0
- /package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatMapUnavailable.d.ts +0 -0
- /package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.d.ts +0 -0
- /package/react-dist/components/{AnimatedLoaderEllipsis.d.ts → shared/AnimatedLoaderEllipsis.d.ts} +0 -0
- /package/react-dist/components/{FetchOfferErrorState.d.ts → shared/FetchOfferErrorState.d.ts} +0 -0
- /package/react-dist/components/{Icon.d.ts → shared/Icon.d.ts} +0 -0
- /package/react-dist/components/{Modal.d.ts → shared/Modal.d.ts} +0 -0
- /package/react-dist/components/{Stamp.d.ts → shared/Stamp.d.ts} +0 -0
- /package/react-dist/components/{Tabs.d.ts → shared/Tabs.d.ts} +0 -0
- /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
package/src/lib/logging.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import
|
|
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";
|
|
2
12
|
|
|
3
13
|
/**
|
|
4
14
|
* The functions in this file are used to enable logging.
|
|
@@ -25,36 +35,37 @@ import { createContext, useContext } from "react";
|
|
|
25
35
|
* log('This is a log message')
|
|
26
36
|
* logGroup('These messages will be grouped together', ['This is a log message', 'This is another log message'])
|
|
27
37
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (debugMode) {
|
|
31
|
-
|
|
32
|
-
|
|
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`
|
|
33
50
|
);
|
|
34
51
|
}
|
|
35
|
-
|
|
36
|
-
// We return functions that do nothing because it allows consumers
|
|
37
|
-
// of this function to not have to check if the logger is enabled or not.
|
|
38
|
-
// If we returned undefined, consumers would have to do something like:
|
|
39
|
-
// if (log) { log('message') }
|
|
40
|
-
//
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
42
|
-
const noop = () => {};
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
log: debugMode ? log : noop,
|
|
46
|
-
logGroup: debugMode ? logGroup : noop,
|
|
47
|
-
};
|
|
52
|
+
LOG_INITIALISED = true;
|
|
48
53
|
};
|
|
49
54
|
|
|
50
|
-
const MESSAGE_PREFIX = "[Duffel Ancillaries] ";
|
|
51
|
-
|
|
52
55
|
/**
|
|
53
56
|
* Log a message to the console. Messages will be prefixed with "[Duffel Ancillaries]".
|
|
54
57
|
* @param message The message to print to the console.
|
|
55
58
|
*/
|
|
56
59
|
const log = (message: any) => {
|
|
57
|
-
|
|
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
|
+
}
|
|
58
69
|
};
|
|
59
70
|
|
|
60
71
|
/**
|
|
@@ -77,8 +88,6 @@ function logGroup(
|
|
|
77
88
|
groupName: string,
|
|
78
89
|
messagesOrObject: any[] | { [key: string]: any }
|
|
79
90
|
): void {
|
|
80
|
-
console.groupCollapsed(MESSAGE_PREFIX, groupName);
|
|
81
|
-
|
|
82
91
|
let transformedMessagesOrObject = [];
|
|
83
92
|
if (Array.isArray(messagesOrObject)) {
|
|
84
93
|
transformedMessagesOrObject = messagesOrObject;
|
|
@@ -87,14 +96,25 @@ function logGroup(
|
|
|
87
96
|
([key, value]) => ({ property: key, value })
|
|
88
97
|
);
|
|
89
98
|
}
|
|
90
|
-
transformedMessagesOrObject.forEach((message) => {
|
|
91
|
-
console.log(message);
|
|
92
|
-
});
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
if (shouldLog()) {
|
|
101
|
+
// eslint-disable-next-line
|
|
102
|
+
console.groupCollapsed(MESSAGE_PREFIX, groupName);
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
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
|
+
}
|
|
99
119
|
|
|
100
|
-
export {
|
|
120
|
+
export { initializeLogger, logGroup, log };
|
package/src/lib/retrieveOffer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Offer } from "../types/Offer";
|
|
2
2
|
import { captureErrorInSentry } from "./captureErrorInSentry";
|
|
3
|
+
import { isErrorResponse } from "./fetchFromDuffelAPI";
|
|
3
4
|
import { importFromOfferFixtures } from "./fetchFromFixtures";
|
|
4
5
|
import { isFixtureOfferId } from "./isFixtureOfferId";
|
|
5
6
|
import { retrieveOfferFromDuffelAPI } from "./retrieveOfferFromDuffelAPI";
|
|
@@ -24,24 +25,28 @@ export async function retrieveOffer(
|
|
|
24
25
|
|
|
25
26
|
if (!client_key) {
|
|
26
27
|
throw new Error(
|
|
27
|
-
"Attemptted to retrieve seat maps but the client key is missing"
|
|
28
|
+
"Attemptted to retrieve seat maps but the client key is missing."
|
|
28
29
|
);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
try {
|
|
32
33
|
const data = await retrieveOfferFromDuffelAPI(offer_id, client_key);
|
|
33
|
-
|
|
34
|
+
onOfferReady(data);
|
|
34
35
|
} catch (error) {
|
|
35
36
|
let message = "An unknown error occurred while retrieving the offer.";
|
|
36
37
|
if (error instanceof Error) {
|
|
37
38
|
message = error.message;
|
|
38
|
-
if (error.message.includes("
|
|
39
|
+
if (error.message.includes("Load failed")) {
|
|
39
40
|
message = "The Duffel API is not available. Please try again later.";
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
44
47
|
}
|
|
48
|
+
|
|
49
|
+
captureErrorInSentry(new Error(message));
|
|
45
50
|
onError(message);
|
|
46
51
|
} finally {
|
|
47
52
|
setIsLoading(false);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SeatMap } from "../types/SeatMap";
|
|
2
2
|
import { captureErrorInSentry } from "./captureErrorInSentry";
|
|
3
|
+
import { isErrorResponse } from "./fetchFromDuffelAPI";
|
|
3
4
|
import { importFromSeatMapsFixtures } from "./fetchFromFixtures";
|
|
4
5
|
import { isFixtureOfferId } from "./isFixtureOfferId";
|
|
5
6
|
import { retrieveSeatMapsFromDuffelAPI } from "./retrieveSeatMapsFromDuffelAPI";
|
|
@@ -7,7 +8,7 @@ import { retrieveSeatMapsFromDuffelAPI } from "./retrieveSeatMapsFromDuffelAPI";
|
|
|
7
8
|
export async function retrieveSeatMaps(
|
|
8
9
|
offer_id: string,
|
|
9
10
|
client_key: string | null,
|
|
10
|
-
onError: (
|
|
11
|
+
onError: () => void,
|
|
11
12
|
setIsLoading: (isLoading: boolean) => void,
|
|
12
13
|
onSeatMapReady: (seatMaps: SeatMap[]) => void
|
|
13
14
|
) {
|
|
@@ -25,7 +26,7 @@ export async function retrieveSeatMaps(
|
|
|
25
26
|
|
|
26
27
|
if (!client_key) {
|
|
27
28
|
throw new Error(
|
|
28
|
-
"Attemptted to retrieve seat maps but the client key is missing"
|
|
29
|
+
"Attemptted to retrieve seat maps but the client key is missing."
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -33,17 +34,21 @@ export async function retrieveSeatMaps(
|
|
|
33
34
|
const data = await retrieveSeatMapsFromDuffelAPI(offer_id, client_key);
|
|
34
35
|
onSeatMapReady(data);
|
|
35
36
|
} catch (error) {
|
|
36
|
-
let message = "An unknown error occurred while retrieving the
|
|
37
|
+
let message = "An unknown error occurred while retrieving the seat maps.";
|
|
38
|
+
|
|
37
39
|
if (error instanceof Error) {
|
|
38
40
|
message = error.message;
|
|
39
|
-
if (error.message.includes("
|
|
41
|
+
if (error.message.includes("Load failed")) {
|
|
40
42
|
message = "The Duffel API is not available. Please try again later.";
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
} else if (isErrorResponse(error)) {
|
|
45
|
+
message =
|
|
46
|
+
error.data.errors[0]?.message ||
|
|
47
|
+
"Received an unknown error from the Duffel API.";
|
|
45
48
|
}
|
|
46
|
-
|
|
49
|
+
|
|
50
|
+
captureErrorInSentry(new Error(message));
|
|
51
|
+
onError();
|
|
47
52
|
} finally {
|
|
48
53
|
setIsLoading(false);
|
|
49
54
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaggageSelectionModalHeader } from "@components/bags/BaggageSelectionModalHeader";
|
|
2
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { BaggageSelectionModalHeader } from "../components/DuffelAncillaries/bags/BaggageSelectionModalHeader";
|
|
3
3
|
import { OfferSliceSegment } from "../types/Offer";
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
5
|
const offer = require("../fixtures/offers/off_0000AUde3KwTztSRK1cznH.json");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Button, ButtonProps } from "@components/Button";
|
|
2
1
|
import type { Meta, StoryFn, StoryObj } from "@storybook/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button, ButtonProps } from "../components/shared/Button";
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
title: "Button",
|
|
@@ -47,7 +48,9 @@ export const WithSize48: ButtonStory = {
|
|
|
47
48
|
args: { ...defaultProps, size: 48, iconBefore: "autorenew" },
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
const AccentColorWrapper: React.FC<{ children: React.ReactNode }> = ({
|
|
52
|
+
children,
|
|
53
|
+
}) => (
|
|
51
54
|
<div
|
|
52
55
|
style={
|
|
53
56
|
{
|
|
@@ -55,6 +58,12 @@ export const WithAccentColorSet: StoryFn<ButtonProps> = () => (
|
|
|
55
58
|
} as any
|
|
56
59
|
}
|
|
57
60
|
>
|
|
58
|
-
|
|
61
|
+
{children}
|
|
59
62
|
</div>
|
|
60
63
|
);
|
|
64
|
+
|
|
65
|
+
export const WithAccentColorSet: StoryFn<ButtonProps> = () => (
|
|
66
|
+
<AccentColorWrapper>
|
|
67
|
+
<Button {...defaultProps} />
|
|
68
|
+
</AccentColorWrapper>
|
|
69
|
+
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DuffelAncillaries } from "@components/DuffelAncillaries";
|
|
2
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { DuffelAncillaries } from "../components/DuffelAncillaries/DuffelAncillaries";
|
|
3
3
|
import mockPassengers from "../fixtures/passengers/mock_passengers";
|
|
4
4
|
import { DuffelAncillariesPropsWithOffersAndSeatMaps } from "../types/DuffelAncillariesProps";
|
|
5
5
|
import { Offer } from "../types/Offer";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
DuffelPayments,
|
|
4
|
+
DuffelPaymentsProps,
|
|
5
|
+
} from "../components/DuffelPayments/DuffelPayments";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: "DuffelPayments",
|
|
9
|
+
component: DuffelPayments,
|
|
10
|
+
} as Meta;
|
|
11
|
+
|
|
12
|
+
type DuffelPaymentsStory = StoryObj<typeof DuffelPayments>;
|
|
13
|
+
|
|
14
|
+
const defaultProps: DuffelPaymentsProps = {
|
|
15
|
+
paymentIntentClientToken:
|
|
16
|
+
"eyJjbGllbnRfc2VjcmV0IjoicGlfM0psczlVQWcySmhFeTh2WTBSTm1MU0JkX3NlY3JldF9QUW9yZXNuU3laeWJadGRiejZwNzBCbUdPIiwicHVibGlzaGFibGVfa2V5IjoicGtfdGVzdF9EQUJLY0E2Vzh6OTc0cTdPSWY0YmJ2MVQwMEpwRmMyOUpWIn0=",
|
|
17
|
+
onSuccessfulPayment: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
18
|
+
onFailedPayment: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Default: DuffelPaymentsStory = {
|
|
22
|
+
args: defaultProps,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithCustomStyles: DuffelPaymentsStory = {
|
|
26
|
+
args: {
|
|
27
|
+
...defaultProps,
|
|
28
|
+
styles: {
|
|
29
|
+
accentColor: "29, 78, 216",
|
|
30
|
+
fontFamily: "monospace",
|
|
31
|
+
buttonCornerRadius: "15px",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Icon, ICON_MAP, IconName } from "@components/Icon";
|
|
2
1
|
import { Meta } from "@storybook/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Icon, ICON_MAP, IconName } from "../components/shared/Icon";
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
title: "Icon",
|
|
6
7
|
component: Icon,
|
|
7
8
|
} as Meta;
|
|
8
9
|
|
|
9
|
-
export const FullList = () => (
|
|
10
|
+
export const FullList: React.FC = () => (
|
|
10
11
|
<div
|
|
11
12
|
style={{
|
|
12
13
|
display: "grid",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.card-payment__container {
|
|
2
|
+
width: 480px;
|
|
3
|
+
height: auto;
|
|
4
|
+
max-width: 480px;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
margin: var(--SPACING-XS-3);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.card-payment__container--invalid {
|
|
10
|
+
margin-top: var(--SPACING-SM-1);
|
|
11
|
+
font-size: var(--FONT-SIZES-C2);
|
|
12
|
+
color: var(--RED);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.card-payment__container--invalid {
|
|
16
|
+
height: 16px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.card-details {
|
|
20
|
+
background-color: rgb(var(--WHITE));
|
|
21
|
+
border: 1px solid var(--GREY-200);
|
|
22
|
+
border-radius: 5px;
|
|
23
|
+
padding: var(--SPACING-SM-2);
|
|
24
|
+
width: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.card-details.StripeElement--invalid {
|
|
28
|
+
border: 1px solid var(--RED);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.card-payment__pay-button {
|
|
32
|
+
width: 100%;
|
|
33
|
+
margin-top: var(--SPACING-SM-3);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card-payment--in-progress {
|
|
37
|
+
display: block;
|
|
38
|
+
z-index: 3;
|
|
39
|
+
width: 100vw;
|
|
40
|
+
height: 100vh;
|
|
41
|
+
position: absolute;
|
|
42
|
+
}
|
package/src/styles/global.css
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
@import "./components/CfarSelectionModal.css";
|
|
13
13
|
@import "./components/Card.css";
|
|
14
14
|
@import "./components/Counter.css";
|
|
15
|
+
@import "./components/DuffelPayments.css";
|
|
15
16
|
@import "./components/IconButton.css";
|
|
16
17
|
@import "./components/Legend.css";
|
|
17
18
|
@import "./components/Loader.css";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fireEvent, render } from "@testing-library/react";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { DuffelAncillaries } from "../../components/DuffelAncillaries";
|
|
3
|
+
import { DuffelAncillaries } from "../../components/DuffelAncillaries/DuffelAncillaries";
|
|
4
4
|
import mockPassengers from "../../fixtures/passengers/mock_passengers";
|
|
5
5
|
import {
|
|
6
6
|
DuffelAncillariesPropsWithOffersAndSeatMaps,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createPriceFormatters } from "
|
|
1
|
+
import { createPriceFormatters } from "../../lib/createPriceFormatters";
|
|
2
2
|
|
|
3
3
|
describe("createPriceFormatters", () => {
|
|
4
4
|
it("should return an empty object if neither markup nor priceFormatters are supplied", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatAvailableServices } from "
|
|
1
|
+
import { formatAvailableServices } from "../../lib/formatAvailableServices";
|
|
2
2
|
import { OfferAvailableServiceBaggage } from "../../types/Offer";
|
|
3
3
|
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatSeatMaps } from "
|
|
2
|
-
import { getFirstSeatElementMatchingCriteria } from "
|
|
1
|
+
import { formatSeatMaps } from "../../lib/formatSeatMaps";
|
|
2
|
+
import { getFirstSeatElementMatchingCriteria } from "../../lib/getFirstSeatElementMatchingCriteria";
|
|
3
3
|
import { SeatMapCabinRowSectionElementSeat } from "../../types/SeatMap";
|
|
4
4
|
|
|
5
5
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasServiceOfSameMetadataTypeAlreadyBeenSelected } from "
|
|
1
|
+
import { hasServiceOfSameMetadataTypeAlreadyBeenSelected } from "../../lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected";
|
|
2
2
|
import { CreateOrderPayloadService } from "../../types/CreateOrderPayload";
|
|
3
3
|
import { OfferAvailableBaggageService } from "../../types/Offer";
|
|
4
4
|
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { initializeLogger } from "
|
|
1
|
+
import { initializeLogger, log, logGroup } from "../../lib/logging";
|
|
2
2
|
|
|
3
3
|
const consoleSpies = {
|
|
4
|
-
log: jest.spyOn(console, "
|
|
4
|
+
log: jest.spyOn(console, "info"),
|
|
5
5
|
groupCollapsed: jest.spyOn(console, "groupCollapsed"),
|
|
6
6
|
groupEnd: jest.spyOn(console, "groupEnd"),
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
describe("
|
|
9
|
+
describe("logging", () => {
|
|
10
10
|
it("should return functions that do nothing when debugMode is false", () => {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
initializeLogger(false);
|
|
12
|
+
log("This should not be logged");
|
|
13
13
|
expect(consoleSpies.log).not.toHaveBeenCalled();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it("should return functions that log a message when debugMode is true", () => {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
initializeLogger(true);
|
|
18
|
+
log("This should be logged");
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
expect(consoleSpies.log).toHaveBeenCalledTimes(
|
|
20
|
+
// Once because initializeLogger was initialised on the test above
|
|
21
|
+
// And so, the initialisation message would not show up twice.
|
|
22
|
+
expect(consoleSpies.log).toHaveBeenCalledTimes(1);
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
logGroup("This should be logged", ["one", "two"]);
|
|
25
25
|
expect(consoleSpies.groupCollapsed).toHaveBeenCalledTimes(1);
|
|
26
26
|
expect(consoleSpies.groupEnd).toHaveBeenCalledTimes(1);
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
expect(consoleSpies.log).toHaveBeenCalledTimes(
|
|
28
|
+
// once for the call to `log` above and then
|
|
29
|
+
// twice for the array passed to logGroup
|
|
30
|
+
expect(consoleSpies.log).toHaveBeenCalledTimes(3);
|
|
31
31
|
});
|
|
32
32
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* that we can test functions with invalid props.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { hasCommonRequiredProps } from "
|
|
7
|
+
import { hasCommonRequiredProps } from "../../lib/validateProps";
|
|
8
8
|
import { Ancillaries } from "../../types/DuffelAncillariesProps";
|
|
9
9
|
|
|
10
10
|
describe("hasCommonRequiredProps", () => {
|
|
@@ -75,7 +75,7 @@ export type DuffelAncillariesPriceFormatterForCancelForAnyReason = (
|
|
|
75
75
|
amount: number,
|
|
76
76
|
currency: string,
|
|
77
77
|
service: OfferAvailableServiceCancelForAnyReason
|
|
78
|
-
) => { amount: number; currency
|
|
78
|
+
) => { amount: number; currency?: string };
|
|
79
79
|
|
|
80
80
|
export interface DuffelAncillariesPriceFormatters {
|
|
81
81
|
bags?: DuffelAncillariesPriceFormatterForBags;
|
package/tsconfig.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.d.ts
RENAMED
|
File without changes
|
/package/react-dist/components/{AnimatedLoaderEllipsis.d.ts → shared/AnimatedLoaderEllipsis.d.ts}
RENAMED
|
File without changes
|
/package/react-dist/components/{FetchOfferErrorState.d.ts → shared/FetchOfferErrorState.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|