@moneylion/react-native-offer-carousel 1.2.0 → 1.2.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 (125) hide show
  1. package/lib/commonjs/capabilities/offer-catalog/src/utils/getEventCallbackContext.js +11 -0
  2. package/lib/commonjs/capabilities/offer-catalog/src/utils/getEventCallbackContext.js.map +1 -0
  3. package/lib/commonjs/components/Button/index.js +4 -2
  4. package/lib/commonjs/components/Button/index.js.map +1 -1
  5. package/lib/commonjs/components/Common/BaseOfferCard/Stat/Stat.js +1 -1
  6. package/lib/commonjs/components/Common/BaseOfferCard/Stat/Stat.js.map +1 -1
  7. package/lib/commonjs/components/Common/BaseOfferCard/index.js +15 -8
  8. package/lib/commonjs/components/Common/BaseOfferCard/index.js.map +1 -1
  9. package/lib/commonjs/components/Common/DynamicOfferCard/SeeMore.js +7 -6
  10. package/lib/commonjs/components/Common/DynamicOfferCard/SeeMore.js.map +1 -1
  11. package/lib/commonjs/components/Common/DynamicOfferCard/index.js +7 -3
  12. package/lib/commonjs/components/Common/DynamicOfferCard/index.js.map +1 -1
  13. package/lib/commonjs/components/DynamicOffers/DynamicOffers.js +10 -2
  14. package/lib/commonjs/components/DynamicOffers/DynamicOffers.js.map +1 -1
  15. package/lib/commonjs/components/DynamicOffers/Render/DynamicOffersRender.js +180 -63
  16. package/lib/commonjs/components/DynamicOffers/Render/DynamicOffersRender.js.map +1 -1
  17. package/lib/commonjs/components/DynamicOffers/Render/FallbackOfferTemplate.js +4 -2
  18. package/lib/commonjs/components/DynamicOffers/Render/FallbackOfferTemplate.js.map +1 -1
  19. package/lib/commonjs/components/DynamicOffers/Render/Offer.js +15 -5
  20. package/lib/commonjs/components/DynamicOffers/Render/Offer.js.map +1 -1
  21. package/lib/commonjs/components/DynamicOffers/SeeAllButton.js +30 -0
  22. package/lib/commonjs/components/DynamicOffers/SeeAllButton.js.map +1 -0
  23. package/lib/commonjs/components/Layouts/CreditCardOfferCard/index.js +9 -4
  24. package/lib/commonjs/components/Layouts/CreditCardOfferCard/index.js.map +1 -1
  25. package/lib/commonjs/components/Layouts/DefaultOfferCard/index.js +8 -3
  26. package/lib/commonjs/components/Layouts/DefaultOfferCard/index.js.map +1 -1
  27. package/lib/commonjs/components/Layouts/HeadlineWithDescriptionCard/index.js +8 -3
  28. package/lib/commonjs/components/Layouts/HeadlineWithDescriptionCard/index.js.map +1 -1
  29. package/lib/commonjs/components/Modal/AllOffersModal.js +180 -0
  30. package/lib/commonjs/components/Modal/AllOffersModal.js.map +1 -0
  31. package/lib/commonjs/components/Modal/OfferDetailsModal.js +4 -2
  32. package/lib/commonjs/components/Modal/OfferDetailsModal.js.map +1 -1
  33. package/lib/commonjs/components/MoneyLionOfferCarousel.js +2 -1
  34. package/lib/commonjs/components/MoneyLionOfferCarousel.js.map +1 -1
  35. package/lib/commonjs/context/EventHandlerProvider.js.map +1 -1
  36. package/lib/module/capabilities/offer-catalog/src/utils/getEventCallbackContext.js +4 -0
  37. package/lib/module/capabilities/offer-catalog/src/utils/getEventCallbackContext.js.map +1 -0
  38. package/lib/module/components/Button/index.js +4 -2
  39. package/lib/module/components/Button/index.js.map +1 -1
  40. package/lib/module/components/Common/BaseOfferCard/Stat/Stat.js +1 -1
  41. package/lib/module/components/Common/BaseOfferCard/Stat/Stat.js.map +1 -1
  42. package/lib/module/components/Common/BaseOfferCard/index.js +16 -9
  43. package/lib/module/components/Common/BaseOfferCard/index.js.map +1 -1
  44. package/lib/module/components/Common/DynamicOfferCard/SeeMore.js +7 -6
  45. package/lib/module/components/Common/DynamicOfferCard/SeeMore.js.map +1 -1
  46. package/lib/module/components/Common/DynamicOfferCard/index.js +7 -3
  47. package/lib/module/components/Common/DynamicOfferCard/index.js.map +1 -1
  48. package/lib/module/components/DynamicOffers/DynamicOffers.js +10 -2
  49. package/lib/module/components/DynamicOffers/DynamicOffers.js.map +1 -1
  50. package/lib/module/components/DynamicOffers/Render/DynamicOffersRender.js +180 -63
  51. package/lib/module/components/DynamicOffers/Render/DynamicOffersRender.js.map +1 -1
  52. package/lib/module/components/DynamicOffers/Render/FallbackOfferTemplate.js +4 -2
  53. package/lib/module/components/DynamicOffers/Render/FallbackOfferTemplate.js.map +1 -1
  54. package/lib/module/components/DynamicOffers/Render/Offer.js +15 -5
  55. package/lib/module/components/DynamicOffers/Render/Offer.js.map +1 -1
  56. package/lib/module/components/DynamicOffers/SeeAllButton.js +22 -0
  57. package/lib/module/components/DynamicOffers/SeeAllButton.js.map +1 -0
  58. package/lib/module/components/Layouts/CreditCardOfferCard/index.js +9 -4
  59. package/lib/module/components/Layouts/CreditCardOfferCard/index.js.map +1 -1
  60. package/lib/module/components/Layouts/DefaultOfferCard/index.js +8 -3
  61. package/lib/module/components/Layouts/DefaultOfferCard/index.js.map +1 -1
  62. package/lib/module/components/Layouts/HeadlineWithDescriptionCard/index.js +8 -3
  63. package/lib/module/components/Layouts/HeadlineWithDescriptionCard/index.js.map +1 -1
  64. package/lib/module/components/Modal/AllOffersModal.js +170 -0
  65. package/lib/module/components/Modal/AllOffersModal.js.map +1 -0
  66. package/lib/module/components/Modal/OfferDetailsModal.js +4 -2
  67. package/lib/module/components/Modal/OfferDetailsModal.js.map +1 -1
  68. package/lib/module/components/MoneyLionOfferCarousel.js +2 -1
  69. package/lib/module/components/MoneyLionOfferCarousel.js.map +1 -1
  70. package/lib/module/context/EventHandlerProvider.js.map +1 -1
  71. package/lib/typescript/src/capabilities/offer-catalog/src/utils/getEventCallbackContext.d.ts +2 -0
  72. package/lib/typescript/src/capabilities/offer-catalog/src/utils/getEventCallbackContext.d.ts.map +1 -0
  73. package/lib/typescript/src/components/Button/index.d.ts +3 -0
  74. package/lib/typescript/src/components/Button/index.d.ts.map +1 -1
  75. package/lib/typescript/src/components/Common/BaseOfferCard/index.d.ts +2 -1
  76. package/lib/typescript/src/components/Common/BaseOfferCard/index.d.ts.map +1 -1
  77. package/lib/typescript/src/components/Common/DynamicOfferCard/CallToAction.d.ts +1 -1
  78. package/lib/typescript/src/components/Common/DynamicOfferCard/CallToAction.d.ts.map +1 -1
  79. package/lib/typescript/src/components/Common/DynamicOfferCard/SeeMore.d.ts +2 -1
  80. package/lib/typescript/src/components/Common/DynamicOfferCard/SeeMore.d.ts.map +1 -1
  81. package/lib/typescript/src/components/Common/DynamicOfferCard/index.d.ts +3 -1
  82. package/lib/typescript/src/components/Common/DynamicOfferCard/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/DynamicOffers/DynamicOffers.d.ts +2 -2
  84. package/lib/typescript/src/components/DynamicOffers/DynamicOffers.d.ts.map +1 -1
  85. package/lib/typescript/src/components/DynamicOffers/Render/DynamicOffersRender.d.ts +8 -1
  86. package/lib/typescript/src/components/DynamicOffers/Render/DynamicOffersRender.d.ts.map +1 -1
  87. package/lib/typescript/src/components/DynamicOffers/Render/FallbackOfferTemplate.d.ts +2 -1
  88. package/lib/typescript/src/components/DynamicOffers/Render/FallbackOfferTemplate.d.ts.map +1 -1
  89. package/lib/typescript/src/components/DynamicOffers/Render/Offer.d.ts +7 -2
  90. package/lib/typescript/src/components/DynamicOffers/Render/Offer.d.ts.map +1 -1
  91. package/lib/typescript/src/components/DynamicOffers/SeeAllButton.d.ts +7 -0
  92. package/lib/typescript/src/components/DynamicOffers/SeeAllButton.d.ts.map +1 -0
  93. package/lib/typescript/src/components/Layouts/CreditCardOfferCard/index.d.ts +3 -1
  94. package/lib/typescript/src/components/Layouts/CreditCardOfferCard/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/Layouts/DefaultOfferCard/index.d.ts +3 -1
  96. package/lib/typescript/src/components/Layouts/DefaultOfferCard/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/Layouts/HeadlineWithDescriptionCard/index.d.ts +3 -1
  98. package/lib/typescript/src/components/Layouts/HeadlineWithDescriptionCard/index.d.ts.map +1 -1
  99. package/lib/typescript/src/components/Modal/AllOffersModal.d.ts +10 -0
  100. package/lib/typescript/src/components/Modal/AllOffersModal.d.ts.map +1 -0
  101. package/lib/typescript/src/components/Modal/OfferDetailsModal.d.ts +2 -1
  102. package/lib/typescript/src/components/Modal/OfferDetailsModal.d.ts.map +1 -1
  103. package/lib/typescript/src/components/MoneyLionOfferCarousel.d.ts.map +1 -1
  104. package/lib/typescript/src/context/EventHandlerProvider.d.ts +3 -1
  105. package/lib/typescript/src/context/EventHandlerProvider.d.ts.map +1 -1
  106. package/package.json +1 -1
  107. package/src/capabilities/offer-catalog/src/utils/getEventCallbackContext.ts +3 -0
  108. package/src/components/Button/index.tsx +7 -3
  109. package/src/components/Common/BaseOfferCard/Stat/Stat.tsx +1 -1
  110. package/src/components/Common/BaseOfferCard/index.tsx +19 -14
  111. package/src/components/Common/DynamicOfferCard/CallToAction.tsx +1 -1
  112. package/src/components/Common/DynamicOfferCard/SeeMore.tsx +17 -4
  113. package/src/components/Common/DynamicOfferCard/index.tsx +10 -2
  114. package/src/components/DynamicOffers/DynamicOffers.tsx +16 -1
  115. package/src/components/DynamicOffers/Render/DynamicOffersRender.tsx +227 -75
  116. package/src/components/DynamicOffers/Render/FallbackOfferTemplate.tsx +3 -0
  117. package/src/components/DynamicOffers/Render/Offer.tsx +17 -0
  118. package/src/components/DynamicOffers/SeeAllButton.tsx +31 -0
  119. package/src/components/Layouts/CreditCardOfferCard/index.tsx +15 -3
  120. package/src/components/Layouts/DefaultOfferCard/index.tsx +13 -1
  121. package/src/components/Layouts/HeadlineWithDescriptionCard/index.tsx +13 -1
  122. package/src/components/Modal/AllOffersModal.tsx +213 -0
  123. package/src/components/Modal/OfferDetailsModal.tsx +4 -1
  124. package/src/components/MoneyLionOfferCarousel.tsx +1 -0
  125. package/src/context/EventHandlerProvider.tsx +3 -1
@@ -20,6 +20,10 @@ import {
20
20
  import { useEventHandler } from "../../../context/EventHandlerProvider";
21
21
  import type { BaseOffer } from "../../../capabilities/offer-catalog/src";
22
22
  import Text from "../../Text";
23
+ import { SeeAllButton } from "../SeeAllButton";
24
+ import type { CnfContext } from "../../../capabilities/core/src/system/cnfContext/CnfContext";
25
+ import { AllOffersModal } from "../../Modal/AllOffersModal";
26
+ import { getEventCallbackContext } from "../../../capabilities/offer-catalog/src/utils/getEventCallbackContext";
23
27
 
24
28
  export type DynamicOffersRenderProps = {
25
29
  title?: string;
@@ -34,6 +38,12 @@ export type DynamicOffersRenderProps = {
34
38
  productTypeBuilder: typeof builder;
35
39
  showCardBorder: boolean;
36
40
  showProductTypeLabel?: boolean;
41
+ isHorizontalScroll?: boolean;
42
+ /** Used for vertical scroll only */
43
+ parentScrollOffset?: number;
44
+ /** Used for vertical scroll only */
45
+ parentScrollViewHeight?: number;
46
+ brand: CnfContext["brand"];
37
47
  };
38
48
 
39
49
  export const DynamicOffersRender = ({
@@ -45,13 +55,18 @@ export const DynamicOffersRender = ({
45
55
  fallbackTemplate,
46
56
  showCardBorder,
47
57
  showProductTypeLabel,
58
+ isHorizontalScroll = true,
59
+ parentScrollOffset = 0,
60
+ parentScrollViewHeight = 0,
61
+ brand,
48
62
  }: DynamicOffersRenderProps) => {
49
63
  // Performance optimization: store card measurements
50
64
  const [cardLayouts, setCardLayouts] = useState<
51
- Record<string, { x: number; width: number }>
65
+ Record<string, { x: number; width: number; y: number; height: number }>
52
66
  >({});
53
67
  const [scrollViewWidth, setScrollViewWidth] = useState(0);
54
68
  const [scrollOffset, setScrollOffset] = useState(0);
69
+ const [allOffersModalVisible, setAllOffersModalVisible] = useState(false);
55
70
 
56
71
  // Callback to fire when offer is displayed in viewport
57
72
  const { onOfferDisplayInViewport, leadUuid, rateTableUuid } =
@@ -70,86 +85,158 @@ export const DynamicOffersRender = ({
70
85
  setCardLayouts({});
71
86
  }, [offers]);
72
87
 
73
- // Handle card layout measurements
74
88
  const handleCardLayout = useCallback(
75
89
  (offerId: string, event: LayoutChangeEvent) => {
76
- const { x, width } = event.nativeEvent.layout;
90
+ const { x, y, width, height } = event.nativeEvent.layout;
77
91
 
78
92
  setCardLayouts((prev) => {
93
+ const positionValue = isHorizontalScroll ? x : y;
94
+ const sizeValue = isHorizontalScroll ? width : height;
95
+
79
96
  // Only update if measurements changed
80
97
  if (
81
98
  prev[offerId] &&
82
- prev[offerId]?.x === x &&
83
- prev[offerId]?.width === width
99
+ prev[offerId]?.x === positionValue &&
100
+ prev[offerId]?.width === sizeValue
84
101
  ) {
85
102
  return prev;
86
103
  }
87
- return { ...prev, [offerId]: { x, width } };
104
+
105
+ return {
106
+ ...prev,
107
+ [offerId]: {
108
+ x: positionValue,
109
+ width: sizeValue,
110
+ y: y,
111
+ height: height,
112
+ },
113
+ };
88
114
  });
89
115
  },
90
- []
116
+ [isHorizontalScroll]
91
117
  );
92
118
 
93
119
  // Check which offers are visible
94
120
  const checkVisibleOffers = useCallback(() => {
95
- if (!onOfferDisplayInViewport || !scrollViewWidth) return;
96
-
97
- // Calculate visible range
98
- const visibleLeft = scrollOffset;
99
- const visibleRight = scrollOffset + scrollViewWidth;
100
-
101
- // Check each offer's visibility
102
- offers.forEach((offer, index) => {
103
- // Skip if already tracked
104
- if (viewedOffers.current.has(offer.uuid)) return;
105
-
106
- const layout = cardLayouts[offer.uuid];
107
- if (!layout) return;
108
-
109
- // Calculate card position
110
- const cardLeft = layout.x;
111
- const cardRight = cardLeft + layout.width;
112
- const cardWidth = layout.width;
113
-
114
- // Calculate visibility
115
- let visibleWidth = 0;
116
-
117
- if (cardLeft >= visibleLeft && cardRight <= visibleRight) {
118
- // Fully visible
119
- visibleWidth = cardWidth;
120
- } else if (cardLeft < visibleLeft && cardRight > visibleLeft) {
121
- // Partly visible from left
122
- visibleWidth = cardRight - visibleLeft;
123
- } else if (cardLeft < visibleRight && cardRight > visibleRight) {
124
- // Partly visible from right
125
- visibleWidth = visibleRight - cardLeft;
126
- }
121
+ if (!onOfferDisplayInViewport) return;
122
+
123
+ if (isHorizontalScroll) {
124
+ if (!scrollViewWidth) return;
125
+
126
+ // Calculate visible range for horizontal scrolling
127
+ const visibleLeft = scrollOffset;
128
+ const visibleRight = scrollOffset + scrollViewWidth;
129
+
130
+ // Check each offer's visibility
131
+ offers.forEach((offer, index) => {
132
+ // Skip if already tracked
133
+ if (viewedOffers.current.has(offer.uuid)) return;
134
+
135
+ const layout = cardLayouts[offer.uuid];
136
+ if (!layout) return;
137
+
138
+ // Calculate card position
139
+ const cardLeft = layout.x;
140
+ const cardRight = cardLeft + layout.width;
141
+ const cardWidth = layout.width;
142
+
143
+ // Calculate visibility
144
+ let visibleWidth = 0;
145
+
146
+ if (cardLeft >= visibleLeft && cardRight <= visibleRight) {
147
+ // Fully visible
148
+ visibleWidth = cardWidth;
149
+ } else if (cardLeft < visibleLeft && cardRight > visibleLeft) {
150
+ // Partly visible from left
151
+ visibleWidth = cardRight - visibleLeft;
152
+ } else if (cardLeft < visibleRight && cardRight > visibleRight) {
153
+ // Partly visible from right
154
+ visibleWidth = visibleRight - cardLeft;
155
+ }
156
+
157
+ // Calculate visibility percentage
158
+ const visibilityPercentage = (visibleWidth / cardWidth) * 100;
159
+
160
+ // If ≥50% visible, track it and fire callback
161
+ if (visibilityPercentage >= 50) {
162
+ viewedOffers.current.add(offer.uuid);
163
+
164
+ onOfferDisplayInViewport({
165
+ timestamp: new Date().toISOString(),
166
+ offerUuid: offer.uuid,
167
+ leadUuid,
168
+ offer,
169
+ offerIndex: index,
170
+ rateTableUuid,
171
+ context: getEventCallbackContext(isHorizontalScroll),
172
+ });
173
+ }
174
+ });
175
+ } else {
176
+ // For vertical scrolling with parent scroll view
177
+ if (parentScrollViewHeight > 0) {
178
+ // Calculate visible range for vertical scrolling
179
+ const visibleTop = parentScrollOffset;
180
+ const visibleBottom = parentScrollOffset + parentScrollViewHeight;
181
+
182
+ // Check each offer's visibility
183
+ offers.forEach((offer, index) => {
184
+ // Skip if already tracked
185
+ if (viewedOffers.current.has(offer.uuid)) return;
186
+
187
+ const layout = cardLayouts[offer.uuid];
188
+ if (!layout) return;
189
+
190
+ // Calculate card position
191
+ const cardTop = layout.y;
192
+ const cardHeight = layout.height;
193
+ const cardBottom = cardTop + cardHeight;
194
+
195
+ // Calculate visibility
196
+ let visibleHeight = 0;
127
197
 
128
- // Calculate visibility percentage
129
- const visibilityPercentage = (visibleWidth / cardWidth) * 100;
198
+ if (cardTop >= visibleTop && cardBottom <= visibleBottom) {
199
+ // Fully visible
200
+ visibleHeight = cardHeight;
201
+ } else if (cardTop < visibleTop && cardBottom > visibleTop) {
202
+ // Partly visible from top
203
+ visibleHeight = cardBottom - visibleTop;
204
+ } else if (cardTop < visibleBottom && cardBottom > visibleBottom) {
205
+ // Partly visible from bottom
206
+ visibleHeight = visibleBottom - cardTop;
207
+ }
130
208
 
131
- // If ≥50% visible, track it and fire callback
132
- if (visibilityPercentage >= 50) {
133
- viewedOffers.current.add(offer.uuid);
209
+ // Calculate visibility percentage
210
+ const visibilityPercentage = (visibleHeight / cardHeight) * 100;
134
211
 
135
- onOfferDisplayInViewport({
136
- timestamp: new Date().toISOString(),
137
- offerUuid: offer.uuid,
138
- leadUuid,
139
- offer,
140
- offerIndex: index,
141
- rateTableUuid,
212
+ // If ≥50% visible, track it and fire callback
213
+ if (visibilityPercentage >= 50) {
214
+ viewedOffers.current.add(offer.uuid);
215
+
216
+ onOfferDisplayInViewport({
217
+ timestamp: new Date().toISOString(),
218
+ offerUuid: offer.uuid,
219
+ leadUuid,
220
+ offer,
221
+ offerIndex: index,
222
+ rateTableUuid,
223
+ context: getEventCallbackContext(isHorizontalScroll),
224
+ });
225
+ }
142
226
  });
143
227
  }
144
- });
228
+ }
145
229
  }, [
146
230
  cardLayouts,
231
+ isHorizontalScroll,
147
232
  leadUuid,
148
233
  offers,
149
234
  onOfferDisplayInViewport,
150
235
  rateTableUuid,
151
236
  scrollOffset,
152
237
  scrollViewWidth,
238
+ parentScrollOffset,
239
+ parentScrollViewHeight,
153
240
  ]);
154
241
 
155
242
  // Handle ScrollView layout
@@ -178,6 +265,8 @@ export const DynamicOffersRender = ({
178
265
 
179
266
  // Memoize offer components to prevent unnecessary re-renders
180
267
  const offerComponents = useMemo(() => {
268
+ const fullCardWidth = offers.length === 1 || !isHorizontalScroll;
269
+
181
270
  // Create offer components
182
271
  return displayLayout === "fixed"
183
272
  ? [
@@ -188,6 +277,8 @@ export const DynamicOffersRender = ({
188
277
  showProductTypeLabel,
189
278
  fallbackTemplate,
190
279
  showCardBorder,
280
+ fullCardWidth,
281
+ isHorizontalScroll,
191
282
  }),
192
283
  ]
193
284
  : Offer({
@@ -196,6 +287,8 @@ export const DynamicOffersRender = ({
196
287
  showProductTypeLabel,
197
288
  fallbackTemplate,
198
289
  showCardBorder,
290
+ fullCardWidth,
291
+ isHorizontalScroll,
199
292
  });
200
293
  }, [
201
294
  displayLayout,
@@ -204,6 +297,7 @@ export const DynamicOffersRender = ({
204
297
  showProductTypeLabel,
205
298
  fallbackTemplate,
206
299
  showCardBorder,
300
+ isHorizontalScroll,
207
301
  ]);
208
302
 
209
303
  // Wrapped offer components with layout measurement
@@ -236,49 +330,107 @@ export const DynamicOffersRender = ({
236
330
  return () => clearTimeout(timeoutId);
237
331
  }, [checkVisibleOffers, scrollOffset, scrollViewWidth, cardLayouts]);
238
332
 
333
+ useEffect(() => {
334
+ if (parentScrollOffset !== undefined) {
335
+ // Check visibility when parent scrolls
336
+ checkVisibleOffers();
337
+ }
338
+ }, [parentScrollOffset, checkVisibleOffers]);
339
+
239
340
  const showTitle = Boolean(title?.trim());
240
341
  const showSubtitle = Boolean(subtitle?.trim());
241
342
  const showHeaderText = showTitle || showSubtitle;
343
+ const showHeader = isHorizontalScroll;
344
+
345
+ const onPressSeeAll = () => setAllOffersModalVisible(true);
346
+ const onCloseModal = () => setAllOffersModalVisible(false);
347
+
348
+ const config = {
349
+ title,
350
+ subtitle,
351
+ displayLayout,
352
+ showCardBorder,
353
+ showProductTypeLabel,
354
+ shouldHideFooter: false,
355
+ isHorizontalScroll: false,
356
+ offers,
357
+ brand,
358
+ };
242
359
 
243
360
  return (
244
361
  <>
245
- <View style={styles.header}>
246
- {showHeaderText && (
247
- <View style={styles.headerTextContainer}>
248
- {showTitle && (
249
- <Text variant="featured-3" weight="bold">
250
- {title}
251
- </Text>
252
- )}
253
- {showSubtitle && <Text variant="body-3">{subtitle}</Text>}
254
- </View>
255
- )}
256
- </View>
257
- <ScrollView
258
- horizontal={true}
259
- showsHorizontalScrollIndicator={false}
260
- contentContainerStyle={styles.contentContainer}
261
- onLayout={handleScrollViewLayout}
262
- onScroll={handleScroll}
263
- scrollEventThrottle={16} // Lower number for more precision, higher for better performance
264
- >
265
- {wrappedOfferComponents}
266
- </ScrollView>
362
+ {showHeader && (
363
+ <View
364
+ style={[
365
+ styles.header,
366
+ { alignItems: showSubtitle ? "flex-start" : "center" },
367
+ ]}
368
+ >
369
+ {showHeaderText && (
370
+ <View style={styles.headerTextContainer}>
371
+ {showTitle && (
372
+ <Text variant="featured-3" weight="bold">
373
+ {title}
374
+ </Text>
375
+ )}
376
+ {showSubtitle && (
377
+ <Text variant="body-3" color="foregroundNeutralFaded">
378
+ {subtitle}
379
+ </Text>
380
+ )}
381
+ </View>
382
+ )}
383
+ <SeeAllButton onPress={onPressSeeAll} />
384
+ <AllOffersModal
385
+ visible={allOffersModalVisible}
386
+ onClose={onCloseModal}
387
+ config={config}
388
+ />
389
+ </View>
390
+ )}
391
+ {isHorizontalScroll ? (
392
+ <ScrollView
393
+ horizontal={true}
394
+ showsHorizontalScrollIndicator={false}
395
+ contentContainerStyle={styles.contentContainer}
396
+ style={styles.scrollView}
397
+ onLayout={handleScrollViewLayout}
398
+ onScroll={handleScroll}
399
+ scrollEventThrottle={16} // Lower number for more precision, higher for better performance
400
+ >
401
+ {wrappedOfferComponents}
402
+ </ScrollView>
403
+ ) : (
404
+ // For vertical scroll, parent component will handle the scroll.
405
+ // Event callback triggers will happen in the parent component.
406
+ <View
407
+ style={[styles.contentContainer, { flex: 1 }]}
408
+ onLayout={handleScrollViewLayout}
409
+ >
410
+ {wrappedOfferComponents}
411
+ </View>
412
+ )}
267
413
  </>
268
414
  );
269
415
  };
270
416
 
271
417
  const styles = StyleSheet.create({
272
418
  contentContainer: {
419
+ // for horizontal scroll
273
420
  columnGap: 20,
274
421
  paddingHorizontal: 16,
422
+
423
+ // for all offers vertical scroll
424
+ rowGap: 16,
425
+ },
426
+ scrollView: {
427
+ flex: 1,
275
428
  },
276
429
  header: {
277
430
  marginHorizontal: 16,
278
431
  marginBottom: 16,
279
432
  flexDirection: "row",
280
433
  justifyContent: "space-between",
281
- alignItems: "center",
282
434
  },
283
435
  headerTextContainer: {
284
436
  flex: 1,
@@ -7,11 +7,13 @@ export function FallbackOfferTemplate({
7
7
  offerIndex,
8
8
  showCardBorder = true,
9
9
  showProductTypeLabel = true,
10
+ isHorizontalScroll = true,
10
11
  }: {
11
12
  offer: BaseOffer;
12
13
  offerIndex: number;
13
14
  showCardBorder?: boolean;
14
15
  showProductTypeLabel?: boolean;
16
+ isHorizontalScroll?: boolean;
15
17
  }) {
16
18
  return (
17
19
  <HeadlineWithDescriptionCard
@@ -24,6 +26,7 @@ export function FallbackOfferTemplate({
24
26
  }}
25
27
  showCardBorder={showCardBorder}
26
28
  showProductTypeLabel={showProductTypeLabel}
29
+ isHorizontalScroll={isHorizontalScroll}
27
30
  />
28
31
  );
29
32
  }
@@ -18,14 +18,21 @@ type RenderOfferListProps = {
18
18
  offerIndex,
19
19
  showProductTypeLabel,
20
20
  showCardBorder,
21
+ fullCardWidth,
22
+ isHorizontalScroll,
21
23
  }: {
22
24
  offer: BaseOffer;
23
25
  offerIndex: number;
24
26
  showProductTypeLabel?: boolean;
25
27
  showCardBorder?: boolean;
28
+ fullCardWidth?: boolean;
29
+ isHorizontalScroll?: boolean;
26
30
  }) => React.ReactNode;
27
31
  showCardBorder?: boolean;
28
32
  showProductTypeLabel?: boolean;
33
+ /** Offer card should be full width when offers.length === 1 in horizontal scroll or offers are scrolled vertically (when !isHorizontalScroll is true) */
34
+ fullCardWidth?: boolean;
35
+ isHorizontalScroll?: boolean;
29
36
  // featureClient?: CnfFeatureClient; TODO: this is the like split on cnf side it uses growthbook
30
37
  };
31
38
 
@@ -35,6 +42,8 @@ export const Offer = ({
35
42
  showProductTypeLabel = true,
36
43
  fallbackTemplate,
37
44
  showCardBorder = true,
45
+ fullCardWidth = false,
46
+ isHorizontalScroll = true,
38
47
  }: RenderOfferListProps) =>
39
48
  offers.map((offer: BaseOffer, offerIndex: number) => {
40
49
  const builder = productTypeBuilder.find(({ productTypes }) =>
@@ -60,6 +69,8 @@ export const Offer = ({
60
69
  productTypeBuilder={layout}
61
70
  showCardBorder={showCardBorder}
62
71
  showProductTypeLabel={showProductTypeLabel}
72
+ fullCardWidth={fullCardWidth}
73
+ isHorizontalScroll={isHorizontalScroll}
63
74
  />
64
75
  ))
65
76
  .with({ layout: "creditCardOfferCard" }, (layout) => (
@@ -70,6 +81,8 @@ export const Offer = ({
70
81
  productTypeBuilder={layout}
71
82
  showCardBorder={showCardBorder}
72
83
  showProductTypeLabel={showProductTypeLabel}
84
+ fullCardWidth={fullCardWidth}
85
+ isHorizontalScroll={isHorizontalScroll}
73
86
  />
74
87
  ))
75
88
  .with({ layout: "headlineWithDescriptionCard" }, (layout) => (
@@ -80,6 +93,8 @@ export const Offer = ({
80
93
  productTypeBuilder={layout}
81
94
  showCardBorder={showCardBorder}
82
95
  showProductTypeLabel={showProductTypeLabel}
96
+ fullCardWidth={fullCardWidth}
97
+ isHorizontalScroll={isHorizontalScroll}
83
98
  />
84
99
  ))
85
100
  .with(undefined, () => {
@@ -94,6 +109,8 @@ export const Offer = ({
94
109
  key={`${offerIndex}-${offer.uuid}`}
95
110
  offer={offer}
96
111
  showProductTypeLabel={showProductTypeLabel}
112
+ fullCardWidth={fullCardWidth}
113
+ isHorizontalScroll={isHorizontalScroll}
97
114
  />
98
115
  );
99
116
  })
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { StyleSheet } from "react-native";
3
+ import Button from "../Button";
4
+
5
+ interface SeeAllButtonProps {
6
+ onPress: () => void;
7
+ }
8
+
9
+ export const SeeAllButton = ({ onPress }: SeeAllButtonProps) => {
10
+ return (
11
+ <>
12
+ <Button
13
+ color={"backgroundNeutral"}
14
+ textVariant={"body-3"}
15
+ textFontWeight={"bold"}
16
+ style={styles.seeAllButton}
17
+ onPress={onPress}
18
+ >
19
+ {"See all"}
20
+ </Button>
21
+ </>
22
+ );
23
+ };
24
+
25
+ const styles = StyleSheet.create({
26
+ seeAllButton: {
27
+ height: "auto",
28
+ paddingVertical: 8,
29
+ paddingHorizontal: 12,
30
+ },
31
+ });
@@ -9,6 +9,7 @@ import Text from "../../Text";
9
9
  import { CallToAction } from "../../Common/DynamicOfferCard/CallToAction";
10
10
  import { StyleSheet } from "react-native";
11
11
  import type { BaseOffer } from "../../../capabilities/offer-catalog/src";
12
+ import { getEventCallbackContext } from "../../../capabilities/offer-catalog/src/utils/getEventCallbackContext";
12
13
 
13
14
  type CreditCardOfferCardProps = {
14
15
  productTypeBuilder: CreditCardOfferCardBuilderProps;
@@ -16,6 +17,8 @@ type CreditCardOfferCardProps = {
16
17
  showCardBorder: boolean;
17
18
  showProductTypeLabel?: boolean;
18
19
  offerIndex: number;
20
+ fullCardWidth?: boolean;
21
+ isHorizontalScroll?: boolean;
19
22
  };
20
23
 
21
24
  export const CreditCardOfferCard = ({
@@ -24,6 +27,8 @@ export const CreditCardOfferCard = ({
24
27
  showCardBorder,
25
28
  showProductTypeLabel,
26
29
  offerIndex,
30
+ fullCardWidth = false,
31
+ isHorizontalScroll = true,
27
32
  }: CreditCardOfferCardProps) => {
28
33
  const { stats } = makeCreditCardOfferStats(productTypeBuilder)(offer);
29
34
  const descriptionPoints = get(offer, "descriptionPoints", []);
@@ -33,7 +38,10 @@ export const CreditCardOfferCard = ({
33
38
  const showBadge = badges.length > 0;
34
39
 
35
40
  return (
36
- <BaseOfferCard showCardBorder={showCardBorder}>
41
+ <BaseOfferCard
42
+ showCardBorder={showCardBorder}
43
+ fullCardWidth={fullCardWidth}
44
+ >
37
45
  {showProductTypeLabel && (
38
46
  <BaseOfferCard.TopBar>
39
47
  <BaseOfferCard.ProductType>
@@ -66,7 +74,11 @@ export const CreditCardOfferCard = ({
66
74
  />
67
75
  ))}
68
76
  {showMoreInfo && (
69
- <SeeMore offer={offer} offerIndex={offerIndex} />
77
+ <SeeMore
78
+ offer={offer}
79
+ offerIndex={offerIndex}
80
+ isHorizontalScroll={isHorizontalScroll}
81
+ />
70
82
  )}
71
83
  </BaseOfferCard.Stats>
72
84
  </BaseOfferCard.Content>
@@ -78,7 +90,7 @@ export const CreditCardOfferCard = ({
78
90
  offer={offer}
79
91
  overrideUrl={offer.overrideUrl}
80
92
  style={styles.cta}
81
- context="offer"
93
+ context={getEventCallbackContext(isHorizontalScroll)}
82
94
  offerIndex={offerIndex}
83
95
  >
84
96
  Continue
@@ -13,6 +13,8 @@ type DefaultOfferCardProps = {
13
13
  offerIndex: number;
14
14
  showCardBorder: boolean;
15
15
  showProductTypeLabel?: boolean;
16
+ fullCardWidth?: boolean;
17
+ isHorizontalScroll?: boolean;
16
18
  };
17
19
 
18
20
  export const DefaultOfferCard = ({
@@ -21,6 +23,8 @@ export const DefaultOfferCard = ({
21
23
  offerIndex,
22
24
  showCardBorder,
23
25
  showProductTypeLabel,
26
+ fullCardWidth = false,
27
+ isHorizontalScroll = true,
24
28
  }: DefaultOfferCardProps) => {
25
29
  const { stats, topStats } =
26
30
  makeDefaultOfferCardStats(productTypeBuilder)(offer);
@@ -41,6 +45,8 @@ export const DefaultOfferCard = ({
41
45
  offerIndex={offerIndex}
42
46
  showCardBorder={showCardBorder}
43
47
  showProductTypeLabel={showProductTypeLabel}
48
+ fullCardWidth={fullCardWidth}
49
+ isHorizontalScroll={isHorizontalScroll}
44
50
  >
45
51
  <BaseOfferCard.TopContainer>
46
52
  {topStats.map((stat) => (
@@ -67,7 +73,13 @@ export const DefaultOfferCard = ({
67
73
  contentDescription={contentDescription}
68
74
  />
69
75
  )}
70
- {showMoreInfo && <SeeMore offer={offer} offerIndex={offerIndex} />}
76
+ {showMoreInfo && (
77
+ <SeeMore
78
+ offer={offer}
79
+ offerIndex={offerIndex}
80
+ isHorizontalScroll={isHorizontalScroll}
81
+ />
82
+ )}
71
83
  </BaseOfferCard.Stats>
72
84
  </DynamicOfferCard>
73
85
  );
@@ -13,6 +13,8 @@ type HeadlineWithDescriptionCardProps = {
13
13
  offerIndex: number;
14
14
  showCardBorder: boolean;
15
15
  showProductTypeLabel?: boolean;
16
+ fullCardWidth?: boolean;
17
+ isHorizontalScroll?: boolean;
16
18
  };
17
19
  export const HeadlineWithDescriptionCard = ({
18
20
  offer,
@@ -20,6 +22,8 @@ export const HeadlineWithDescriptionCard = ({
20
22
  offerIndex,
21
23
  showCardBorder,
22
24
  showProductTypeLabel,
25
+ fullCardWidth = false,
26
+ isHorizontalScroll = true,
23
27
  }: HeadlineWithDescriptionCardProps) => {
24
28
  const {
25
29
  headlineField = "headline",
@@ -45,6 +49,8 @@ export const HeadlineWithDescriptionCard = ({
45
49
  offerIndex={offerIndex}
46
50
  showCardBorder={showCardBorder}
47
51
  showProductTypeLabel={showProductTypeLabel}
52
+ fullCardWidth={fullCardWidth}
53
+ isHorizontalScroll={isHorizontalScroll}
48
54
  >
49
55
  <BaseOfferCard.TopContainer>
50
56
  <BaseOfferCard.Headline>{headline}</BaseOfferCard.Headline>
@@ -56,7 +62,13 @@ export const HeadlineWithDescriptionCard = ({
56
62
  hasDivider={false}
57
63
  />
58
64
  )}
59
- {showMoreInfo && <SeeMore offer={offer} offerIndex={offerIndex} />}
65
+ {showMoreInfo && (
66
+ <SeeMore
67
+ offer={offer}
68
+ offerIndex={offerIndex}
69
+ isHorizontalScroll={isHorizontalScroll}
70
+ />
71
+ )}
60
72
  </DynamicOfferCard>
61
73
  );
62
74
  };