@duffel/components 3.0.7-canary → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/.eslintrc.js +8 -0
  2. package/.github/CODEOWNERS +4 -0
  3. package/.github/renovate.json +1 -5
  4. package/.github/workflows/release.yml +3 -0
  5. package/.storybook/__snapshots__/Storyshots.test.js.snap +20384 -533
  6. package/README.md +16 -2
  7. package/config/esbuild.base.config.js +6 -2
  8. package/config/esbuild.cdn.config.js +2 -1
  9. package/config/esbuild.dev.config.js +2 -1
  10. package/config/esbuild.react.config.js +1 -1
  11. package/data/airports.csv +9084 -0
  12. package/data/cities.csv +256 -0
  13. package/package.json +11 -1
  14. package/react-dist/components/{Card.d.ts → DuffelAncillaries/Card.d.ts} +1 -1
  15. package/react-dist/components/{DuffelAncillaries.d.ts → DuffelAncillaries/DuffelAncillaries.d.ts} +1 -1
  16. package/react-dist/components/{DuffelAncillariesCustomElement.d.ts → DuffelAncillaries/DuffelAncillariesCustomElement.d.ts} +1 -1
  17. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.d.ts +2 -2
  18. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.d.ts +2 -2
  19. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.d.ts +2 -2
  20. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.d.ts +2 -2
  21. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.d.ts +2 -2
  22. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.d.ts +1 -1
  23. package/react-dist/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.d.ts +1 -1
  24. package/react-dist/components/{bags → DuffelAncillaries/bags}/IncludedBaggageBanner.d.ts +1 -1
  25. package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionCard.d.ts +2 -2
  26. package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModal.d.ts +2 -2
  27. package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBody.d.ts +1 -1
  28. package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalFooter.d.ts +2 -2
  29. package/react-dist/components/{seats → DuffelAncillaries/seats}/Amenity.d.ts +1 -1
  30. package/react-dist/components/{seats → DuffelAncillaries/seats}/Element.d.ts +2 -2
  31. package/react-dist/components/{seats → DuffelAncillaries/seats}/Legend.d.ts +1 -1
  32. package/react-dist/components/{seats → DuffelAncillaries/seats}/Row.d.ts +2 -2
  33. package/react-dist/components/{seats → DuffelAncillaries/seats}/RowSection.d.ts +2 -2
  34. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatElement.d.ts +2 -2
  35. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatInfo.d.ts +1 -1
  36. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatMap.d.ts +2 -2
  37. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionCard.d.ts +3 -3
  38. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModal.d.ts +3 -3
  39. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.d.ts +2 -2
  40. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.d.ts +1 -1
  41. package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.d.ts +1 -1
  42. package/react-dist/components/DuffelPayments/DuffelPayments.d.ts +11 -0
  43. package/react-dist/components/DuffelPayments/DuffelPaymentsCustomElement.d.ts +14 -0
  44. package/react-dist/components/PlacesLookup/PlacesLookup.d.ts +20 -0
  45. package/react-dist/components/{Button.d.ts → shared/Button.d.ts} +2 -2
  46. package/react-dist/components/{ErrorBoundary.d.ts → shared/ErrorBoundary.d.ts} +1 -1
  47. package/react-dist/components/{Icon.d.ts → shared/Icon.d.ts} +2 -0
  48. package/react-dist/components/{IconButton.d.ts → shared/IconButton.d.ts} +1 -1
  49. package/react-dist/components/{NonIdealState.d.ts → shared/NonIdealState.d.ts} +1 -1
  50. package/react-dist/custom-elements.d.ts +2 -1
  51. package/react-dist/custom-elements.js +21 -20
  52. package/react-dist/custom-elements.js.map +4 -4
  53. package/react-dist/index.d.ts +3 -1
  54. package/react-dist/index.js +51 -21
  55. package/react-dist/index.js.map +4 -4
  56. package/react-dist/lib/captureErrorInSentry.d.ts +1 -1
  57. package/react-dist/lib/fetchFromDuffelAPI.d.ts +7 -0
  58. package/react-dist/lib/hasHighLuminance.d.ts +1 -0
  59. package/react-dist/lib/logging.d.ts +7 -14
  60. package/react-dist/lib/retrieveSeatMaps.d.ts +1 -1
  61. package/react-dist/types/DuffelAncillariesProps.d.ts +1 -1
  62. package/scripts/generate-fixture.ts +13 -8
  63. package/scripts/setup-suggestion-data.ts +100 -0
  64. package/scripts/upload-to-cdn.sh +1 -1
  65. package/src/components/{Card.tsx → DuffelAncillaries/Card.tsx} +1 -1
  66. package/src/components/{Counter.tsx → DuffelAncillaries/Counter.tsx} +1 -1
  67. package/src/components/{DuffelAncillaries.tsx → DuffelAncillaries/DuffelAncillaries.tsx} +68 -64
  68. package/src/components/{DuffelAncillariesCustomElement.tsx → DuffelAncillaries/DuffelAncillariesCustomElement.tsx} +2 -2
  69. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionCard.tsx +10 -5
  70. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionController.tsx +2 -2
  71. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModal.tsx +4 -4
  72. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBody.tsx +3 -3
  73. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalBodyPassenger.tsx +4 -4
  74. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalFooter.tsx +23 -16
  75. package/src/components/{bags → DuffelAncillaries/bags}/BaggageSelectionModalHeader.tsx +24 -18
  76. package/src/components/{bags → DuffelAncillaries/bags}/IncludedBaggageBanner.tsx +1 -1
  77. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionCard.tsx +4 -4
  78. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModal.tsx +3 -3
  79. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBody.tsx +3 -3
  80. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBodyListItem.tsx +1 -1
  81. package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalFooter.tsx +5 -5
  82. package/src/components/{seats → DuffelAncillaries/seats}/Amenity.tsx +2 -2
  83. package/src/components/{seats → DuffelAncillaries/seats}/DeckSelect.tsx +1 -1
  84. package/src/components/{seats → DuffelAncillaries/seats}/Element.tsx +2 -2
  85. package/src/components/{seats → DuffelAncillaries/seats}/ExitElement.tsx +1 -1
  86. package/src/components/{seats → DuffelAncillaries/seats}/Legend.tsx +2 -2
  87. package/src/components/{seats → DuffelAncillaries/seats}/Row.tsx +2 -2
  88. package/src/components/{seats → DuffelAncillaries/seats}/RowSection.tsx +5 -2
  89. package/src/components/{seats → DuffelAncillaries/seats}/SeatElement.tsx +3 -3
  90. package/src/components/{seats → DuffelAncillaries/seats}/SeatInfo.tsx +1 -1
  91. package/src/components/{seats → DuffelAncillaries/seats}/SeatMap.tsx +6 -2
  92. package/src/components/{seats → DuffelAncillaries/seats}/SeatMapUnavailable.tsx +1 -1
  93. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionCard.tsx +5 -5
  94. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModal.tsx +5 -5
  95. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.tsx +1 -1
  96. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalFooter.tsx +24 -17
  97. package/src/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalHeader.tsx +30 -20
  98. package/src/components/{seats → DuffelAncillaries/seats}/SeatUnavailable.tsx +2 -2
  99. package/src/components/DuffelPayments/DuffelPayments.tsx +224 -0
  100. package/src/components/DuffelPayments/DuffelPaymentsCustomElement.tsx +130 -0
  101. package/src/components/PlacesLookup/PlacesLookup.tsx +123 -0
  102. package/src/components/{Button.tsx → shared/Button.tsx} +4 -3
  103. package/src/components/{ErrorBoundary.tsx → shared/ErrorBoundary.tsx} +2 -2
  104. package/src/components/{Icon.tsx → shared/Icon.tsx} +11 -11
  105. package/src/components/{IconButton.tsx → shared/IconButton.tsx} +1 -1
  106. package/src/components/{Modal.tsx → shared/Modal.tsx} +5 -1
  107. package/src/components/{NonIdealState.tsx → shared/NonIdealState.tsx} +1 -1
  108. package/src/custom-elements.ts +6 -1
  109. package/src/examples/client-side/index.html +1 -1
  110. package/src/examples/full-stack/index.html +1 -1
  111. package/src/examples/full-stack/server.mjs +1 -0
  112. package/src/examples/just-typescript/src/index.html +2 -2
  113. package/src/examples/just-typescript/src/index.ts +2 -1
  114. package/src/examples/payments-custom-element/README.md +17 -0
  115. package/src/examples/payments-custom-element/index.html +43 -0
  116. package/src/examples/payments-just-typescript/README.md +37 -0
  117. package/src/examples/payments-just-typescript/package.json +16 -0
  118. package/src/examples/payments-just-typescript/src/index.html +23 -0
  119. package/src/examples/payments-just-typescript/src/index.ts +18 -0
  120. package/src/examples/payments-just-typescript/yarn.lock +154 -0
  121. package/src/examples/react-app/src/index.tsx +11 -6
  122. package/src/fixtures/offers/off_1.json +1 -10
  123. package/src/index.ts +3 -1
  124. package/src/lib/captureErrorInSentry.ts +2 -20
  125. package/src/lib/fetchFromDuffelAPI.ts +36 -6
  126. package/src/lib/formatDate.ts +3 -4
  127. package/src/lib/getBaggageServiceDescription.ts +1 -6
  128. package/src/lib/getPassengerName.ts +4 -0
  129. package/src/lib/getTotalAmountForServices.ts +1 -1
  130. package/src/lib/hasHighLuminance.ts +9 -0
  131. package/src/lib/logging.ts +52 -32
  132. package/src/lib/retrieveOffer.ts +13 -6
  133. package/src/lib/retrieveSeatMaps.ts +13 -8
  134. package/src/stories/BaggageSelectionModalHeader.stories.tsx +1 -1
  135. package/src/stories/Button.stories.tsx +33 -2
  136. package/src/stories/DuffelAncillaries.stories.tsx +42 -2
  137. package/src/stories/DuffelPayments.stories.tsx +34 -0
  138. package/src/stories/Icon.stories.tsx +3 -2
  139. package/src/stories/IconButton.stories.tsx +1 -1
  140. package/src/stories/PlacesLookup.stories.tsx +22 -0
  141. package/src/styles/components/Button.css +11 -3
  142. package/src/styles/components/Card.css +3 -3
  143. package/src/styles/components/CfarSelectionModal.css +1 -1
  144. package/src/styles/components/DuffelPayments.css +42 -0
  145. package/src/styles/components/Legend.css +10 -6
  146. package/src/styles/components/LoadingState.css +8 -2
  147. package/src/styles/components/Modal.css +2 -1
  148. package/src/styles/components/PassengerSelect.css +8 -2
  149. package/src/styles/components/PlacesLookup.css +36 -0
  150. package/src/styles/components/Seat.css +9 -7
  151. package/src/styles/components/SeatInfo.css +1 -1
  152. package/src/styles/components/Tabs.css +5 -2
  153. package/src/styles/global.css +2 -0
  154. package/src/tests/components/DuffelAncillaries.test.tsx +1 -1
  155. package/src/tests/lib/createPriceFormatters.test.tsx +1 -1
  156. package/src/tests/lib/formatAvailableServices.test.tsx +1 -1
  157. package/src/tests/lib/formatSeatMaps.test.tsx +2 -2
  158. package/src/tests/lib/getCurrencyForServices.test.tsx +1 -1
  159. package/src/tests/lib/hasServiceOfSameMetadataTypeAlreadyBeenSelected.test.ts +1 -1
  160. package/src/tests/lib/logging.test.tsx +14 -14
  161. package/src/tests/lib/moneyStringFormatter.test.tsx +1 -1
  162. package/src/tests/lib/validateProps.test.tsx +1 -1
  163. package/src/types/DuffelAncillariesProps.ts +1 -1
  164. /package/react-dist/components/{Counter.d.ts → DuffelAncillaries/Counter.d.ts} +0 -0
  165. /package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalBodyListItem.d.ts +0 -0
  166. /package/react-dist/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalHeader.d.ts +0 -0
  167. /package/react-dist/components/{seats → DuffelAncillaries/seats}/DeckSelect.d.ts +0 -0
  168. /package/react-dist/components/{seats → DuffelAncillaries/seats}/EmptyElement.d.ts +0 -0
  169. /package/react-dist/components/{seats → DuffelAncillaries/seats}/ExitElement.d.ts +0 -0
  170. /package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatMapUnavailable.d.ts +0 -0
  171. /package/react-dist/components/{seats → DuffelAncillaries/seats}/SeatSelectionModalBody.d.ts +0 -0
  172. /package/react-dist/components/{AnimatedLoaderEllipsis.d.ts → shared/AnimatedLoaderEllipsis.d.ts} +0 -0
  173. /package/react-dist/components/{FetchOfferErrorState.d.ts → shared/FetchOfferErrorState.d.ts} +0 -0
  174. /package/react-dist/components/{Modal.d.ts → shared/Modal.d.ts} +0 -0
  175. /package/react-dist/components/{Stamp.d.ts → shared/Stamp.d.ts} +0 -0
  176. /package/react-dist/components/{Tabs.d.ts → shared/Tabs.d.ts} +0 -0
  177. /package/src/components/{cancel_for_any_reason → DuffelAncillaries/cancel_for_any_reason}/CfarSelectionModalHeader.tsx +0 -0
  178. /package/src/components/{seats → DuffelAncillaries/seats}/EmptyElement.tsx +0 -0
  179. /package/src/components/{AnimatedLoaderEllipsis.tsx → shared/AnimatedLoaderEllipsis.tsx} +0 -0
  180. /package/src/components/{FetchOfferErrorState.tsx → shared/FetchOfferErrorState.tsx} +0 -0
  181. /package/src/components/{Stamp.tsx → shared/Stamp.tsx} +0 -0
  182. /package/src/components/{Tabs.tsx → shared/Tabs.tsx} +0 -0
@@ -1,4 +1,14 @@
1
- import { createContext, useContext } from "react";
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
- const initializeLogger = (debugMode: boolean) => {
30
- if (debugMode) {
31
- log(
32
- `\n\nDebug mode is enabled. Information about your setup will be printed to the console.\n\nIf you do not want to enable debug mode (for example in a production environment), pass "debug: false" when initializing this component.\n\nLearn more about the Ancillaries component:\nhttp://duffel.com/docs/guides/ancillaries-component`
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
- console.log(MESSAGE_PREFIX, message);
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
- console.groupEnd();
95
- }
100
+ if (shouldLog()) {
101
+ // eslint-disable-next-line
102
+ console.groupCollapsed(MESSAGE_PREFIX, groupName);
96
103
 
97
- const LogContext = createContext(initializeLogger(false));
98
- const useLog = () => useContext(LogContext);
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 { LogContext, initializeLogger, useLog };
120
+ export { initializeLogger, logGroup, log };
@@ -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,23 +25,29 @@ 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
+ "Attempted 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
- return onOfferReady(data);
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("ECONNREFUSED")) {
39
+ if (error.message.includes("Load failed")) {
39
40
  message = "The Duffel API is not available. Please try again later.";
40
41
  }
41
- captureErrorInSentry(error, { offer_id });
42
- } else {
43
- captureErrorInSentry(new Error(message), { offer_id });
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
+ if (isErrorResponse(error) && error.status >= 500 && error.status < 600) {
50
+ captureErrorInSentry(new Error(message));
44
51
  }
45
52
  onError(message);
46
53
  } finally {
@@ -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: (error: string) => void,
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 offer.";
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("ECONNREFUSED")) {
41
+ if (error.message.includes("Load failed")) {
40
42
  message = "The Duffel API is not available. Please try again later.";
41
43
  }
42
- captureErrorInSentry(error, { offer_id });
43
- } else {
44
- captureErrorInSentry(new Error(message), { offer_id });
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
- onError(message);
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
- export const WithAccentColorSet: StoryFn<ButtonProps> = () => (
51
+ const AccentColorWrapper: React.FC<{ children: React.ReactNode }> = ({
52
+ children,
53
+ }) => (
51
54
  <div
52
55
  style={
53
56
  {
@@ -55,6 +58,34 @@ export const WithAccentColorSet: StoryFn<ButtonProps> = () => (
55
58
  } as any
56
59
  }
57
60
  >
61
+ {children}
62
+ </div>
63
+ );
64
+
65
+ export const WithAccentColorSet: StoryFn<ButtonProps> = () => (
66
+ <AccentColorWrapper>
58
67
  <Button {...defaultProps} />
68
+ </AccentColorWrapper>
69
+ );
70
+
71
+ const WhiteAccentColorWrapper: React.FC<{ children: React.ReactNode }> = ({
72
+ children,
73
+ }) => (
74
+ <div
75
+ style={
76
+ {
77
+ "--ACCENT": "255, 255, 255",
78
+ "--SECONDARY": "black",
79
+ "--TERTIARY": "grey",
80
+ } as any
81
+ }
82
+ >
83
+ {children}
59
84
  </div>
60
85
  );
86
+
87
+ export const WithWhiteAccentColorSet: StoryFn<ButtonProps> = () => (
88
+ <WhiteAccentColorWrapper>
89
+ <Button {...defaultProps} />
90
+ </WhiteAccentColorWrapper>
91
+ );
@@ -1,5 +1,5 @@
1
- import { DuffelAncillaries } from "@components/DuffelAncillaries";
2
- import type { Meta, StoryObj } from "@storybook/react";
1
+ import type { Meta, StoryFn, 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";
@@ -62,6 +62,28 @@ export const ExpiredOffer: DuffelAncillariesStory = {
62
62
  },
63
63
  };
64
64
 
65
+ export const OneWayOnePassengerOffer: DuffelAncillariesStory = {
66
+ args: {
67
+ ...defaultProps,
68
+ offer: {
69
+ ...offer,
70
+ slices: [
71
+ {
72
+ ...offer.slices[0],
73
+ segments: [
74
+ {
75
+ ...offer.slices[0].segments[0],
76
+ passengers: [offer.slices[0].segments[0].passengers[0]],
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ passengers: [offer.passengers[0]],
82
+ available_services: [offer.available_services[0]],
83
+ },
84
+ },
85
+ };
86
+
65
87
  export const WithCustomStyles: DuffelAncillariesStory = {
66
88
  args: {
67
89
  ...defaultProps,
@@ -124,3 +146,21 @@ export const MarkupUsingPriceFormattersWithCustomCurrency: DuffelAncillariesStor
124
146
  },
125
147
  },
126
148
  };
149
+
150
+ export const WithAccentColorSet: StoryFn<
151
+ DuffelAncillariesPropsWithOffersAndSeatMaps
152
+ > = () => (
153
+ <DuffelAncillaries
154
+ {...defaultProps}
155
+ styles={{ accentColor: "29, 78, 216" }}
156
+ />
157
+ );
158
+
159
+ export const WithWhiteAccentColorSet: StoryFn<
160
+ DuffelAncillariesPropsWithOffersAndSeatMaps
161
+ > = () => (
162
+ <DuffelAncillaries
163
+ {...defaultProps}
164
+ styles={{ accentColor: "255, 255, 255" }}
165
+ />
166
+ );
@@ -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",
@@ -1,5 +1,5 @@
1
- import { IconButton, IconButtonProps } from "@components/IconButton";
2
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { IconButton, IconButtonProps } from "../components/shared/IconButton";
3
3
 
4
4
  export default {
5
5
  title: "IconButton",
@@ -0,0 +1,22 @@
1
+ import type { Meta } from "@storybook/react";
2
+ import React from "react";
3
+ import {
4
+ PlacesLookup,
5
+ PlacesLookupProps,
6
+ } from "../components/PlacesLookup/PlacesLookup";
7
+
8
+ export default {
9
+ title: "PlacesLookup",
10
+ component: PlacesLookup,
11
+ } as Meta;
12
+
13
+ const defaultProps: PlacesLookupProps = {
14
+ onPlaceSelected: (selection) =>
15
+ alert(`Selected: ${JSON.stringify(selection)}`),
16
+ };
17
+
18
+ export const Default: React.FC = () => (
19
+ <div style={{ width: "350px" }}>
20
+ <PlacesLookup {...defaultProps} />
21
+ </div>
22
+ );
@@ -62,7 +62,7 @@ button:-moz-focusring,
62
62
  }
63
63
 
64
64
  .button {
65
- --button-color: white;
65
+ --button-color: var(--SECONDARY, white);
66
66
 
67
67
  box-sizing: border-box;
68
68
  border-radius: var(--BUTTON-RADIUS, 6px);
@@ -80,6 +80,8 @@ button:-moz-focusring,
80
80
  box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.25), 0px 0px 0px #e9e9eb;
81
81
 
82
82
  border: 1px solid rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
83
+ /* Will give the button an outline if the accent color has high luminance */
84
+ border: 1px solid var(--SECONDARY);
83
85
  transition: background-color 0.3s var(--TRANSITION-CUBIC-BEZIER),
84
86
  box-shadow 0.3s var(--TRANSITION-CUBIC-BEZIER),
85
87
  color 0.3s var(--TRANSITION-CUBIC-BEZIER);
@@ -128,13 +130,19 @@ button:-moz-focusring,
128
130
 
129
131
  .button--primary:not(:disabled):hover {
130
132
  background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-800));
131
- border-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-800));
133
+ border-color: (
134
+ var(--SECONDARY),
135
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-800))
136
+ );
132
137
  box-shadow: 0px 2px 4px 1px rgba(0, 0, 0, 0.15), 0px 0px 0px 2px #e9e9eb;
133
138
  }
134
139
 
135
140
  .button--primary:not(:disabled):active {
136
141
  background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
137
- border-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
142
+ border-color: (
143
+ var(--SECONDARY),
144
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000))
145
+ );
138
146
  box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px #dcdcde;
139
147
  }
140
148
 
@@ -33,18 +33,18 @@
33
33
  .ancillary-card:not(:disabled):not(.ancillary-card--loading):hover,
34
34
  .ancillary-card:not(:disabled):not(.ancillary-card--loading):focus-visible {
35
35
  /* important is needed here to override the inline border style from Card.tsx */
36
- border-color: rgb(var(--ACCENT)) !important;
36
+ border-color: var(--SECONDARY, rgb(var(--ACCENT))) !important;
37
37
  }
38
38
 
39
39
  .ancillary-card:not(:disabled):not(.ancillary-card--loading):hover
40
40
  .ancillary-card__expand-icon,
41
41
  .ancillary-card:not(:disabled):not(.ancillary-card--loading):focus-visible
42
42
  .ancillary-card__expand-icon {
43
- color: rgb(var(--ACCENT));
43
+ color: var(--SECONDARY, rgb(var(--ACCENT)));
44
44
  }
45
45
 
46
46
  .ancillary-card__selected-icon {
47
- background-color: rgb(var(--ACCENT));
47
+ background-color: var(--SECONDARY, rgb(var(--ACCENT)));
48
48
  border-radius: 20px;
49
49
  color: rgb(var(--WHITE));
50
50
  height: 24px;
@@ -17,7 +17,7 @@
17
17
  }
18
18
 
19
19
  .cfar-modal-list-item > svg {
20
- color: rgb(var(--ACCENT));
20
+ color: var(--SECONDARY, rgb(var(--ACCENT)));
21
21
  }
22
22
 
23
23
  .cfar-modal-list-item > p {
@@ -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
+ }
@@ -27,8 +27,9 @@
27
27
  width: var(--SPACING-MD-1);
28
28
  height: var(--SPACING-MD-1);
29
29
  border-radius: 5px;
30
- border: 2px solid var(--GREY-400);
31
- color: var(--GREY-400);
30
+ border: 2px solid var(--GREY-200);
31
+ color: var(--GREY-600);
32
+ background-color: var(--GREY-100);
32
33
  display: flex;
33
34
  align-items: center;
34
35
  justify-content: center;
@@ -37,22 +38,25 @@
37
38
 
38
39
  .seat-map__legend-seat--fee-payable {
39
40
  background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-100));
40
- border: 2px solid rgba(var(--ACCENT), var(--ACCENT-LIGHT-200));
41
+ border: 2px solid
42
+ var(--TERTIARY, rgba(var(--ACCENT), var(--ACCENT-LIGHT-200)));
41
43
  }
42
44
 
43
45
  .seat-map__legend-seat--fee-payable-indicator {
44
46
  position: relative;
45
47
  top: var(--SPACING-XS-2);
46
48
  left: var(--SPACING-XS-2);
47
- color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-300));
49
+ color: var(--TERTIARY, rgba(var(--ACCENT), var(--ACCENT-LIGHT-300)));
48
50
  }
49
51
 
50
52
  .seat-map__legend-seat--included {
51
53
  background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-100));
52
- border: 2px solid rgba(var(--ACCENT), var(--ACCENT-LIGHT-200));
54
+ border: 2px solid
55
+ var(--TERTIARY, rgba(var(--ACCENT), var(--ACCENT-LIGHT-200)));
53
56
  }
54
57
 
55
58
  .seat-map__legend-seat--selected {
56
59
  background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
57
- border: 2px solid rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
60
+ border: 2px solid
61
+ var(--SECONDARY, rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000)));
58
62
  }
@@ -24,7 +24,10 @@
24
24
  margin: var(--SPACING-SM-3) 0;
25
25
  border-radius: 999px;
26
26
  width: calc(100% - var(--SPACING-SM-1));
27
- background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-100));
27
+ background-color: var(
28
+ --SECONDARY,
29
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-100))
30
+ );
28
31
  position: relative;
29
32
  transition: var(--TRANSITIONS-CUBIC-BEZIER);
30
33
  }
@@ -33,7 +36,10 @@
33
36
  display: block;
34
37
  height: 100%;
35
38
  border-radius: 999px;
36
- background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000));
39
+ background-color: var(
40
+ --TERTIARY,
41
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000))
42
+ );
37
43
  position: relative;
38
44
  animation: fillstatus 1s linear;
39
45
  }
@@ -27,7 +27,8 @@
27
27
 
28
28
  .modal--open,
29
29
  .modal--open .modal--content {
30
- opacity: 1;
30
+ /* important needed to override inline style */
31
+ opacity: 1 !important;
31
32
  pointer-events: all;
32
33
  }
33
34
 
@@ -43,7 +43,10 @@
43
43
  .passenger-selection-passenger:hover,
44
44
  .passenger-selection-passenger:focus,
45
45
  .passenger-selection-passenger:active {
46
- background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-100));
46
+ background-color: var(
47
+ --SECONDARY,
48
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-100))
49
+ );
47
50
  }
48
51
 
49
52
  .passenger-selection-passenger__identifier,
@@ -78,7 +81,10 @@
78
81
  .passenger-selection-passenger--selected {
79
82
  box-shadow: 4px 0px 0px 0px rgba(var(--ACCENT), var(--ACCENT-LIGHT-1000))
80
83
  inset;
81
- background-color: rgba(var(--ACCENT), var(--ACCENT-LIGHT-100));
84
+ background-color: var(
85
+ --SECONDARY,
86
+ rgba(var(--ACCENT), var(--ACCENT-LIGHT-100))
87
+ );
82
88
  }
83
89
 
84
90
  .passenger-selection-segment {
@@ -0,0 +1,36 @@
1
+ .places-lookup-input {
2
+ width: 100%;
3
+ }
4
+
5
+ .places-lookup-popover {
6
+ background-color: var(--WHITE);
7
+ box-shadow: 0px 1px 4px rgba(59, 64, 86, 0.3);
8
+ border-radius: 4px;
9
+ margin-top: 4px;
10
+ width: 100%;
11
+ padding: 4px;
12
+ }
13
+
14
+ .places-lookup-popover__item {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ padding: 0.5rem;
19
+ cursor: pointer;
20
+ column-gap: var(--SPACING-XS-2);
21
+ background-color: transparent;
22
+ border: none;
23
+ border-radius: 4px;
24
+ }
25
+
26
+ .places-lookup-popover__item:hover,
27
+ .places-lookup-popover__item:focus {
28
+ background-color: var(--GREY-100);
29
+ }
30
+
31
+ .places-lookup-popover__icon-and-name-container {
32
+ column-gap: var(--SPACING-SM-1);
33
+ display: flex;
34
+ align-items: flex-start;
35
+ text-align: start;
36
+ }