@moneylion/react-native-offer-carousel 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/lib/commonjs/capabilities/errors/types/index.js +20 -0
  2. package/lib/commonjs/capabilities/errors/types/index.js.map +1 -0
  3. package/lib/commonjs/capabilities/offer-catalog/src/api/offerCatalogApi.js +41 -2
  4. package/lib/commonjs/capabilities/offer-catalog/src/api/offerCatalogApi.js.map +1 -1
  5. package/lib/commonjs/components/ErrorBoundary/index.js +1 -4
  6. package/lib/commonjs/components/ErrorBoundary/index.js.map +1 -1
  7. package/lib/commonjs/components/Modal/AllOffersModal.js +15 -6
  8. package/lib/commonjs/components/Modal/AllOffersModal.js.map +1 -1
  9. package/lib/commonjs/components/MoneyLionOfferCarousel.js +95 -43
  10. package/lib/commonjs/components/MoneyLionOfferCarousel.js.map +1 -1
  11. package/lib/commonjs/components/utils/errorUtils.js +71 -0
  12. package/lib/commonjs/components/utils/errorUtils.js.map +1 -0
  13. package/lib/commonjs/context/ThemeProvider.js +6 -26
  14. package/lib/commonjs/context/ThemeProvider.js.map +1 -1
  15. package/lib/commonjs/pageData.js +6 -3
  16. package/lib/commonjs/pageData.js.map +1 -1
  17. package/lib/commonjs/services/getDynamicOffers.js +6 -3
  18. package/lib/commonjs/services/getDynamicOffers.js.map +1 -1
  19. package/lib/commonjs/services/getProductTypes.js +4 -2
  20. package/lib/commonjs/services/getProductTypes.js.map +1 -1
  21. package/lib/commonjs/services/getProductTypesByQuery.js +19 -2
  22. package/lib/commonjs/services/getProductTypesByQuery.js.map +1 -1
  23. package/lib/commonjs/utils/getOffersByProductTypes.js +4 -3
  24. package/lib/commonjs/utils/getOffersByProductTypes.js.map +1 -1
  25. package/lib/commonjs/utils/resolveProductTypes.js.map +1 -1
  26. package/lib/module/capabilities/errors/types/index.js +14 -0
  27. package/lib/module/capabilities/errors/types/index.js.map +1 -0
  28. package/lib/module/capabilities/offer-catalog/src/api/offerCatalogApi.js +41 -2
  29. package/lib/module/capabilities/offer-catalog/src/api/offerCatalogApi.js.map +1 -1
  30. package/lib/module/components/ErrorBoundary/index.js +1 -4
  31. package/lib/module/components/ErrorBoundary/index.js.map +1 -1
  32. package/lib/module/components/Modal/AllOffersModal.js +16 -7
  33. package/lib/module/components/Modal/AllOffersModal.js.map +1 -1
  34. package/lib/module/components/MoneyLionOfferCarousel.js +96 -44
  35. package/lib/module/components/MoneyLionOfferCarousel.js.map +1 -1
  36. package/lib/module/components/utils/errorUtils.js +63 -0
  37. package/lib/module/components/utils/errorUtils.js.map +1 -0
  38. package/lib/module/context/ThemeProvider.js +7 -27
  39. package/lib/module/context/ThemeProvider.js.map +1 -1
  40. package/lib/module/pageData.js +6 -3
  41. package/lib/module/pageData.js.map +1 -1
  42. package/lib/module/services/getDynamicOffers.js +6 -3
  43. package/lib/module/services/getDynamicOffers.js.map +1 -1
  44. package/lib/module/services/getProductTypes.js +4 -2
  45. package/lib/module/services/getProductTypes.js.map +1 -1
  46. package/lib/module/services/getProductTypesByQuery.js +19 -2
  47. package/lib/module/services/getProductTypesByQuery.js.map +1 -1
  48. package/lib/module/utils/getOffersByProductTypes.js +4 -3
  49. package/lib/module/utils/getOffersByProductTypes.js.map +1 -1
  50. package/lib/module/utils/resolveProductTypes.js.map +1 -1
  51. package/lib/typescript/src/capabilities/errors/types/index.d.ts +26 -0
  52. package/lib/typescript/src/capabilities/errors/types/index.d.ts.map +1 -0
  53. package/lib/typescript/src/capabilities/offer-catalog/src/api/offerCatalogApi.d.ts +3 -2
  54. package/lib/typescript/src/capabilities/offer-catalog/src/api/offerCatalogApi.d.ts.map +1 -1
  55. package/lib/typescript/src/components/ErrorBoundary/index.d.ts +1 -2
  56. package/lib/typescript/src/components/ErrorBoundary/index.d.ts.map +1 -1
  57. package/lib/typescript/src/components/Modal/AllOffersModal.d.ts.map +1 -1
  58. package/lib/typescript/src/components/MoneyLionOfferCarousel.d.ts +12 -3
  59. package/lib/typescript/src/components/MoneyLionOfferCarousel.d.ts.map +1 -1
  60. package/lib/typescript/src/components/utils/errorUtils.d.ts +22 -0
  61. package/lib/typescript/src/components/utils/errorUtils.d.ts.map +1 -0
  62. package/lib/typescript/src/context/ThemeProvider.d.ts +0 -3
  63. package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
  64. package/lib/typescript/src/pageData.d.ts +3 -1
  65. package/lib/typescript/src/pageData.d.ts.map +1 -1
  66. package/lib/typescript/src/services/getDynamicOffers.d.ts +3 -1
  67. package/lib/typescript/src/services/getDynamicOffers.d.ts.map +1 -1
  68. package/lib/typescript/src/services/getProductTypes.d.ts +3 -1
  69. package/lib/typescript/src/services/getProductTypes.d.ts.map +1 -1
  70. package/lib/typescript/src/services/getProductTypesByQuery.d.ts +3 -1
  71. package/lib/typescript/src/services/getProductTypesByQuery.d.ts.map +1 -1
  72. package/lib/typescript/src/utils/getOffersByProductTypes.d.ts +3 -1
  73. package/lib/typescript/src/utils/getOffersByProductTypes.d.ts.map +1 -1
  74. package/lib/typescript/src/utils/resolveProductTypes.d.ts +2 -0
  75. package/lib/typescript/src/utils/resolveProductTypes.d.ts.map +1 -1
  76. package/package.json +5 -3
  77. package/src/capabilities/errors/types/index.ts +39 -0
  78. package/src/capabilities/offer-catalog/src/api/offerCatalogApi.ts +49 -6
  79. package/src/components/ErrorBoundary/index.tsx +2 -6
  80. package/src/components/Modal/AllOffersModal.tsx +18 -8
  81. package/src/components/MoneyLionOfferCarousel.tsx +130 -49
  82. package/src/components/utils/errorUtils.ts +70 -0
  83. package/src/context/ThemeProvider.tsx +16 -36
  84. package/src/pageData.ts +5 -0
  85. package/src/services/getDynamicOffers.ts +5 -0
  86. package/src/services/getProductTypes.ts +4 -0
  87. package/src/services/getProductTypesByQuery.ts +24 -1
  88. package/src/utils/getOffersByProductTypes.ts +6 -2
  89. package/src/utils/resolveProductTypes.ts +2 -0
@@ -1,4 +1,11 @@
1
- import React, { useEffect, useMemo, useState } from "react";
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useState,
6
+ type Dispatch,
7
+ type SetStateAction,
8
+ } from "react";
2
9
  import { localCnfContext } from "../config/mocks/cnfContext";
3
10
  import { Text } from "react-native";
4
11
  import { getPageData, type GetPageDataParams } from "../pageData";
@@ -19,6 +26,15 @@ import { getThemeColors } from "../utils/getThemeColors";
19
26
  import type { UserData } from "../capabilities/configuration/src/userData/types";
20
27
  import type { ReshapedThemeColors } from "../capabilities/core/src";
21
28
  import ErrorBoundary from "./ErrorBoundary";
29
+ import { VERSION } from "../version";
30
+ import type {
31
+ ApiError,
32
+ InternalMoneyLionOfferCarouselError,
33
+ InternalOnErrorCallback,
34
+ OnErrorCallback,
35
+ } from "../capabilities/errors/types";
36
+ import { ErrorCodes } from "../capabilities/errors/types";
37
+ import { getApiErrorLogProperties } from "./utils/errorUtils";
22
38
 
23
39
  export type CustomError = {
24
40
  code?: number;
@@ -44,10 +60,19 @@ export type MoneyLionOfferCarouselProps = {
44
60
  subtitle?: string;
45
61
  isDarkTheme?: boolean;
46
62
  fallbackUI?: React.ReactNode;
47
- onError?: (error: CustomError) => void;
63
+ onError?: OnErrorCallback;
48
64
  onLoad?: (numOffers: number) => void;
49
65
  userData?: UserData;
50
- };
66
+ } & Omit<EventHandlerContextType, "rateTableUuid" | "leadUuid">;
67
+
68
+ export interface InternalMoneyLionOfferCarouselProps
69
+ extends Omit<MoneyLionOfferCarouselProps, "fallbackUI"> {
70
+ context: CnfContext | null;
71
+ pageData: any;
72
+ setContext: Dispatch<SetStateAction<CnfContext | null>>;
73
+ setPageData: Dispatch<SetStateAction<any>>;
74
+ onError?: InternalOnErrorCallback;
75
+ }
51
76
 
52
77
  const getConfiguration = async ({
53
78
  channel,
@@ -56,7 +81,7 @@ const getConfiguration = async ({
56
81
  isDev,
57
82
  onError,
58
83
  }: Pick<
59
- MoneyLionOfferCarouselProps,
84
+ InternalMoneyLionOfferCarouselProps,
60
85
  "channel" | "zone" | "subAccountToken" | "isDev" | "onError"
61
86
  >) => {
62
87
  const url = `${getConfigApiBaseUrl(isDev)}/network/${channel}/${zone}/api/configuration`;
@@ -67,17 +92,11 @@ const getConfiguration = async ({
67
92
  try {
68
93
  const response = await fetch(url, { headers });
69
94
  if (!response.ok) {
70
- const errorCode = response.status;
71
-
72
- onError?.({
73
- code: errorCode,
74
- message: `Configuration request failed with status: ${response.status}`,
75
- timestamp: new Date().toISOString(),
76
- });
77
-
78
- throw new Error(
95
+ const error = new Error(
79
96
  `Configuration request failed with status: ${response.status}`
80
- );
97
+ ) as ApiError;
98
+ error.code = response.status;
99
+ throw error;
81
100
  }
82
101
 
83
102
  const data = await response.json();
@@ -86,21 +105,26 @@ const getConfiguration = async ({
86
105
  } catch (error) {
87
106
  console.error("Error fetching configuration", error);
88
107
 
89
- if (!(error instanceof Error && error.message.includes("status"))) {
90
- // Call if network error not already reported
91
- onError?.({
92
- message: "Network request failed",
93
- timestamp: new Date().toISOString(),
94
- });
95
- }
108
+ onError?.({
109
+ message: "Failed to fetch configuration",
110
+ error,
111
+ additionalInfo: {
112
+ channel,
113
+ zone,
114
+ isDev,
115
+ },
116
+ ...(getApiErrorLogProperties(error) || {
117
+ code: ErrorCodes.FLOW_ERROR,
118
+ severity: "error",
119
+ }),
120
+ });
96
121
 
97
122
  return localCnfContext;
98
123
  }
99
124
  };
100
125
 
101
126
  const InternalMoneyLionOfferCarousel = (
102
- props: Omit<MoneyLionOfferCarouselProps, "fallbackUI"> &
103
- Omit<EventHandlerContextType, "rateTableUuid" | "leadUuid">
127
+ props: InternalMoneyLionOfferCarouselProps
104
128
  ) => {
105
129
  const {
106
130
  channel,
@@ -121,6 +145,10 @@ const InternalMoneyLionOfferCarousel = (
121
145
  showProductTypeLabel: _showProductTypeLabel,
122
146
  showCardBorder: _showCardBorder,
123
147
  userData,
148
+ context,
149
+ setContext,
150
+ pageData,
151
+ setPageData,
124
152
  } = props;
125
153
 
126
154
  const {
@@ -166,11 +194,9 @@ const InternalMoneyLionOfferCarousel = (
166
194
  ]
167
195
  );
168
196
 
169
- const [context, setContext] = useState<CnfContext | null>(null);
170
197
  const [isFetchingData, setIsFetchingData] = useState(true);
171
198
  const [isFetchingConfig, setIsFetchingConfig] = useState(true);
172
199
  const [error, setError] = useState<Error | null>(null);
173
- const [pageData, setPageData] = useState<any>(null);
174
200
 
175
201
  const isLoading = isFetchingData || isFetchingConfig;
176
202
 
@@ -194,13 +220,19 @@ const InternalMoneyLionOfferCarousel = (
194
220
 
195
221
  setError(errorObj);
196
222
 
197
- // Only call onError if not already called in getConfiguration
198
- if (!errorObj.message.includes("status")) {
199
- onError?.({
200
- message: errorObj.message,
201
- timestamp: new Date().toISOString(),
202
- });
203
- }
223
+ onError?.({
224
+ message: "Failed to fetch configuration",
225
+ error: errorObj,
226
+ additionalInfo: {
227
+ channel,
228
+ zone,
229
+ isDev,
230
+ },
231
+ ...(getApiErrorLogProperties(errorObj) || {
232
+ code: ErrorCodes.FLOW_ERROR,
233
+ severity: "error",
234
+ }),
235
+ });
204
236
  } finally {
205
237
  setIsFetchingConfig(false);
206
238
  }
@@ -221,6 +253,23 @@ const InternalMoneyLionOfferCarousel = (
221
253
  // eslint-disable-next-line react-hooks/exhaustive-deps
222
254
  }, [isFetchingConfig]);
223
255
 
256
+ useEffect(() => {
257
+ if (!channel || !zone || !subAccountToken || !searchAPIToken) {
258
+ const missingParams: string[] = [];
259
+ if (!channel) missingParams.push("channel");
260
+ if (!zone) missingParams.push("zone");
261
+ if (!subAccountToken) missingParams.push("subAccountToken");
262
+ if (!searchAPIToken) missingParams.push("searchAPIToken");
263
+
264
+ onError?.({
265
+ code: ErrorCodes.MISSING_CONFIG,
266
+ message: `Missing required parameters: ${missingParams.join(", ")}`,
267
+ severity: "error",
268
+ });
269
+ }
270
+ // eslint-disable-next-line react-hooks/exhaustive-deps
271
+ }, [channel, zone, subAccountToken, searchAPIToken]);
272
+
224
273
  useEffect(() => {
225
274
  const fetchPageData = async () => {
226
275
  // Should only fetch data if context is done fetching
@@ -231,17 +280,9 @@ const InternalMoneyLionOfferCarousel = (
231
280
  context: { ...context, isDev },
232
281
  params: getPageDataParams,
233
282
  onRateTableSubmit,
283
+ onError,
234
284
  });
235
285
 
236
- // Check for API errors in the response
237
- if (data.isError) {
238
- onError?.({
239
- code: data.errorCode,
240
- message: "Rate table error occurred",
241
- timestamp: new Date().toISOString(),
242
- });
243
- }
244
-
245
286
  onRateTableResponse?.({
246
287
  timestamp: new Date().toISOString(),
247
288
  isError: Boolean(data.isError),
@@ -257,8 +298,16 @@ const InternalMoneyLionOfferCarousel = (
257
298
  err instanceof Error ? err : new Error("Failed to fetch page data");
258
299
 
259
300
  onError?.({
260
- message: errorObj.message,
261
- timestamp: new Date().toISOString(),
301
+ message: "Failed to fetch page data",
302
+ error: errorObj,
303
+ additionalInfo: {
304
+ context,
305
+ pageDataParams: getPageDataParams,
306
+ },
307
+ ...(getApiErrorLogProperties(errorObj) || {
308
+ code: ErrorCodes.FLOW_ERROR,
309
+ severity: "error",
310
+ }),
262
311
  });
263
312
  } finally {
264
313
  setIsFetchingData(false);
@@ -343,15 +392,47 @@ export const MoneyLionOfferCarousel = (
343
392
  props: MoneyLionOfferCarouselProps &
344
393
  Omit<EventHandlerContextType, "rateTableUuid" | "leadUuid">
345
394
  ) => {
346
- const { fallbackUI, onError } = props;
395
+ const { fallbackUI, onError: _onError } = props;
396
+ const [context, setContext] = useState<CnfContext | null>(null);
397
+ const [pageData, setPageData] = useState<any>(null);
347
398
 
348
- const handleError = (err: CustomError): void => {
349
- onError?.(err);
350
- };
399
+ const onError = useCallback(
400
+ (err: InternalMoneyLionOfferCarouselError): void => {
401
+ _onError?.({
402
+ timestamp: new Date().toISOString(),
403
+ sdkVersion: VERSION,
404
+ ...err,
405
+ });
406
+ },
407
+ [_onError]
408
+ );
409
+
410
+ const logErrorBoundary = useCallback(
411
+ (error: Error) => {
412
+ onError?.({
413
+ code: ErrorCodes.UI_CRASH,
414
+ severity: "error",
415
+ message: "Error boundary crash",
416
+ error,
417
+ additionalInfo: {
418
+ context,
419
+ pageData,
420
+ },
421
+ });
422
+ },
423
+ [onError, pageData, context]
424
+ );
351
425
 
352
426
  return (
353
- <ErrorBoundary fallbackUI={fallbackUI} onError={handleError}>
354
- <InternalMoneyLionOfferCarousel {...props} />
427
+ <ErrorBoundary fallbackUI={fallbackUI} onError={logErrorBoundary}>
428
+ <InternalMoneyLionOfferCarousel
429
+ {...props}
430
+ context={context}
431
+ setContext={setContext}
432
+ pageData={pageData}
433
+ setPageData={setPageData}
434
+ onError={onError}
435
+ />
355
436
  </ErrorBoundary>
356
437
  );
357
438
  };
@@ -0,0 +1,70 @@
1
+ import { ErrorCodes } from "../../capabilities/errors/types";
2
+ import type { InternalMoneyLionOfferCarouselError } from "../../capabilities/errors/types";
3
+
4
+ /**
5
+ * Extracts HTTP status code from the custom Api Error object.
6
+ * Api Error object contains code field which is the HTTP status code.
7
+ *
8
+ * @param error - The error object (can be any type)
9
+ * @returns The HTTP status code if available, null otherwise
10
+ */
11
+ export const getCustomApiErrorStatusCode = (error: unknown): number | null => {
12
+ // Check if error is an object with code property (custom ApiError pattern)
13
+ if (error && typeof error === "object") {
14
+ if ("code" in error && typeof (error as any).code === "number") {
15
+ return (error as any).code;
16
+ }
17
+ }
18
+ return null;
19
+ };
20
+
21
+ /**
22
+ * Builds API error log properties from an error object.
23
+ * Checks if the error is a network error, and categorizes it as:
24
+ * - Client-side error (4xx status codes)
25
+ * - Server-side error (5xx status codes)
26
+ * - Other network error
27
+ *
28
+ * @param error - The error object (can be any type)
29
+ * @param message - The error message to include in the log properties
30
+ * @returns InternalMoneyLionOfferCarouselError object if it's a network error, null otherwise
31
+ */
32
+ export const getApiErrorLogProperties = (
33
+ error: Error | unknown
34
+ ): Omit<InternalMoneyLionOfferCarouselError, "message"> | null => {
35
+ const statusCode = getCustomApiErrorStatusCode(error);
36
+ const logProperties: Omit<
37
+ InternalMoneyLionOfferCarouselError,
38
+ "code" | "message"
39
+ > = {
40
+ severity: "error",
41
+ error,
42
+ };
43
+
44
+ if (statusCode) {
45
+ if (statusCode >= 400 && statusCode < 500) {
46
+ // 4xx - Request error (client-side)
47
+ return {
48
+ ...logProperties,
49
+ statusCode,
50
+ code: ErrorCodes.NETWORK_REQUEST_ERROR,
51
+ };
52
+ } else if (statusCode >= 500 && statusCode < 600) {
53
+ // 5xx - Server error
54
+ return {
55
+ ...logProperties,
56
+ statusCode,
57
+ code: ErrorCodes.NETWORK_SERVER_ERROR,
58
+ };
59
+ }
60
+ // Other status codes
61
+ return {
62
+ ...logProperties,
63
+ statusCode,
64
+ code: ErrorCodes.NETWORK_OTHER_ERROR,
65
+ };
66
+ }
67
+
68
+ // Not a network error (no status code and not a timeout)
69
+ return null;
70
+ };
@@ -3,7 +3,7 @@ import React, {
3
3
  type FC,
4
4
  type ReactNode,
5
5
  useContext,
6
- useState,
6
+ useMemo,
7
7
  } from "react";
8
8
  import { Platform } from "react-native";
9
9
 
@@ -69,9 +69,6 @@ interface ThemeContextType {
69
69
  theme: ThemeColors;
70
70
  fontFamily: FontFamily;
71
71
  isDarkTheme: boolean;
72
- updateTheme: (newTheme: Partial<ThemeColors>) => void;
73
- updateFontFamily: (newFontFamily: Partial<FontFamily>) => void;
74
- setIsDarkTheme: (isDark: boolean) => void;
75
72
  }
76
73
 
77
74
  export const ThemeContext = createContext<ThemeContextType | null>(null);
@@ -97,39 +94,22 @@ export const ThemeProvider: FC<ThemeProviderProps> = ({
97
94
  isDarkTheme = false,
98
95
  children,
99
96
  }) => {
100
- const [theme, setTheme] = useState<ThemeColors>(themeColors);
101
- const [fonts, setFonts] = useState<FontFamily>({
102
- ...defaultFontFamily,
103
- ...fontFamily,
104
- });
105
- const [darkTheme, setDarkTheme] = useState<boolean>(isDarkTheme);
106
-
107
- const updateTheme = (newTheme: Partial<ThemeColors>) => {
108
- setTheme((prevTheme) => ({
109
- ...prevTheme,
110
- ...newTheme,
111
- }));
112
- };
113
-
114
- const updateFontFamily = (newFontFamily: Partial<FontFamily>) => {
115
- setFonts((prevFonts) => ({
116
- ...prevFonts,
117
- ...newFontFamily,
118
- }));
119
- };
120
-
121
- const setIsDarkTheme = (isDark: boolean) => {
122
- setDarkTheme(isDark);
123
- };
97
+ const fonts = useMemo<FontFamily>(
98
+ () => ({
99
+ ...defaultFontFamily,
100
+ ...fontFamily,
101
+ }),
102
+ [fontFamily]
103
+ );
124
104
 
125
- const contextValue = {
126
- theme,
127
- fontFamily: fonts,
128
- isDarkTheme: darkTheme,
129
- updateTheme,
130
- updateFontFamily,
131
- setIsDarkTheme,
132
- };
105
+ const contextValue = useMemo<ThemeContextType>(
106
+ () => ({
107
+ theme: themeColors,
108
+ fontFamily: fonts,
109
+ isDarkTheme,
110
+ }),
111
+ [themeColors, fonts, isDarkTheme]
112
+ );
133
113
 
134
114
  return (
135
115
  <ThemeContext.Provider value={contextValue}>
package/src/pageData.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { UserData } from "./capabilities/configuration/src/userData/types";
2
2
  import type { CnfContext } from "./capabilities/core/src/system/cnfContext/CnfContext";
3
+ import type { InternalOnErrorCallback } from "./capabilities/errors/types";
3
4
  import { getIsDevEnvironment } from "./capabilities/offer-catalog/src/utils/contextUtil";
4
5
 
5
6
  import type { onRateTableSubmitProps } from "./context/EventHandlerProvider";
@@ -26,12 +27,14 @@ type GetPageDataProps = {
26
27
  context: CnfContext;
27
28
  params: GetPageDataParams;
28
29
  onRateTableSubmit?: (props: onRateTableSubmitProps) => void;
30
+ onError?: InternalOnErrorCallback;
29
31
  };
30
32
 
31
33
  export async function getPageData({
32
34
  context,
33
35
  params,
34
36
  onRateTableSubmit,
37
+ onError,
35
38
  }: GetPageDataProps) {
36
39
  const {
37
40
  productType,
@@ -69,6 +72,7 @@ export async function getPageData({
69
72
  query,
70
73
  searchAPIToken,
71
74
  isDev,
75
+ onError,
72
76
  });
73
77
 
74
78
  onRateTableSubmit?.({
@@ -90,6 +94,7 @@ export async function getPageData({
90
94
  context,
91
95
  defaultProductType,
92
96
  userData,
97
+ onError,
93
98
  });
94
99
 
95
100
  return {
@@ -17,6 +17,7 @@ import type {
17
17
  } from "../capabilities/offer-catalog/src";
18
18
  import type { UserData } from "../capabilities/configuration/src/userData/types";
19
19
  import type { CnfContext } from "../capabilities/core/src/system/cnfContext/CnfContext";
20
+ import type { InternalOnErrorCallback } from "../capabilities/errors/types";
20
21
 
21
22
  export type GetDynamicOffersProps = {
22
23
  tags: string;
@@ -27,6 +28,7 @@ export type GetDynamicOffersProps = {
27
28
  partnersOverrideDefinition: PartnerOverride[];
28
29
  context: CnfContext;
29
30
  userData?: UserData;
31
+ onError?: InternalOnErrorCallback;
30
32
  } & Omit<
31
33
  GetProductTypesProps,
32
34
  | "context"
@@ -53,6 +55,7 @@ export const getDynamicOffers = async ({
53
55
  partnersOverrideDefinition,
54
56
  context,
55
57
  userData,
58
+ onError,
56
59
  }: Omit<GetDynamicOffersProps, "isDev">) => {
57
60
  // Get the initial offers
58
61
  let { offers, rateTableUuid, isError, leadUuid, errorCode } =
@@ -63,6 +66,7 @@ export const getDynamicOffers = async ({
63
66
  context,
64
67
  userData,
65
68
  enablePolling: true,
69
+ onError,
66
70
  });
67
71
 
68
72
  // If no offers found, fall back to the default product type
@@ -73,6 +77,7 @@ export const getDynamicOffers = async ({
73
77
  tags,
74
78
  context,
75
79
  userData,
80
+ onError,
76
81
  });
77
82
 
78
83
  // Update the offers, rateTableUuid, isError, leafUuid and productTypes
@@ -1,3 +1,4 @@
1
+ import type { InternalOnErrorCallback } from "../capabilities/errors/types";
1
2
  import { getProductTypesByQuery } from "./getProductTypesByQuery";
2
3
 
3
4
  export type GetProductTypesProps = {
@@ -6,6 +7,7 @@ export type GetProductTypesProps = {
6
7
  defaultProductType: string;
7
8
  searchAPIToken: string;
8
9
  isDev: boolean;
10
+ onError?: InternalOnErrorCallback;
9
11
  };
10
12
 
11
13
  export type ResultType = "STATIC" | "QUERY" | "PRODUCT_TYPE" | "DEFAULT";
@@ -32,12 +34,14 @@ export const getProductTypes = async ({
32
34
  query,
33
35
  searchAPIToken,
34
36
  isDev,
37
+ onError,
35
38
  }: GetProductTypesProps): Promise<ProductTypesResult> => {
36
39
  if (query && query.trim().length > 0) {
37
40
  const productTypes = await getProductTypesByQuery({
38
41
  query,
39
42
  searchAPIToken,
40
43
  isDev,
44
+ onError,
41
45
  });
42
46
 
43
47
  return { productTypes, resultType: "QUERY" };
@@ -1,11 +1,18 @@
1
1
  import { pipe } from "effect";
2
2
  import { getEwsSearchApiBaseUrl } from "../apiEnvironment";
3
3
  import { orderProductTypesByRank } from "../capabilities/core/src/domain/third-party-search";
4
+ import {
5
+ ErrorCodes,
6
+ type ApiError,
7
+ type InternalOnErrorCallback,
8
+ } from "../capabilities/errors/types";
9
+ import { getApiErrorLogProperties } from "../components/utils/errorUtils";
4
10
 
5
11
  type GetProductTypesByQueryProps = {
6
12
  query: string;
7
13
  searchAPIToken: string;
8
14
  isDev: boolean;
15
+ onError?: InternalOnErrorCallback;
9
16
  };
10
17
 
11
18
  /**
@@ -33,6 +40,7 @@ export const getProductTypesByQuery = async ({
33
40
  query,
34
41
  searchAPIToken,
35
42
  isDev,
43
+ onError,
36
44
  }: GetProductTypesByQueryProps) => {
37
45
  const url = `${getEwsSearchApiBaseUrl(isDev)}/search/product-types?query=${encodeURIComponent(query)}`;
38
46
  const headers = {
@@ -41,13 +49,28 @@ export const getProductTypesByQuery = async ({
41
49
  try {
42
50
  const response = await fetch(url, { headers });
43
51
  if (!response.ok) {
44
- throw new Error();
52
+ const error = new Error() as ApiError;
53
+ error.code = response.status;
54
+ throw error;
45
55
  }
46
56
  const productTypes = await response.json();
47
57
 
48
58
  return pipe(productTypes, orderProductTypesByRank);
49
59
  } catch (error) {
50
60
  console.error("Error fetching ProductTypesByQuery", JSON.stringify(error));
61
+ onError?.({
62
+ message: "Failed to fetch product types by query",
63
+ error,
64
+ additionalInfo: {
65
+ query,
66
+ isDev,
67
+ },
68
+ ...(getApiErrorLogProperties(error) || {
69
+ code: ErrorCodes.FLOW_ERROR,
70
+ severity: "error",
71
+ }),
72
+ });
73
+
51
74
  return [];
52
75
  }
53
76
  };
@@ -1,5 +1,6 @@
1
1
  import type { UserData } from "../capabilities/configuration/src/userData/types";
2
2
  import type { CnfContext } from "../capabilities/core/src/system/cnfContext/CnfContext";
3
+ import type { InternalOnErrorCallback } from "../capabilities/errors/types";
3
4
  import {
4
5
  getCachedOffersByProductTypes,
5
6
  getOffersForProductTypes,
@@ -23,6 +24,7 @@ export const getOffersByProductTypes = async ({
23
24
  context,
24
25
  userData,
25
26
  enablePolling = false,
27
+ onError,
26
28
  }: {
27
29
  isCachedOffersRequest: boolean;
28
30
  productTypes: string[];
@@ -30,10 +32,11 @@ export const getOffersByProductTypes = async ({
30
32
  context: CnfContext;
31
33
  userData?: UserData;
32
34
  enablePolling?: boolean;
35
+ onError?: InternalOnErrorCallback;
33
36
  }) => {
34
37
  if (isCachedOffersRequest) {
35
38
  const { offers, uuid, isError, leadUuid, errorCode } =
36
- await getCachedOffersByProductTypes(context)(productTypes);
39
+ await getCachedOffersByProductTypes(context)(productTypes, onError);
37
40
 
38
41
  return { offers, rateTableUuid: uuid, isError, leadUuid, errorCode };
39
42
  }
@@ -43,7 +46,8 @@ export const getOffersByProductTypes = async ({
43
46
  productTypes,
44
47
  parseClientTags(tags),
45
48
  userData,
46
- enablePolling
49
+ enablePolling,
50
+ onError
47
51
  );
48
52
 
49
53
  return { offers, rateTableUuid: uuid, isError, leadUuid, errorCode };
@@ -1,3 +1,4 @@
1
+ import type { InternalOnErrorCallback } from "../capabilities/errors/types";
1
2
  import { getProductTypes, type ResultType } from "../services/getProductTypes";
2
3
 
3
4
  type ResolveProductTypesProps = {
@@ -6,6 +7,7 @@ type ResolveProductTypesProps = {
6
7
  defaultProductType: string;
7
8
  searchAPIToken: string;
8
9
  isDev: boolean;
10
+ onError?: InternalOnErrorCallback;
9
11
  };
10
12
 
11
13
  export const resolveProductTypes = async (