@wlloyalty/wll-react-sdk 1.0.101 → 1.0.103

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.
@@ -3,10 +3,13 @@ declare const _default: Meta;
3
3
  export default _default;
4
4
  export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, {
5
5
  section?: import("../../../types/section").TSection;
6
+ autoRotateInterval?: number;
6
7
  }>;
7
8
  export declare const SingleItem: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, {
8
9
  section?: import("../../../types/section").TSection;
10
+ autoRotateInterval?: number;
9
11
  }>;
10
12
  export declare const ManyItems: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, {
11
13
  section?: import("../../../types/section").TSection;
14
+ autoRotateInterval?: number;
12
15
  }>;
@@ -1,6 +1,7 @@
1
1
  import { TSection } from '../../../types/section';
2
2
  type CarouselProps = {
3
3
  section?: TSection;
4
+ autoRotateInterval?: number;
4
5
  };
5
- declare const Carousel: ({ section }: CarouselProps) => JSX.Element | null;
6
+ declare const Carousel: ({ section, autoRotateInterval, }: CarouselProps) => JSX.Element | null;
6
7
  export default Carousel;
@@ -4,6 +4,7 @@ import { NavigationConfig } from '../types/navigation';
4
4
  import { TSection } from '../types/section';
5
5
  import { BaseThemeObject, ThemeContextType } from '../types/theme';
6
6
  import { CTALinkTarget, Tile } from '../types/tile';
7
+ import { EventTypes } from '../utils/eventEmitter';
7
8
  type Fetcher = <T>(endpoint: string, options?: RequestInit) => Promise<APIResponse<T>>;
8
9
  export type SDKConfig = {
9
10
  apiKey: string;
@@ -22,6 +23,8 @@ type WllSdkContextType = ThemeContextType & {
22
23
  getTileByID: (id: string) => Promise<APIResponse<Tile>>;
23
24
  handleNavigation: (link: string, target: CTALinkTarget) => Promise<void>;
24
25
  refreshGroup: (id: string) => Promise<APIResponse<TGroup>>;
26
+ notifyDataChange: (eventType: EventTypes, data?: any) => void;
27
+ subscribeToDataChange: (eventType: EventTypes, callback: (data?: any) => void) => () => void;
25
28
  } & Readonly<{
26
29
  readonly config: SDKConfig;
27
30
  }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Type definition for event callback functions
3
+ * @typedef {function} EventCallback
4
+ * @param {any} [data] - Optional data passed with the event
5
+ */
6
+ export type EventCallback = (data?: any) => void;
7
+ /**
8
+ * Supported event types for the SDK event system
9
+ * @typedef {string} EventTypes
10
+ */
11
+ export type EventTypes = 'GROUP_DATA_CHANGED' | 'SECTION_DATA_CHANGED' | 'TILE_DATA_CHANGED' | string;
12
+ /**
13
+ * A simple event emitter for handling SDK events
14
+ * Provides a pub-sub mechanism for components to communicate
15
+ */
16
+ export declare class EventEmitter {
17
+ /**
18
+ * Map of event types to arrays of callback functions
19
+ * @private
20
+ */
21
+ private events;
22
+ /**
23
+ * Subscribe to an event
24
+ * @param {EventTypes} event - The event type to subscribe to
25
+ * @param {EventCallback} callback - The callback function to execute when the event is emitted
26
+ * @returns {Function} An unsubscribe function that removes this callback when called
27
+ */
28
+ on(event: EventTypes, callback: EventCallback): () => void;
29
+ /**
30
+ * Emit an event with optional data
31
+ * @param {EventTypes} event - The event type to emit
32
+ * @param {any} [data] - Optional data to pass to the event callbacks
33
+ */
34
+ emit(event: EventTypes, data?: any): void;
35
+ /**
36
+ * Remove all event listeners
37
+ * Use this method to clean up when the event emitter is no longer needed
38
+ */
39
+ clear(): void;
40
+ }
41
+ /**
42
+ * Singleton instance of the EventEmitter for use throughout the SDK
43
+ */
44
+ export declare const sdkEventEmitter: EventEmitter;
package/dist/web.js CHANGED
@@ -7924,6 +7924,68 @@ var useGetGroupByID = createResourceGetter('groups', true);
7924
7924
  var useGetSectionByID = createResourceGetter('sections');
7925
7925
  var useGetTileByID = createResourceGetter('tiles');
7926
7926
 
7927
+ /**
7928
+ * A simple event emitter for handling SDK events
7929
+ * Provides a pub-sub mechanism for components to communicate
7930
+ */
7931
+ var EventEmitter$1 = /** @class */function () {
7932
+ function EventEmitter() {
7933
+ /**
7934
+ * Map of event types to arrays of callback functions
7935
+ * @private
7936
+ */
7937
+ this.events = new Map();
7938
+ }
7939
+ /**
7940
+ * Subscribe to an event
7941
+ * @param {EventTypes} event - The event type to subscribe to
7942
+ * @param {EventCallback} callback - The callback function to execute when the event is emitted
7943
+ * @returns {Function} An unsubscribe function that removes this callback when called
7944
+ */
7945
+ EventEmitter.prototype.on = function (event, callback) {
7946
+ var _this = this;
7947
+ var _a;
7948
+ if (!this.events.has(event)) {
7949
+ this.events.set(event, []);
7950
+ }
7951
+ (_a = this.events.get(event)) === null || _a === void 0 ? void 0 : _a.push(callback);
7952
+ return function () {
7953
+ var callbacks = _this.events.get(event);
7954
+ if (callbacks) {
7955
+ var index = callbacks.indexOf(callback);
7956
+ if (index !== -1) {
7957
+ callbacks.splice(index, 1);
7958
+ }
7959
+ }
7960
+ };
7961
+ };
7962
+ /**
7963
+ * Emit an event with optional data
7964
+ * @param {EventTypes} event - The event type to emit
7965
+ * @param {any} [data] - Optional data to pass to the event callbacks
7966
+ */
7967
+ EventEmitter.prototype.emit = function (event, data) {
7968
+ var callbacks = this.events.get(event);
7969
+ if (callbacks) {
7970
+ callbacks.forEach(function (callback) {
7971
+ return callback(data);
7972
+ });
7973
+ }
7974
+ };
7975
+ /**
7976
+ * Remove all event listeners
7977
+ * Use this method to clean up when the event emitter is no longer needed
7978
+ */
7979
+ EventEmitter.prototype.clear = function () {
7980
+ this.events.clear();
7981
+ };
7982
+ return EventEmitter;
7983
+ }();
7984
+ /**
7985
+ * Singleton instance of the EventEmitter for use throughout the SDK
7986
+ */
7987
+ var sdkEventEmitter = new EventEmitter$1();
7988
+
7927
7989
  var sizes = {
7928
7990
  borderRadiusSm: 15,
7929
7991
  borderRadiusLg: 20,
@@ -8525,6 +8587,12 @@ var WllSdkProvider = function (_a) {
8525
8587
  });
8526
8588
  });
8527
8589
  }, [getGroupByID]);
8590
+ var notifyDataChange = React.useCallback(function (eventType, data) {
8591
+ sdkEventEmitter.emit(eventType, data);
8592
+ }, []);
8593
+ var subscribeToDataChange = React.useCallback(function (eventType, callback) {
8594
+ return sdkEventEmitter.on(eventType, callback);
8595
+ }, []);
8528
8596
  var contextValue = React.useMemo(function () {
8529
8597
  return {
8530
8598
  theme: theme,
@@ -8534,9 +8602,11 @@ var WllSdkProvider = function (_a) {
8534
8602
  getTileByID: getTileByID,
8535
8603
  handleNavigation: handleNavigation,
8536
8604
  refreshGroup: refreshGroup,
8605
+ notifyDataChange: notifyDataChange,
8606
+ subscribeToDataChange: subscribeToDataChange,
8537
8607
  config: config
8538
8608
  };
8539
- }, [theme, setTheme, getGroupByID, getSectionByID, getTileByID, handleNavigation, refreshGroup, config]);
8609
+ }, [theme, setTheme, getGroupByID, getSectionByID, getTileByID, handleNavigation, refreshGroup, notifyDataChange, subscribeToDataChange, config]);
8540
8610
  return /*#__PURE__*/React.createElement(WllSdkContext.Provider, {
8541
8611
  value: contextValue
8542
8612
  }, /*#__PURE__*/React.createElement(ResponsiveProvider, null, children));
@@ -20558,22 +20628,24 @@ var carouselReducer = function (state, action) {
20558
20628
  }
20559
20629
  };
20560
20630
  var Carousel = function (_a) {
20561
- var section = _a.section;
20631
+ var section = _a.section,
20632
+ _b = _a.autoRotateInterval,
20633
+ autoRotateInterval = _b === void 0 ? 5000 : _b;
20562
20634
  if (!section) return null;
20563
20635
  var WINDOW_WIDTH = useWindowDimensions().width;
20564
20636
  var containerRef = React.useRef(null);
20565
20637
  var styles = useCarouselStyles(BUTTON_SIZE);
20566
20638
  var animatedIndex = React.useRef(new Animated$1.Value(0)).current;
20567
20639
  var theme = useWllSdk().theme;
20568
- var _b = useResponsive(),
20569
- isDesktop = _b.isDesktop,
20570
- isTablet = _b.isTablet;
20640
+ var _c = useResponsive(),
20641
+ isDesktop = _c.isDesktop,
20642
+ isTablet = _c.isTablet;
20571
20643
  var scrollViewRef = React.useRef(null);
20572
- var _c = React.useReducer(carouselReducer, __assign(__assign({}, initialState), {
20644
+ var _d = React.useReducer(carouselReducer, __assign(__assign({}, initialState), {
20573
20645
  containerWidth: WINDOW_WIDTH
20574
20646
  })),
20575
- state = _c[0],
20576
- dispatch = _c[1];
20647
+ state = _d[0],
20648
+ dispatch = _d[1];
20577
20649
  var currentIndex = state.currentIndex,
20578
20650
  containerWidth = state.containerWidth;
20579
20651
  var sortedTiles = sortByPriority(section.tiles.filter(function (tile) {
@@ -20604,15 +20676,40 @@ var Carousel = function (_a) {
20604
20676
  };
20605
20677
  var handleNext = function () {
20606
20678
  var _a;
20679
+ var nextIndex = Math.min(sortedTiles.length - 1, currentIndex + 1);
20607
20680
  dispatch({
20608
20681
  type: 'NEXT_SLIDE',
20609
20682
  maxIndex: sortedTiles.length - 1
20610
20683
  });
20611
20684
  (_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo({
20612
- x: (currentIndex + 1) * containerWidth,
20685
+ x: nextIndex * containerWidth,
20613
20686
  animated: true
20614
20687
  });
20615
20688
  };
20689
+ var rotateToNextSlide = React.useCallback(function () {
20690
+ var _a;
20691
+ if (currentIndex >= sortedTiles.length - 1) {
20692
+ dispatch({
20693
+ type: 'SET_CURRENT_INDEX',
20694
+ payload: 0
20695
+ });
20696
+ (_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo({
20697
+ x: 0,
20698
+ animated: true
20699
+ });
20700
+ } else {
20701
+ handleNext();
20702
+ }
20703
+ }, [currentIndex, containerWidth, sortedTiles.length]);
20704
+ React.useEffect(function () {
20705
+ if (sortedTiles.length <= 1) return;
20706
+ var rotationTimer = setInterval(function () {
20707
+ rotateToNextSlide();
20708
+ }, autoRotateInterval);
20709
+ return function () {
20710
+ return clearInterval(rotationTimer);
20711
+ };
20712
+ }, [rotateToNextSlide, autoRotateInterval, sortedTiles.length]);
20616
20713
  var displayControls = sortedTiles.length > 1;
20617
20714
  var showPrevButton = displayControls && currentIndex > 0;
20618
20715
  var showNextButton = displayControls && currentIndex < sortedTiles.length - 1;
@@ -21080,6 +21177,14 @@ var useGroupContext = function () {
21080
21177
  /**
21081
21178
  * Custom hook to fetch and manage group data
21082
21179
  *
21180
+ * This hook handles two distinct data fetching scenarios:
21181
+ * 1. Initial fetch: Shows loading state while first loading data
21182
+ * 2. Background refresh: Updates data silently without loading states
21183
+ *
21184
+ * The hook subscribes to GROUP_DATA_CHANGED events to refresh data
21185
+ * when notified by the host application, ensuring UI remains responsive
21186
+ * without showing loading indicators during refreshes.
21187
+ *
21083
21188
  * @param {string} id - The ID of the group to fetch
21084
21189
  * @returns {Object} Object containing group data, loading state, and any error
21085
21190
  */
@@ -21094,27 +21199,22 @@ var useGroupData = function (id) {
21094
21199
  var _c = React.useState(true),
21095
21200
  isLoading = _c[0],
21096
21201
  setIsLoading = _c[1];
21097
- var fetchGroup = React.useCallback(function () {
21098
- var args_1 = [];
21099
- for (var _i = 0; _i < arguments.length; _i++) {
21100
- args_1[_i] = arguments[_i];
21101
- }
21102
- return __awaiter(void 0, __spreadArray([], args_1, true), void 0, function (showLoading) {
21202
+ var _d = React.useState(false),
21203
+ isInitialFetchDone = _d[0],
21204
+ setIsInitialFetchDone = _d[1];
21205
+ var initialFetch = React.useCallback(function () {
21206
+ return __awaiter(void 0, void 0, void 0, function () {
21103
21207
  var response, err_1;
21104
- if (showLoading === void 0) {
21105
- showLoading = true;
21106
- }
21107
21208
  return __generator(this, function (_a) {
21108
21209
  switch (_a.label) {
21109
21210
  case 0:
21110
21211
  if (!id || !sdk || !sdk.getGroupByID) {
21111
21212
  setError('Unable to fetch group data: invalid configuration');
21112
21213
  setIsLoading(false);
21214
+ setIsInitialFetchDone(true);
21113
21215
  return [2 /*return*/];
21114
21216
  }
21115
- if (showLoading) {
21116
- setIsLoading(true);
21117
- }
21217
+ setIsLoading(true);
21118
21218
  setError(null);
21119
21219
  _a.label = 1;
21120
21220
  case 1:
@@ -21134,9 +21234,8 @@ var useGroupData = function (id) {
21134
21234
  console.error('Error fetching group:', err_1);
21135
21235
  return [3 /*break*/, 5];
21136
21236
  case 4:
21137
- if (showLoading) {
21138
- setIsLoading(false);
21139
- }
21237
+ setIsLoading(false);
21238
+ setIsInitialFetchDone(true);
21140
21239
  return [7 /*endfinally*/];
21141
21240
  case 5:
21142
21241
  return [2 /*return*/];
@@ -21144,12 +21243,49 @@ var useGroupData = function (id) {
21144
21243
  });
21145
21244
  });
21146
21245
  }, [id, sdk]);
21246
+ var silentRefresh = React.useCallback(function () {
21247
+ return __awaiter(void 0, void 0, void 0, function () {
21248
+ var response, err_2;
21249
+ return __generator(this, function (_a) {
21250
+ switch (_a.label) {
21251
+ case 0:
21252
+ if (!id || !sdk || !sdk.getGroupByID || !isInitialFetchDone) {
21253
+ return [2 /*return*/];
21254
+ }
21255
+ _a.label = 1;
21256
+ case 1:
21257
+ _a.trys.push([1, 3,, 4]);
21258
+ return [4 /*yield*/, sdk.refreshGroup(id)];
21259
+ case 2:
21260
+ response = _a.sent();
21261
+ if (response && response.status === 'success' && response.data) {
21262
+ setGroupData(response.data);
21263
+ }
21264
+ return [3 /*break*/, 4];
21265
+ case 3:
21266
+ err_2 = _a.sent();
21267
+ console.error('Error during silent refresh:', err_2);
21268
+ return [3 /*break*/, 4];
21269
+ case 4:
21270
+ return [2 /*return*/];
21271
+ }
21272
+ });
21273
+ });
21274
+ }, [id, sdk, isInitialFetchDone]);
21147
21275
  var refreshGroup = React.useCallback(function () {
21148
- return fetchGroup(false);
21149
- }, [fetchGroup]);
21276
+ return silentRefresh();
21277
+ }, [silentRefresh]);
21150
21278
  React.useEffect(function () {
21151
- fetchGroup(true);
21152
- }, [fetchGroup]);
21279
+ if (sdk) {
21280
+ initialFetch();
21281
+ var unsubscribeGroup_1 = sdk.subscribeToDataChange('GROUP_DATA_CHANGED', function () {
21282
+ silentRefresh();
21283
+ });
21284
+ return function () {
21285
+ unsubscribeGroup_1();
21286
+ };
21287
+ }
21288
+ }, [initialFetch, id, sdk, silentRefresh]);
21153
21289
  return {
21154
21290
  groupData: groupData,
21155
21291
  isLoading: isLoading,
@@ -21246,7 +21382,7 @@ var Group = function (_a) {
21246
21382
  groupData: groupData
21247
21383
  }
21248
21384
  }, /*#__PURE__*/React.createElement(View$2, {
21249
- "data-testid": "group-container"
21385
+ testID: "group-container"
21250
21386
  }, /*#__PURE__*/React.createElement(GroupSections, null)));
21251
21387
  };
21252
21388