@explorins/pers-sdk-react-native 1.5.35 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +402 -13
  2. package/dist/hooks/index.d.ts +3 -1
  3. package/dist/hooks/index.d.ts.map +1 -1
  4. package/dist/hooks/index.js +1 -0
  5. package/dist/hooks/useBusiness.d.ts +5 -5
  6. package/dist/hooks/useBusiness.d.ts.map +1 -1
  7. package/dist/hooks/useCampaigns.d.ts +8 -8
  8. package/dist/hooks/useCampaigns.d.ts.map +1 -1
  9. package/dist/hooks/useCampaigns.js +3 -3
  10. package/dist/hooks/useDonations.d.ts +2 -2
  11. package/dist/hooks/useDonations.d.ts.map +1 -1
  12. package/dist/hooks/useEvents.d.ts +178 -0
  13. package/dist/hooks/useEvents.d.ts.map +1 -0
  14. package/dist/hooks/useEvents.js +312 -0
  15. package/dist/hooks/usePurchases.d.ts +3 -3
  16. package/dist/hooks/usePurchases.d.ts.map +1 -1
  17. package/dist/hooks/useRedemptions.d.ts +6 -5
  18. package/dist/hooks/useRedemptions.d.ts.map +1 -1
  19. package/dist/hooks/useRedemptions.js +13 -19
  20. package/dist/hooks/useTenants.d.ts +2 -2
  21. package/dist/hooks/useTenants.d.ts.map +1 -1
  22. package/dist/hooks/useTokens.d.ts +4 -4
  23. package/dist/hooks/useTokens.d.ts.map +1 -1
  24. package/dist/hooks/useTransactionSigner.d.ts +2 -0
  25. package/dist/hooks/useTransactionSigner.d.ts.map +1 -1
  26. package/dist/hooks/useTransactionSigner.js +68 -0
  27. package/dist/hooks/useTransactions.d.ts +3 -3
  28. package/dist/hooks/useTransactions.d.ts.map +1 -1
  29. package/dist/hooks/useUserStatus.d.ts +3 -3
  30. package/dist/hooks/useUserStatus.d.ts.map +1 -1
  31. package/dist/hooks/useUsers.d.ts +3 -3
  32. package/dist/hooks/useUsers.d.ts.map +1 -1
  33. package/dist/hooks/useWeb3.d.ts +5 -53
  34. package/dist/hooks/useWeb3.d.ts.map +1 -1
  35. package/dist/hooks/useWeb3.js +82 -177
  36. package/dist/index.d.ts +83 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3227 -837
  39. package/dist/index.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/hooks/index.ts +3 -1
  42. package/src/hooks/useBusiness.ts +5 -5
  43. package/src/hooks/useCampaigns.ts +12 -11
  44. package/src/hooks/useDonations.ts +2 -2
  45. package/src/hooks/useEvents.ts +360 -0
  46. package/src/hooks/usePurchases.ts +3 -3
  47. package/src/hooks/useRedemptions.ts +16 -22
  48. package/src/hooks/useTenants.ts +2 -2
  49. package/src/hooks/useTokens.ts +4 -4
  50. package/src/hooks/useTransactionSigner.ts +73 -0
  51. package/src/hooks/useTransactions.ts +4 -3
  52. package/src/hooks/useUserStatus.ts +3 -3
  53. package/src/hooks/useUsers.ts +3 -3
  54. package/src/hooks/useWeb3.ts +98 -202
  55. package/src/index.ts +105 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorins/pers-sdk-react-native",
3
- "version": "1.5.35",
3
+ "version": "2.0.0",
4
4
  "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,8 +37,8 @@
37
37
  "author": "eXplorins",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@explorins/pers-sdk": "^1.6.42",
41
- "@explorins/pers-shared": "^2.1.54",
40
+ "@explorins/pers-sdk": "^2.0.0",
41
+ "@explorins/pers-shared": "^2.1.64",
42
42
  "@explorins/pers-signer": "^1.0.33",
43
43
  "@explorins/web3-ts": "^0.3.77",
44
44
  "buffer": "^6.0.3",
@@ -14,6 +14,7 @@ export { useUserStatus } from './useUserStatus';
14
14
  export { useFiles } from './useFiles';
15
15
  export { useAnalytics } from './useAnalytics';
16
16
  export { useDonations } from './useDonations';
17
+ export { useEvents } from './useEvents';
17
18
 
18
19
  // Re-export auth-related types for convenience
19
20
  export type { RawUserData } from './useAuth';
@@ -26,4 +27,5 @@ export type {
26
27
  OnStatusUpdateFn,
27
28
  SigningStatus as SigningStatusType
28
29
  } from './useTransactionSigner';
29
- export type { UserOwnedTokensResult, Web3Hook } from './useWeb3';
30
+ export type { AccountOwnedTokensResult, Web3Hook } from './useWeb3';
31
+ export type { EventsHook, PersEvent, EventHandler, EventFilter, Unsubscribe } from './useEvents';
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
- import type { BusinessDTO, BusinessTypeDTO, BusinessUpdateRequestDTO, BusinessToggleActiveRequestDTO } from '@explorins/pers-shared';
3
+ import type { BusinessDTO, BusinessTypeDTO, BusinessUpdateRequestDTO, BusinessToggleActiveRequestDTO, PaginatedResponseDTO } from '@explorins/pers-shared';
4
4
 
5
5
  /**
6
6
  * React hook for business operations in the PERS SDK
@@ -52,7 +52,7 @@ export const useBusiness = () => {
52
52
  * console.log('Active businesses:', businesses);
53
53
  * ```
54
54
  */
55
- const getActiveBusinesses = useCallback(async (): Promise<BusinessDTO[]> => {
55
+ const getActiveBusinesses = useCallback(async (): Promise<PaginatedResponseDTO<BusinessDTO>> => {
56
56
  if (!isInitialized || !sdk) {
57
57
  throw new Error('SDK not initialized. Call initialize() first.');
58
58
  }
@@ -79,7 +79,7 @@ export const useBusiness = () => {
79
79
  * console.log('Business types:', types);
80
80
  * ```
81
81
  */
82
- const getBusinessTypes = useCallback(async (): Promise<BusinessTypeDTO[]> => {
82
+ const getBusinessTypes = useCallback(async (): Promise<PaginatedResponseDTO<BusinessTypeDTO>> => {
83
83
  if (!isInitialized || !sdk) {
84
84
  throw new Error('SDK not initialized. Call initialize() first.');
85
85
  }
@@ -107,7 +107,7 @@ export const useBusiness = () => {
107
107
  }
108
108
  }, [sdk, isInitialized]);
109
109
 
110
- const getBusinesses = useCallback(async (): Promise<BusinessDTO[]> => {
110
+ const getBusinesses = useCallback(async (): Promise<PaginatedResponseDTO<BusinessDTO>> => {
111
111
  if (!isInitialized || !sdk) {
112
112
  throw new Error('SDK not initialized. Call initialize() first.');
113
113
  }
@@ -135,7 +135,7 @@ export const useBusiness = () => {
135
135
  }
136
136
  }, [sdk, isInitialized]);
137
137
 
138
- const getBusinessesByType = useCallback(async (typeId: string): Promise<BusinessDTO[]> => {
138
+ const getBusinessesByType = useCallback(async (typeId: string): Promise<PaginatedResponseDTO<BusinessDTO>> => {
139
139
  if (!isInitialized || !sdk) {
140
140
  throw new Error('SDK not initialized. Call initialize() first.');
141
141
  }
@@ -7,19 +7,20 @@ import type {
7
7
  CampaignTriggerDTO,
8
8
  CampaignCreateRequestDTO,
9
9
  TokenUnitCreateRequestDTO,
10
- CampaignBusinessEngagementCreateRequestDTO
10
+ CampaignBusinessEngagementCreateRequestDTO,
11
+ PaginatedResponseDTO
11
12
  } from '@explorins/pers-shared';
12
13
 
13
14
  export const useCampaigns = () => {
14
15
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();
15
16
 
16
- const getActiveCampaigns = useCallback(async (): Promise<CampaignDTO[]> => {
17
+ const getActiveCampaigns = useCallback(async (): Promise<PaginatedResponseDTO<CampaignDTO>> => {
17
18
  if (!isInitialized || !sdk) {
18
19
  throw new Error('SDK not initialized. Call initialize() first.');
19
20
  }
20
21
 
21
22
  try {
22
- const result = await sdk.campaigns.getActiveCampaigns();
23
+ const result = await sdk.campaigns.getCampaigns({ active: true });
23
24
  return result;
24
25
  } catch (error) {
25
26
  console.error('Failed to fetch active campaigns:', error);
@@ -58,13 +59,13 @@ export const useCampaigns = () => {
58
59
  }
59
60
  }, [sdk, isInitialized, isAuthenticated]);
60
61
 
61
- const getUserClaims = useCallback(async (): Promise<CampaignClaimDTO[]> => {
62
+ const getUserClaims = useCallback(async (): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
62
63
  if (!isInitialized || !sdk) {
63
64
  throw new Error('SDK not initialized. Call initialize() first.');
64
65
  }
65
66
  if (!isAuthenticated) {
66
67
  console.warn('SDK not authenticated. getUserClaims requires authentication.');
67
- return [];
68
+ return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
68
69
  }
69
70
 
70
71
  try {
@@ -76,7 +77,7 @@ export const useCampaigns = () => {
76
77
  }
77
78
  }, [sdk, isInitialized, isAuthenticated]);
78
79
 
79
- const getCampaignTriggers = useCallback(async (): Promise<CampaignTriggerDTO[]> => {
80
+ const getCampaignTriggers = useCallback(async (): Promise<PaginatedResponseDTO<CampaignTriggerDTO>> => {
80
81
  if (!isInitialized || !sdk) {
81
82
  throw new Error('SDK not initialized. Call initialize() first.');
82
83
  }
@@ -91,13 +92,13 @@ export const useCampaigns = () => {
91
92
  }, [sdk, isInitialized]);
92
93
 
93
94
  // Admin methods
94
- const getAllCampaigns = useCallback(async (active?: boolean): Promise<CampaignDTO[]> => {
95
+ const getAllCampaigns = useCallback(async (active?: boolean): Promise<PaginatedResponseDTO<CampaignDTO>> => {
95
96
  if (!isInitialized || !sdk) {
96
97
  throw new Error('SDK not initialized. Call initialize() first.');
97
98
  }
98
99
 
99
100
  try {
100
- const result = await sdk.campaigns.getAllCampaigns(active);
101
+ const result = await sdk.campaigns.getCampaigns({ active });
101
102
  return result;
102
103
  } catch (error) {
103
104
  console.error('Failed to fetch all campaigns:', error);
@@ -105,7 +106,7 @@ export const useCampaigns = () => {
105
106
  }
106
107
  }, [sdk, isInitialized]);
107
108
 
108
- const getCampaignClaims = useCallback(async (): Promise<CampaignClaimDTO[]> => {
109
+ const getCampaignClaims = useCallback(async (): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
109
110
  if (!isInitialized || !sdk) {
110
111
  throw new Error('SDK not initialized. Call initialize() first.');
111
112
  }
@@ -119,7 +120,7 @@ export const useCampaigns = () => {
119
120
  }
120
121
  }, [sdk, isInitialized]);
121
122
 
122
- const getCampaignClaimsByUserId = useCallback(async (userId: string): Promise<CampaignClaimDTO[]> => {
123
+ const getCampaignClaimsByUserId = useCallback(async (userId: string): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
123
124
  if (!isInitialized || !sdk) {
124
125
  throw new Error('SDK not initialized. Call initialize() first.');
125
126
  }
@@ -133,7 +134,7 @@ export const useCampaigns = () => {
133
134
  }
134
135
  }, [sdk, isInitialized]);
135
136
 
136
- const getCampaignClaimsByBusinessId = useCallback(async (businessId: string): Promise<CampaignClaimDTO[]> => {
137
+ const getCampaignClaimsByBusinessId = useCallback(async (businessId: string): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
137
138
  if (!isInitialized || !sdk) {
138
139
  throw new Error('SDK not initialized. Call initialize() first.');
139
140
  }
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
- import type { DonationTypeDTO } from '@explorins/pers-shared';
3
+ import type { DonationTypeDTO, PaginatedResponseDTO } from '@explorins/pers-shared';
4
4
 
5
5
  /**
6
6
  * React hook for donation operations in the PERS SDK
@@ -44,7 +44,7 @@ export const useDonations = () => {
44
44
  * console.log('Donation types:', types.map(t => t.name));
45
45
  * ```
46
46
  */
47
- const getDonationTypes = useCallback(async (): Promise<DonationTypeDTO[]> => {
47
+ const getDonationTypes = useCallback(async (): Promise<PaginatedResponseDTO<DonationTypeDTO>> => {
48
48
  if (!isInitialized || !sdk) {
49
49
  throw new Error('SDK not initialized. Call initialize() first.');
50
50
  }
@@ -0,0 +1,360 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { usePersSDK } from '../providers/PersSDKProvider';
3
+ import type {
4
+ PersEvent,
5
+ EventHandler,
6
+ EventFilter,
7
+ Unsubscribe
8
+ } from '@explorins/pers-sdk/core';
9
+
10
+ // Re-export event types for convenience
11
+ export type { PersEvent, EventHandler, EventFilter, Unsubscribe };
12
+
13
+ /**
14
+ * Return interface for the useEvents hook
15
+ *
16
+ * Provides event subscription capabilities for the PERS SDK event system.
17
+ * Use this to display notifications, update UI, or trigger side effects.
18
+ *
19
+ * @interface EventsHook
20
+ * @property {Function} subscribe - Subscribe to SDK events with optional filtering
21
+ * @property {Function} once - Subscribe to a single event (auto-unsubscribes)
22
+ * @property {Function} clear - Clear all subscriptions
23
+ * @property {boolean} isAvailable - Whether the event system is available
24
+ * @property {number} subscriberCount - Current number of active subscribers
25
+ */
26
+ export interface EventsHook {
27
+ subscribe: (handler: EventHandler, filter?: EventFilter) => Unsubscribe;
28
+ once: (handler: EventHandler, filter?: EventFilter) => Unsubscribe;
29
+ clear: () => void;
30
+ isAvailable: boolean;
31
+ subscriberCount: number;
32
+ }
33
+
34
+ /**
35
+ * React Native hook for PERS SDK event system
36
+ *
37
+ * This hook provides access to the platform-agnostic event system for subscribing to
38
+ * transaction, authentication, campaign, and system events. All events include a
39
+ * `userMessage` field ready for display to end users.
40
+ *
41
+ * **Event Domains:**
42
+ * - `auth` - Authentication events (login, logout, token refresh)
43
+ * - `user` - User profile events (update, create)
44
+ * - `transaction` - Transaction events (created, completed, failed)
45
+ * - `campaign` - Campaign events (claimed, activated)
46
+ * - `redemption` - Redemption events (redeemed, expired)
47
+ * - `business` - Business events (created, updated, membership)
48
+ * - `api` - API error events (network, validation, server errors)
49
+ *
50
+ * **Notification Levels:**
51
+ * - `success` - Operation completed successfully
52
+ * - `error` - Operation failed
53
+ * - `warning` - Operation completed with warnings
54
+ * - `info` - Informational event
55
+ *
56
+ * **Cleanup:**
57
+ * All subscriptions created through this hook are automatically cleaned up when
58
+ * the component unmounts, preventing memory leaks and stale event handlers.
59
+ *
60
+ * @returns {EventsHook} Hook interface with event subscription methods
61
+ *
62
+ * @example
63
+ * **Basic Usage - Show All Notifications**
64
+ * ```typescript
65
+ * import { useEvents } from '@explorins/pers-sdk-react-native';
66
+ *
67
+ * function NotificationComponent() {
68
+ * const { subscribe, isAvailable } = useEvents();
69
+ *
70
+ * useEffect(() => {
71
+ * if (!isAvailable) return;
72
+ *
73
+ * const unsubscribe = subscribe((event) => {
74
+ * // userMessage is always present and UI-ready
75
+ * showNotification(event.userMessage, event.level);
76
+ * });
77
+ *
78
+ * return () => unsubscribe();
79
+ * }, [subscribe, isAvailable]);
80
+ *
81
+ * return <View />;
82
+ * }
83
+ * ```
84
+ *
85
+ * @example
86
+ * **Filter by Domain and Level**
87
+ * ```typescript
88
+ * function TransactionListener() {
89
+ * const { subscribe, isAvailable } = useEvents();
90
+ *
91
+ * useEffect(() => {
92
+ * if (!isAvailable) return;
93
+ *
94
+ * // Only listen to successful transactions
95
+ * const unsubscribe = subscribe(
96
+ * (event) => {
97
+ * if (event.level === 'success') {
98
+ * playSuccessSound();
99
+ * showConfetti();
100
+ * }
101
+ * },
102
+ * { domain: 'transaction' } // Filter
103
+ * );
104
+ *
105
+ * return () => unsubscribe();
106
+ * }, [subscribe, isAvailable]);
107
+ * }
108
+ * ```
109
+ *
110
+ * @example
111
+ * **Filter Errors for Logging**
112
+ * ```typescript
113
+ * function ErrorLogger() {
114
+ * const { subscribe, isAvailable } = useEvents();
115
+ *
116
+ * useEffect(() => {
117
+ * if (!isAvailable) return;
118
+ *
119
+ * const unsubscribe = subscribe(
120
+ * (event) => {
121
+ * // Log errors to crash reporting
122
+ * logToSentry({
123
+ * message: event.userMessage,
124
+ * domain: event.domain,
125
+ * type: event.type,
126
+ * });
127
+ * },
128
+ * { level: 'error' } // Only errors
129
+ * );
130
+ *
131
+ * return () => unsubscribe();
132
+ * }, [subscribe, isAvailable]);
133
+ * }
134
+ * ```
135
+ *
136
+ * @example
137
+ * **One-time Event**
138
+ * ```typescript
139
+ * function FirstTransactionHandler() {
140
+ * const { once, isAvailable } = useEvents();
141
+ *
142
+ * useEffect(() => {
143
+ * if (!isAvailable) return;
144
+ *
145
+ * // Auto-unsubscribe after first transaction event
146
+ * const unsubscribe = once(
147
+ * (event) => {
148
+ * console.log('First transaction event:', event.type);
149
+ * showOnboardingComplete();
150
+ * },
151
+ * { domain: 'transaction', level: 'success' }
152
+ * );
153
+ *
154
+ * return () => unsubscribe();
155
+ * }, [once, isAvailable]);
156
+ * }
157
+ * ```
158
+ *
159
+ * @example
160
+ * **Combined Domain and Level Filter**
161
+ * ```typescript
162
+ * function AuthErrorHandler() {
163
+ * const { subscribe, isAvailable } = useEvents();
164
+ *
165
+ * useEffect(() => {
166
+ * if (!isAvailable) return;
167
+ *
168
+ * const unsubscribe = subscribe(
169
+ * (event) => {
170
+ * Alert.alert('Authentication Error', event.userMessage);
171
+ * navigation.navigate('Login');
172
+ * },
173
+ * { domain: 'auth', level: 'error' }
174
+ * );
175
+ *
176
+ * return () => unsubscribe();
177
+ * }, [subscribe, isAvailable]);
178
+ * }
179
+ * ```
180
+ *
181
+ * @see {@link PersEvent} for event structure
182
+ * @see {@link EventFilter} for filtering options
183
+ * @see {@link EventHandler} for handler function type
184
+ *
185
+ * @since 1.6.49
186
+ */
187
+ export const useEvents = (): EventsHook => {
188
+ const { sdk, isInitialized } = usePersSDK();
189
+
190
+ // Track subscriptions for cleanup
191
+ const subscriptionsRef = useRef<Unsubscribe[]>([]);
192
+
193
+ // Cleanup all subscriptions on unmount
194
+ useEffect(() => {
195
+ return () => {
196
+ subscriptionsRef.current.forEach(unsubscribe => {
197
+ try {
198
+ unsubscribe();
199
+ } catch (e) {
200
+ // Ignore cleanup errors
201
+ }
202
+ });
203
+ subscriptionsRef.current = [];
204
+ };
205
+ }, []);
206
+
207
+ /**
208
+ * Subscribe to SDK events with optional filtering
209
+ *
210
+ * Creates a subscription that will be called for every matching event.
211
+ * The subscription is automatically tracked for cleanup when the component unmounts.
212
+ *
213
+ * @param handler - Callback function invoked for each matching event
214
+ * @param filter - Optional filter to limit events (by domain, level, or both)
215
+ * @returns Unsubscribe function to manually cancel the subscription
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const unsubscribe = subscribe((event) => {
220
+ * console.log('Event:', event.type, event.userMessage);
221
+ * });
222
+ *
223
+ * // Later: manual cleanup
224
+ * unsubscribe();
225
+ * ```
226
+ */
227
+ const subscribe = useCallback((handler: EventHandler, filter?: EventFilter): Unsubscribe => {
228
+ if (!isInitialized || !sdk) {
229
+ console.warn('[useEvents] SDK not initialized. Cannot subscribe to events.');
230
+ return () => {}; // Return no-op unsubscribe
231
+ }
232
+
233
+ const unsubscribe = sdk.events.subscribe(handler, filter);
234
+
235
+ // Track for cleanup
236
+ subscriptionsRef.current.push(unsubscribe);
237
+
238
+ // Return wrapped unsubscribe that also removes from tracking
239
+ return () => {
240
+ unsubscribe();
241
+ const index = subscriptionsRef.current.indexOf(unsubscribe);
242
+ if (index > -1) {
243
+ subscriptionsRef.current.splice(index, 1);
244
+ }
245
+ };
246
+ }, [sdk, isInitialized]);
247
+
248
+ /**
249
+ * Subscribe to a single event (auto-unsubscribes after first match)
250
+ *
251
+ * Useful for one-time event handling, like waiting for a specific action to complete.
252
+ *
253
+ * @param handler - Callback function invoked for the first matching event
254
+ * @param filter - Optional filter to limit events (by domain, level, or both)
255
+ * @returns Unsubscribe function to manually cancel before event fires
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * once((event) => {
260
+ * console.log('First event:', event.type);
261
+ * }, { domain: 'transaction', level: 'success' });
262
+ * ```
263
+ */
264
+ const once = useCallback((handler: EventHandler, filter?: EventFilter): Unsubscribe => {
265
+ if (!isInitialized || !sdk) {
266
+ console.warn('[useEvents] SDK not initialized. Cannot subscribe to events.');
267
+ return () => {};
268
+ }
269
+
270
+ const unsubscribe = sdk.events.once(handler, filter);
271
+
272
+ // Track for cleanup
273
+ subscriptionsRef.current.push(unsubscribe);
274
+
275
+ return () => {
276
+ unsubscribe();
277
+ const index = subscriptionsRef.current.indexOf(unsubscribe);
278
+ if (index > -1) {
279
+ subscriptionsRef.current.splice(index, 1);
280
+ }
281
+ };
282
+ }, [sdk, isInitialized]);
283
+
284
+ /**
285
+ * Clear all event subscriptions from this hook instance
286
+ *
287
+ * Useful when you want to reset all event listeners without unmounting the component.
288
+ * Note: This only clears subscriptions created through this hook instance.
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * const { clear, subscribe } = useEvents();
293
+ *
294
+ * // Create some subscriptions
295
+ * subscribe((event) => console.log(event));
296
+ * subscribe((event) => logToServer(event));
297
+ *
298
+ * // Later: clear all subscriptions from this hook
299
+ * clear();
300
+ * ```
301
+ */
302
+ const clear = useCallback((): void => {
303
+ subscriptionsRef.current.forEach(unsubscribe => {
304
+ try {
305
+ unsubscribe();
306
+ } catch (e) {
307
+ // Ignore cleanup errors
308
+ }
309
+ });
310
+ subscriptionsRef.current = [];
311
+ }, []);
312
+
313
+ /**
314
+ * Get current subscriber count from the event emitter
315
+ *
316
+ * Useful for debugging or showing active listener count in dev tools.
317
+ */
318
+ const subscriberCount = sdk?.events?.subscriberCount ?? 0;
319
+
320
+ return {
321
+ /**
322
+ * Subscribe to SDK events with optional filtering
323
+ *
324
+ * @param handler - Callback function for each matching event
325
+ * @param filter - Optional filter by domain and/or level
326
+ * @returns Unsubscribe function
327
+ */
328
+ subscribe,
329
+
330
+ /**
331
+ * Subscribe to a single event (auto-unsubscribes after first match)
332
+ *
333
+ * @param handler - Callback function for the first matching event
334
+ * @param filter - Optional filter by domain and/or level
335
+ * @returns Unsubscribe function
336
+ */
337
+ once,
338
+
339
+ /**
340
+ * Clear all subscriptions created through this hook instance
341
+ */
342
+ clear,
343
+
344
+ /**
345
+ * Whether the event system is available
346
+ *
347
+ * Returns `true` when SDK is initialized and events can be subscribed to.
348
+ */
349
+ isAvailable: isInitialized && !!sdk?.events,
350
+
351
+ /**
352
+ * Current number of active subscribers across all components
353
+ *
354
+ * Useful for debugging event system usage.
355
+ */
356
+ subscriberCount,
357
+ };
358
+ };
359
+
360
+ export type EventsHook_ = ReturnType<typeof useEvents>;
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
- import type { PurchaseTokenDTO, PurchaseDTO } from '@explorins/pers-shared';
3
+ import type { PurchaseTokenDTO, PurchaseDTO, PaginatedResponseDTO } from '@explorins/pers-shared';
4
4
 
5
5
  export const usePurchases = () => {
6
6
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();
@@ -24,7 +24,7 @@ export const usePurchases = () => {
24
24
  }
25
25
  }, [sdk, isInitialized]);
26
26
 
27
- const getActivePurchaseTokens = useCallback(async (): Promise<PurchaseTokenDTO[]> => {
27
+ const getActivePurchaseTokens = useCallback(async (): Promise<PaginatedResponseDTO<PurchaseTokenDTO>> => {
28
28
  if (!isInitialized || !sdk) {
29
29
  throw new Error('SDK not initialized. Call initialize() first.');
30
30
  }
@@ -38,7 +38,7 @@ export const usePurchases = () => {
38
38
  }
39
39
  }, [sdk, isInitialized]);
40
40
 
41
- const getAllUserPurchases = useCallback(async (): Promise<PurchaseDTO[]> => {
41
+ const getAllUserPurchases = useCallback(async (): Promise<PaginatedResponseDTO<PurchaseDTO>> => {
42
42
  if (!isInitialized || !sdk) {
43
43
  throw new Error('SDK not initialized. Call initialize() first.');
44
44
  }
@@ -7,33 +7,42 @@ import type {
7
7
  RedemptionRedeemDTO,
8
8
  RedemptionRedeemRequestResponseDTO,
9
9
  RedemptionTypeDTO,
10
+ PaginatedResponseDTO
10
11
  } from '@explorins/pers-shared';
11
12
 
12
13
  export const useRedemptions = () => {
13
14
  const { sdk, isInitialized, isAuthenticated } = usePersSDK();
14
15
  const { signAndSubmitTransactionWithJWT, isSignerAvailable, currentStatus, statusMessage } = useTransactionSigner();
15
16
 
16
- const getActiveRedemptions = useCallback(async (): Promise<RedemptionDTO[]> => {
17
+ /**
18
+ * Get redemption offers based on user permissions
19
+ *
20
+ * **Regular Users:** Automatically see only active redemptions available for purchase
21
+ * **Administrators:** Can see all redemptions and filter by active/inactive status
22
+ *
23
+ * The backend enforces permission-based filtering automatically.
24
+ */
25
+ const getRedemptions = useCallback(async (options?: { active?: boolean }): Promise<PaginatedResponseDTO<RedemptionDTO>> => {
17
26
  if (!isInitialized || !sdk) {
18
27
  throw new Error('SDK not initialized. Call initialize() first.');
19
28
  }
20
29
 
21
30
  try {
22
- const result = await sdk.redemptions.getActiveRedemptions();
31
+ const result = await sdk.redemptions.getRedemptions(options);
23
32
  return result;
24
33
  } catch (error) {
25
- console.error('Failed to fetch active redemptions:', error);
34
+ console.error('Failed to fetch redemptions:', error);
26
35
  throw error;
27
36
  }
28
37
  }, [sdk, isInitialized]);
29
38
 
30
- const getUserRedemptions = useCallback(async (): Promise<RedemptionRedeemDTO[]> => {
39
+ const getUserRedemptions = useCallback(async (): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
31
40
  if (!isInitialized || !sdk) {
32
41
  throw new Error('SDK not initialized. Call initialize() first.');
33
42
  }
34
43
  if (!isAuthenticated) {
35
44
  console.warn('SDK not authenticated. getUserRedemptions requires authentication.');
36
- return [];
45
+ return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
37
46
  }
38
47
 
39
48
  try {
@@ -105,21 +114,7 @@ export const useRedemptions = () => {
105
114
  }
106
115
  }, [sdk, isInitialized]);
107
116
 
108
- const getAllRedemptions = useCallback(async (active?: boolean): Promise<RedemptionDTO[]> => {
109
- if (!isInitialized || !sdk) {
110
- throw new Error('SDK not initialized. Call initialize() first.');
111
- }
112
-
113
- try {
114
- const result = await sdk.redemptions.getAllRedemptions(active);
115
- return result;
116
- } catch (error) {
117
- console.error('Failed to fetch all redemptions:', error);
118
- throw error;
119
- }
120
- }, [sdk, isInitialized]);
121
-
122
- const getRedemptionTypes = useCallback(async (): Promise<RedemptionTypeDTO[]> => {
117
+ const getRedemptionTypes = useCallback(async (): Promise<PaginatedResponseDTO<RedemptionTypeDTO>> => {
123
118
  if (!isInitialized || !sdk) {
124
119
  throw new Error('SDK not initialized. Call initialize() first.');
125
120
  }
@@ -162,12 +157,11 @@ export const useRedemptions = () => {
162
157
  }, [sdk, isInitialized]);
163
158
 
164
159
  return {
165
- getActiveRedemptions,
160
+ getRedemptions,
166
161
  getUserRedemptions,
167
162
  redeem,
168
163
  getRedemptionTypes,
169
164
  createRedemption,
170
- getAllRedemptions,
171
165
  updateRedemption,
172
166
  toggleRedemptionStatus,
173
167
  isAvailable: isInitialized && !!sdk?.redemptions,
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
- import type { TenantPublicDTO, TenantClientConfigDTO, AdminDTO } from '@explorins/pers-shared';
3
+ import type { TenantPublicDTO, TenantClientConfigDTO, AdminDTO, PaginatedResponseDTO } from '@explorins/pers-shared';
4
4
 
5
5
  export const useTenants = () => {
6
6
  const { sdk, isInitialized } = usePersSDK();
@@ -47,7 +47,7 @@ export const useTenants = () => {
47
47
  }
48
48
  }, [sdk, isInitialized]);
49
49
 
50
- const getAdmins = useCallback(async (): Promise<AdminDTO[]> => {
50
+ const getAdmins = useCallback(async (): Promise<PaginatedResponseDTO<AdminDTO>> => {
51
51
  if (!isInitialized || !sdk) {
52
52
  throw new Error('SDK not initialized. Call initialize() first.');
53
53
  }