@explorins/pers-sdk-react-native 2.3.6 → 2.3.8

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1266 -1244
  3. package/dist/__tests__/PersSDKProvider.test.js +33 -0
  4. package/dist/hooks/useBusiness.d.ts +2 -2
  5. package/dist/hooks/useBusiness.d.ts.map +1 -1
  6. package/dist/hooks/useBusiness.js +2 -2
  7. package/dist/hooks/useIPFSUrl.d.ts +3 -3
  8. package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
  9. package/dist/hooks/useIPFSUrl.js +7 -7
  10. package/dist/hooks/useTransactions.d.ts.map +1 -1
  11. package/dist/hooks/useTransactions.js +13 -5
  12. package/dist/hooks/useWeb3.js +4 -4
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2412 -492
  16. package/dist/index.js.map +1 -1
  17. package/dist/providers/PersSDKProvider.d.ts +4 -0
  18. package/dist/providers/PersSDKProvider.d.ts.map +1 -1
  19. package/dist/providers/PersSDKProvider.js +14 -0
  20. package/dist/providers/react-native-http-client.d.ts +2 -0
  21. package/dist/providers/react-native-http-client.d.ts.map +1 -1
  22. package/dist/providers/react-native-http-client.js +5 -2
  23. package/dist/storage/rn-secure-storage.d.ts.map +1 -1
  24. package/dist/storage/rn-secure-storage.js +21 -8
  25. package/package.json +3 -3
  26. package/src/__tests__/PersSDKProvider.test.tsx +484 -420
  27. package/src/__tests__/setup.ts +40 -40
  28. package/src/hooks/index.ts +60 -60
  29. package/src/hooks/useAnalytics.ts +327 -327
  30. package/src/hooks/useAuth.ts +265 -265
  31. package/src/hooks/useBookings.ts +191 -191
  32. package/src/hooks/useBusiness.ts +251 -251
  33. package/src/hooks/useCampaigns.ts +500 -500
  34. package/src/hooks/useDonations.ts +66 -66
  35. package/src/hooks/useEvents.ts +372 -372
  36. package/src/hooks/useFiles.ts +155 -155
  37. package/src/hooks/useIPFSUrl.ts +192 -192
  38. package/src/hooks/usePurchases.ts +65 -65
  39. package/src/hooks/useRedemptions.ts +467 -467
  40. package/src/hooks/useTenants.ts +72 -72
  41. package/src/hooks/useTokenBalances.ts +385 -385
  42. package/src/hooks/useTokens.ts +313 -313
  43. package/src/hooks/useTransactionSigner.ts +611 -611
  44. package/src/hooks/useTransactions.ts +299 -290
  45. package/src/hooks/useTriggerSources.ts +301 -301
  46. package/src/hooks/useUserStatus.ts +61 -61
  47. package/src/hooks/useUsers.ts +167 -167
  48. package/src/hooks/useWeb3.ts +355 -355
  49. package/src/index.ts +777 -774
  50. package/src/polyfills/index.ts +160 -160
  51. package/src/polyfills/web3-polyfills.ts +97 -97
  52. package/src/providers/PersSDKProvider.tsx +393 -374
  53. package/src/providers/react-native-auth-provider.ts +110 -110
  54. package/src/providers/react-native-http-client.ts +150 -145
  55. package/src/providers/rn-dpop-provider.ts +190 -190
  56. package/src/storage/async-storage-token-storage.ts +185 -185
  57. package/src/storage/rn-secure-storage.ts +182 -170
  58. package/src/types/react-native-globals.d.ts +45 -45
  59. package/src/utils/index.ts +8 -8
  60. package/src/utils/platform-detection.ts +86 -86
  61. package/dist/index-CzLxqePZ.js +0 -7717
  62. package/dist/index-CzLxqePZ.js.map +0 -1
@@ -1,501 +1,501 @@
1
- import { useCallback } from 'react';
2
- import { usePersSDK } from '../providers/PersSDKProvider';
3
- import type {
4
- CampaignClaimRequestDTO,
5
- CampaignDTO,
6
- CampaignClaimDTO,
7
- CampaignTriggerDTO,
8
- PaginatedResponseDTO,
9
- CampaignClaimIncludeRelation,
10
- CampaignIncludeRelation,
11
- SortOrder,
12
- CampaignFilterOptions,
13
- // DynamicContext
14
- } from '@explorins/pers-sdk';
15
- import type { CampaignClaimFilters } from '@explorins/pers-sdk/campaign';
16
-
17
- // Re-export for consumers
18
- export type { CampaignClaimFilters } from '@explorins/pers-sdk/campaign';
19
- export type { CampaignFilterOptions } from '@explorins/pers-sdk';
20
-
21
- /**
22
- * Options for fetching campaigns
23
- * @deprecated Use CampaignFilterOptions from '@explorins/pers-sdk/campaign' instead
24
- */
25
- export interface GetCampaignsOptions {
26
- /** Filter by tag(s) - single tag or array for OR match */
27
- tags?: string | string[];
28
- /** Free text search across name and description (case-insensitive) */
29
- search?: string;
30
- /** Filter by business ID */
31
- businessId?: string;
32
- /** Filter campaigns starting on or after this date */
33
- startDate?: Date | string;
34
- /** Filter campaigns ending on or before this date */
35
- endDate?: Date | string;
36
- /** Page number (1-indexed) */
37
- page?: number;
38
- /** Items per page */
39
- limit?: number;
40
- /** Sort field */
41
- sortBy?: 'name' | 'createdAt' | 'startDate';
42
- /** Sort order */
43
- sortOrder?: SortOrder;
44
- /** Relations to include: 'triggerSources', 'businesses', 'claimCount' */
45
- include?: CampaignIncludeRelation[];
46
- }
47
-
48
- export const useCampaigns = () => {
49
- const { sdk, isInitialized, isAuthenticated } = usePersSDK();
50
-
51
- /**
52
- * Get active campaigns with optional filters and include relations
53
- *
54
- * @param options - Filter, pagination, and include options
55
- * @returns Promise resolving to paginated active campaigns
56
- *
57
- * @example
58
- * ```typescript
59
- * // Simple: get active campaigns with trigger sources
60
- * const { data: campaigns } = await getActiveCampaigns({ include: ['triggerSources'] });
61
- *
62
- * // With pagination
63
- * const { data, pagination } = await getActiveCampaigns({
64
- * page: 1,
65
- * limit: 10,
66
- * include: ['triggerSources', 'businesses']
67
- * });
68
- *
69
- * // Filter by date range
70
- * const { data } = await getActiveCampaigns({
71
- * startDate: '2026-05-01',
72
- * endDate: '2026-12-31'
73
- * });
74
- *
75
- * // Get campaigns with claim count
76
- * const { data } = await getActiveCampaigns({ include: ['claimCount'] });
77
- * data.forEach(c => console.log(`${c.name}: ${c.claimCount} claims`));
78
- * ```
79
- */
80
- const getActiveCampaigns = useCallback(async (
81
- options?: GetCampaignsOptions
82
- ): Promise<PaginatedResponseDTO<CampaignDTO>> => {
83
- if (!isInitialized || !sdk) {
84
- throw new Error('SDK not initialized. Call initialize() first.');
85
- }
86
-
87
- try {
88
- const result = await sdk.campaigns.getCampaigns({
89
- active: true,
90
- ...options
91
- });
92
- return result;
93
- } catch (error) {
94
- console.error('Failed to fetch active campaigns:', error);
95
- throw error;
96
- }
97
- }, [sdk, isInitialized]);
98
-
99
- /**
100
- * Get campaign by ID with optional include relations
101
- *
102
- * @param campaignId - The campaign ID
103
- * @param include - Relations to include: 'triggerSources', 'businesses', 'claimCount'
104
- * @returns Promise resolving to campaign data
105
- *
106
- * @example
107
- * ```typescript
108
- * // Get campaign with all related data
109
- * const campaign = await getCampaignById('campaign-123', ['triggerSources', 'businesses']);
110
- * console.log('Trigger sources:', campaign.included?.triggerSources);
111
- *
112
- * // Get campaign with claim count
113
- * const campaign = await getCampaignById('campaign-123', ['claimCount']);
114
- * console.log('Total claims:', campaign.claimCount);
115
- * ```
116
- */
117
- const getCampaignById = useCallback(async (
118
- campaignId: string,
119
- include?: CampaignIncludeRelation[]
120
- ): Promise<CampaignDTO | null> => {
121
- if (!isInitialized || !sdk) {
122
- throw new Error('SDK not initialized. Call initialize() first.');
123
- }
124
-
125
- try {
126
- const result = await sdk.campaigns.getCampaignById(campaignId, include);
127
- return result;
128
- } catch (error) {
129
- console.error('Failed to fetch campaign:', error);
130
- throw error;
131
- }
132
- }, [sdk, isInitialized]);
133
-
134
- /**
135
- * Claim a campaign reward with optional dynamic context
136
- *
137
- * Triggers a campaign claim for the authenticated user. The campaign must be active
138
- * and the user must meet all eligibility requirements. Supports dynamic context for
139
- * personalized NFT generation when the campaign issues ERC721 tokens.
140
- *
141
- * @param request - Campaign claim request with optional context
142
- * @param request.campaignId - ID of the campaign to claim (optional if using triggerSourceId)
143
- * @param request.triggerSourceId - ID of trigger source (QR, NFC, etc.)
144
- * @param request.context - Dynamic context for NFT personalization
145
- * @param request.metadata - Additional metadata for the claim
146
- * @param request.latitude - User's latitude for geo-validated claims
147
- * @param request.longitude - User's longitude for geo-validated claims
148
- * @returns Promise resolving to campaign claim result
149
- *
150
- * @example Basic campaign claim
151
- * ```typescript
152
- * const claim = await claimCampaign({
153
- * triggerSourceId: 'qr-code-123',
154
- * });
155
- * ```
156
- *
157
- * @example Campaign claim with dynamic context (personalized NFT)
158
- * ```typescript
159
- * import { DynamicContext } from '@explorins/pers-sdk-react-native';
160
- *
161
- * const context: DynamicContext = {
162
- * // Validity - token valid for 30 days
163
- * validityDate: new Date().toISOString(),
164
- * validityDuration: 30,
165
- *
166
- * // AI prompt context - available as {{context.xxx}} in prompts
167
- * visitorName: 'John Doe',
168
- * visitLocation: 'Grand Hotel Lobby',
169
- * membershipTier: 'Gold',
170
- * };
171
- *
172
- * const claim = await claimCampaign({
173
- * triggerSourceId: 'hotel-checkin-qr',
174
- * context,
175
- * });
176
- * ```
177
- *
178
- * @example Geo-validated claim with context
179
- * ```typescript
180
- * const claim = await claimCampaign({
181
- * triggerSourceId: 'museum-entrance-nfc',
182
- * latitude: 48.8606,
183
- * longitude: 2.3376,
184
- * context: {
185
- * visitorType: 'Adult',
186
- * ticketCategory: 'General Admission',
187
- * }
188
- * });
189
- * ```
190
- */
191
- const claimCampaign = useCallback(async (request: CampaignClaimRequestDTO): Promise<CampaignClaimDTO | null> => {
192
- if (!isInitialized || !sdk) {
193
- throw new Error('SDK not initialized. Call initialize() first.');
194
- }
195
- if (!isAuthenticated) {
196
- throw new Error('SDK not authenticated. claimCampaign requires authentication.');
197
- }
198
-
199
- try {
200
- const result = await sdk.campaigns.claimCampaign(request);
201
- return result;
202
- } catch (error) {
203
- console.error('Failed to claim campaign:', error);
204
- throw error;
205
- }
206
- }, [sdk, isInitialized, isAuthenticated]);
207
-
208
- /**
209
- * Get user's campaign claims with optional pagination and include relations
210
- *
211
- * @param options - Optional options including pagination and include relations
212
- * @returns Promise resolving to paginated campaign claims
213
- *
214
- * @example
215
- * ```typescript
216
- * // Get user's claims with campaign details
217
- * const { data: claims } = await getUserClaims({ include: ['campaign', 'business'] });
218
- * claims.forEach(claim => {
219
- * console.log('Campaign:', claim.included?.campaign?.name);
220
- * });
221
- *
222
- * // With pagination
223
- * const { data } = await getUserClaims({ page: 1, limit: 10, include: ['campaign'] });
224
- * ```
225
- */
226
- const getUserClaims = useCallback(async (options?: {
227
- page?: number;
228
- limit?: number;
229
- include?: CampaignClaimIncludeRelation[];
230
- }): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
231
- if (!isInitialized || !sdk) {
232
- throw new Error('SDK not initialized. Call initialize() first.');
233
- }
234
- if (!isAuthenticated) {
235
- console.warn('SDK not authenticated. getUserClaims requires authentication.');
236
- return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
237
- }
238
-
239
- try {
240
- const result = await sdk.campaigns.getUserClaims(options);
241
- return result;
242
- } catch (error) {
243
- console.error('Failed to fetch user claims:', error);
244
- throw error;
245
- }
246
- }, [sdk, isInitialized, isAuthenticated]);
247
-
248
- const getCampaignTriggers = useCallback(async (): Promise<PaginatedResponseDTO<CampaignTriggerDTO>> => {
249
- if (!isInitialized || !sdk) {
250
- throw new Error('SDK not initialized. Call initialize() first.');
251
- }
252
-
253
- try {
254
- const result = await sdk.campaigns.getCampaignTriggers();
255
- return result;
256
- } catch (error) {
257
- console.error('Failed to fetch campaign triggers:', error);
258
- throw error;
259
- }
260
- }, [sdk, isInitialized]);
261
-
262
- // Admin methods
263
- /**
264
- * Admin: Get all campaigns with optional filters and include relations
265
- *
266
- * @param options - Filter, pagination, and include options (same as getActiveCampaigns but active is optional)
267
- * @returns Promise resolving to paginated campaigns
268
- *
269
- * @example
270
- * ```typescript
271
- * // Get all campaigns
272
- * const { data } = await getAllCampaigns();
273
- *
274
- * // Get only active campaigns with trigger sources
275
- * const { data } = await getAllCampaigns({ active: true, include: ['triggerSources'] });
276
- *
277
- * // Filter by date range with claim count
278
- * const { data } = await getAllCampaigns({
279
- * startDate: '2026-05-01',
280
- * endDate: '2026-12-31',
281
- * include: ['claimCount']
282
- * });
283
- * data.forEach(c => console.log(`${c.name}: ${c.claimCount} claims`));
284
- *
285
- * // With pagination and sorting
286
- * const { data } = await getAllCampaigns({
287
- * page: 1,
288
- * limit: 20,
289
- * sortBy: 'createdAt',
290
- * sortOrder: 'DESC'
291
- * });
292
- * ```
293
- */
294
- const getAllCampaigns = useCallback(async (
295
- options?: GetCampaignsOptions & { active?: boolean }
296
- ): Promise<PaginatedResponseDTO<CampaignDTO>> => {
297
- if (!isInitialized || !sdk) {
298
- throw new Error('SDK not initialized. Call initialize() first.');
299
- }
300
-
301
- try {
302
- const result = await sdk.campaigns.getCampaigns(options);
303
- return result;
304
- } catch (error) {
305
- console.error('Failed to fetch all campaigns:', error);
306
- throw error;
307
- }
308
- }, [sdk, isInitialized]);
309
-
310
- /**
311
- * Admin: Get campaign claims with optional filters and include relations
312
- *
313
- * @param filters - Optional filters for campaign, user, business, and date range
314
- * @param filters.campaignId - Filter by specific campaign ID
315
- * @param filters.userId - Filter by user ID
316
- * @param filters.businessId - Filter by business ID
317
- * @param filters.dateFrom - Filter claims created on or after this date (ISO 8601 format)
318
- * @param filters.dateTo - Filter claims created on or before this date (ISO 8601 format)
319
- * @param filters.page - Page number (1-indexed)
320
- * @param filters.limit - Items per page
321
- * @param filters.sortBy - Field to sort by
322
- * @param filters.sortOrder - Sort direction
323
- * @param include - Relations to include: 'campaign', 'user', 'business'
324
- * @returns Promise resolving to paginated campaign claims
325
- *
326
- * @example
327
- * ```typescript
328
- * // Get all claims with user details
329
- * const { data: claims } = await getCampaignClaims({}, ['user']);
330
- *
331
- * // Get claims for a specific campaign
332
- * const { data } = await getCampaignClaims({ campaignId: 'campaign-123' }, ['user', 'business']);
333
- *
334
- * // Filter by date range
335
- * const { data } = await getCampaignClaims({
336
- * dateFrom: '2026-01-01',
337
- * dateTo: '2026-03-31'
338
- * }, ['campaign']);
339
- * ```
340
- */
341
- const getCampaignClaims = useCallback(async (
342
- filters?: CampaignClaimFilters,
343
- include?: CampaignClaimIncludeRelation[]
344
- ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
345
- if (!isInitialized || !sdk) {
346
- throw new Error('SDK not initialized. Call initialize() first.');
347
- }
348
-
349
- try {
350
- const result = await sdk.campaigns.getCampaignClaims(filters, include);
351
- return result;
352
- } catch (error) {
353
- console.error('Failed to fetch campaign claims:', error);
354
- throw error;
355
- }
356
- }, [sdk, isInitialized]);
357
-
358
- /**
359
- * Admin: Get campaign claims by user ID with optional include relations
360
- *
361
- * @param userId - The user ID
362
- * @param include - Relations to include: 'campaign', 'user', 'business'
363
- * @returns Promise resolving to paginated campaign claims
364
- */
365
- const getCampaignClaimsByUserId = useCallback(async (
366
- userId: string,
367
- include?: CampaignClaimIncludeRelation[]
368
- ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
369
- if (!isInitialized || !sdk) {
370
- throw new Error('SDK not initialized. Call initialize() first.');
371
- }
372
-
373
- try {
374
- const result = await sdk.campaigns.getCampaignClaimsByUserId(userId, undefined, include);
375
- return result;
376
- } catch (error) {
377
- console.error('Failed to fetch campaign claims by user ID:', error);
378
- throw error;
379
- }
380
- }, [sdk, isInitialized]);
381
-
382
- /**
383
- * Admin: Get campaign claims by business ID with optional include relations
384
- *
385
- * @param businessId - The business ID
386
- * @param include - Relations to include: 'campaign', 'user', 'business'
387
- * @returns Promise resolving to paginated campaign claims
388
- */
389
- const getCampaignClaimsByBusinessId = useCallback(async (
390
- businessId: string,
391
- include?: CampaignClaimIncludeRelation[]
392
- ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
393
- if (!isInitialized || !sdk) {
394
- throw new Error('SDK not initialized. Call initialize() first.');
395
- }
396
-
397
- try {
398
- const result = await sdk.campaigns.getCampaignClaimsByBusinessId(businessId, undefined, include);
399
- return result;
400
- } catch (error) {
401
- console.error('Failed to fetch campaign claims by business ID:', error);
402
- throw error;
403
- }
404
- }, [sdk, isInitialized]);
405
-
406
- // ==========================================
407
- // TRIGGER SOURCE ASSIGNMENT (Admin)
408
- // Note: TriggerSource CRUD is in useTriggerSources hook
409
- // ==========================================
410
-
411
- /**
412
- * Admin: Assign a trigger source to a campaign
413
- *
414
- * Associates a trigger source (QR code, NFC tag, etc.) with a campaign.
415
- * A campaign can have multiple trigger sources.
416
- *
417
- * Note: To create/update/delete trigger sources, use `useTriggerSources` hook.
418
- *
419
- * @param campaignId - Campaign UUID
420
- * @param triggerSourceId - Trigger source UUID
421
- * @returns Promise resolving to updated campaign
422
- *
423
- * @example
424
- * ```typescript
425
- * const { create } = useTriggerSources();
426
- * const { assignTriggerSource } = useCampaigns();
427
- *
428
- * // Create trigger source first
429
- * const source = await create({ type: 'QR_CODE', name: 'Store QR' });
430
- *
431
- * // Then assign to campaign
432
- * const updated = await assignTriggerSource('campaign-123', source.id);
433
- * ```
434
- */
435
- const assignTriggerSource = useCallback(async (
436
- campaignId: string,
437
- triggerSourceId: string
438
- ): Promise<CampaignDTO> => {
439
- if (!isInitialized || !sdk) {
440
- throw new Error('SDK not initialized. Call initialize() first.');
441
- }
442
- if (!isAuthenticated) {
443
- throw new Error('SDK not authenticated. assignTriggerSource requires admin authentication.');
444
- }
445
-
446
- try {
447
- const result = await sdk.campaigns.assignTriggerSource(campaignId, triggerSourceId);
448
- return result;
449
- } catch (error) {
450
- console.error('Failed to assign trigger source:', error);
451
- throw error;
452
- }
453
- }, [sdk, isInitialized, isAuthenticated]);
454
-
455
- /**
456
- * Admin: Remove a trigger source from a campaign
457
- *
458
- * Removes the association between a trigger source and a campaign.
459
- * The trigger source itself is not deleted.
460
- *
461
- * @param campaignId - Campaign UUID
462
- * @param triggerSourceId - Trigger source UUID
463
- * @returns Promise resolving to updated campaign
464
- */
465
- const removeTriggerSource = useCallback(async (
466
- campaignId: string,
467
- triggerSourceId: string
468
- ): Promise<CampaignDTO> => {
469
- if (!isInitialized || !sdk) {
470
- throw new Error('SDK not initialized. Call initialize() first.');
471
- }
472
- if (!isAuthenticated) {
473
- throw new Error('SDK not authenticated. removeTriggerSource requires admin authentication.');
474
- }
475
-
476
- try {
477
- const result = await sdk.campaigns.removeTriggerSource(campaignId, triggerSourceId);
478
- return result;
479
- } catch (error) {
480
- console.error('Failed to remove trigger source:', error);
481
- throw error;
482
- }
483
- }, [sdk, isInitialized, isAuthenticated]);
484
-
485
- return {
486
- getActiveCampaigns,
487
- getCampaignById,
488
- claimCampaign,
489
- getUserClaims,
490
- getCampaignTriggers,
491
- getAllCampaigns,
492
- getCampaignClaims,
493
- getCampaignClaimsByUserId,
494
- getCampaignClaimsByBusinessId,
495
- assignTriggerSource,
496
- removeTriggerSource,
497
- isAvailable: isInitialized && !!sdk?.campaigns,
498
- };
499
- };
500
-
1
+ import { useCallback } from 'react';
2
+ import { usePersSDK } from '../providers/PersSDKProvider';
3
+ import type {
4
+ CampaignClaimRequestDTO,
5
+ CampaignDTO,
6
+ CampaignClaimDTO,
7
+ CampaignTriggerDTO,
8
+ PaginatedResponseDTO,
9
+ CampaignClaimIncludeRelation,
10
+ CampaignIncludeRelation,
11
+ SortOrder,
12
+ CampaignFilterOptions,
13
+ // DynamicContext
14
+ } from '@explorins/pers-sdk';
15
+ import type { CampaignClaimFilters } from '@explorins/pers-sdk/campaign';
16
+
17
+ // Re-export for consumers
18
+ export type { CampaignClaimFilters } from '@explorins/pers-sdk/campaign';
19
+ export type { CampaignFilterOptions } from '@explorins/pers-sdk';
20
+
21
+ /**
22
+ * Options for fetching campaigns
23
+ * @deprecated Use CampaignFilterOptions from '@explorins/pers-sdk/campaign' instead
24
+ */
25
+ export interface GetCampaignsOptions {
26
+ /** Filter by tag(s) - single tag or array for OR match */
27
+ tags?: string | string[];
28
+ /** Free text search across name and description (case-insensitive) */
29
+ search?: string;
30
+ /** Filter by business ID */
31
+ businessId?: string;
32
+ /** Filter campaigns starting on or after this date */
33
+ startDate?: Date | string;
34
+ /** Filter campaigns ending on or before this date */
35
+ endDate?: Date | string;
36
+ /** Page number (1-indexed) */
37
+ page?: number;
38
+ /** Items per page */
39
+ limit?: number;
40
+ /** Sort field */
41
+ sortBy?: 'name' | 'createdAt' | 'startDate';
42
+ /** Sort order */
43
+ sortOrder?: SortOrder;
44
+ /** Relations to include: 'triggerSources', 'businesses', 'claimCount' */
45
+ include?: CampaignIncludeRelation[];
46
+ }
47
+
48
+ export const useCampaigns = () => {
49
+ const { sdk, isInitialized, isAuthenticated } = usePersSDK();
50
+
51
+ /**
52
+ * Get active campaigns with optional filters and include relations
53
+ *
54
+ * @param options - Filter, pagination, and include options
55
+ * @returns Promise resolving to paginated active campaigns
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Simple: get active campaigns with trigger sources
60
+ * const { data: campaigns } = await getActiveCampaigns({ include: ['triggerSources'] });
61
+ *
62
+ * // With pagination
63
+ * const { data, pagination } = await getActiveCampaigns({
64
+ * page: 1,
65
+ * limit: 10,
66
+ * include: ['triggerSources', 'businesses']
67
+ * });
68
+ *
69
+ * // Filter by date range
70
+ * const { data } = await getActiveCampaigns({
71
+ * startDate: '2026-05-01',
72
+ * endDate: '2026-12-31'
73
+ * });
74
+ *
75
+ * // Get campaigns with claim count
76
+ * const { data } = await getActiveCampaigns({ include: ['claimCount'] });
77
+ * data.forEach(c => console.log(`${c.name}: ${c.claimCount} claims`));
78
+ * ```
79
+ */
80
+ const getActiveCampaigns = useCallback(async (
81
+ options?: GetCampaignsOptions
82
+ ): Promise<PaginatedResponseDTO<CampaignDTO>> => {
83
+ if (!isInitialized || !sdk) {
84
+ throw new Error('SDK not initialized. Call initialize() first.');
85
+ }
86
+
87
+ try {
88
+ const result = await sdk.campaigns.getCampaigns({
89
+ active: true,
90
+ ...options
91
+ });
92
+ return result;
93
+ } catch (error) {
94
+ console.error('Failed to fetch active campaigns:', error);
95
+ throw error;
96
+ }
97
+ }, [sdk, isInitialized]);
98
+
99
+ /**
100
+ * Get campaign by ID with optional include relations
101
+ *
102
+ * @param campaignId - The campaign ID
103
+ * @param include - Relations to include: 'triggerSources', 'businesses', 'claimCount'
104
+ * @returns Promise resolving to campaign data
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * // Get campaign with all related data
109
+ * const campaign = await getCampaignById('campaign-123', ['triggerSources', 'businesses']);
110
+ * console.log('Trigger sources:', campaign.included?.triggerSources);
111
+ *
112
+ * // Get campaign with claim count
113
+ * const campaign = await getCampaignById('campaign-123', ['claimCount']);
114
+ * console.log('Total claims:', campaign.claimCount);
115
+ * ```
116
+ */
117
+ const getCampaignById = useCallback(async (
118
+ campaignId: string,
119
+ include?: CampaignIncludeRelation[]
120
+ ): Promise<CampaignDTO | null> => {
121
+ if (!isInitialized || !sdk) {
122
+ throw new Error('SDK not initialized. Call initialize() first.');
123
+ }
124
+
125
+ try {
126
+ const result = await sdk.campaigns.getCampaignById(campaignId, include);
127
+ return result;
128
+ } catch (error) {
129
+ console.error('Failed to fetch campaign:', error);
130
+ throw error;
131
+ }
132
+ }, [sdk, isInitialized]);
133
+
134
+ /**
135
+ * Claim a campaign reward with optional dynamic context
136
+ *
137
+ * Triggers a campaign claim for the authenticated user. The campaign must be active
138
+ * and the user must meet all eligibility requirements. Supports dynamic context for
139
+ * personalized NFT generation when the campaign issues ERC721 tokens.
140
+ *
141
+ * @param request - Campaign claim request with optional context
142
+ * @param request.campaignId - ID of the campaign to claim (optional if using triggerSourceId)
143
+ * @param request.triggerSourceId - ID of trigger source (QR, NFC, etc.)
144
+ * @param request.context - Dynamic context for NFT personalization
145
+ * @param request.metadata - Additional metadata for the claim
146
+ * @param request.latitude - User's latitude for geo-validated claims
147
+ * @param request.longitude - User's longitude for geo-validated claims
148
+ * @returns Promise resolving to campaign claim result
149
+ *
150
+ * @example Basic campaign claim
151
+ * ```typescript
152
+ * const claim = await claimCampaign({
153
+ * triggerSourceId: 'qr-code-123',
154
+ * });
155
+ * ```
156
+ *
157
+ * @example Campaign claim with dynamic context (personalized NFT)
158
+ * ```typescript
159
+ * import { DynamicContext } from '@explorins/pers-sdk-react-native';
160
+ *
161
+ * const context: DynamicContext = {
162
+ * // Validity - token valid for 30 days
163
+ * validityDate: new Date().toISOString(),
164
+ * validityDuration: 30,
165
+ *
166
+ * // AI prompt context - available as {{context.xxx}} in prompts
167
+ * visitorName: 'John Doe',
168
+ * visitLocation: 'Grand Hotel Lobby',
169
+ * membershipTier: 'Gold',
170
+ * };
171
+ *
172
+ * const claim = await claimCampaign({
173
+ * triggerSourceId: 'hotel-checkin-qr',
174
+ * context,
175
+ * });
176
+ * ```
177
+ *
178
+ * @example Geo-validated claim with context
179
+ * ```typescript
180
+ * const claim = await claimCampaign({
181
+ * triggerSourceId: 'museum-entrance-nfc',
182
+ * latitude: 48.8606,
183
+ * longitude: 2.3376,
184
+ * context: {
185
+ * visitorType: 'Adult',
186
+ * ticketCategory: 'General Admission',
187
+ * }
188
+ * });
189
+ * ```
190
+ */
191
+ const claimCampaign = useCallback(async (request: CampaignClaimRequestDTO): Promise<CampaignClaimDTO | null> => {
192
+ if (!isInitialized || !sdk) {
193
+ throw new Error('SDK not initialized. Call initialize() first.');
194
+ }
195
+ if (!isAuthenticated) {
196
+ throw new Error('SDK not authenticated. claimCampaign requires authentication.');
197
+ }
198
+
199
+ try {
200
+ const result = await sdk.campaigns.claimCampaign(request);
201
+ return result;
202
+ } catch (error) {
203
+ console.error('Failed to claim campaign:', error);
204
+ throw error;
205
+ }
206
+ }, [sdk, isInitialized, isAuthenticated]);
207
+
208
+ /**
209
+ * Get user's campaign claims with optional pagination and include relations
210
+ *
211
+ * @param options - Optional options including pagination and include relations
212
+ * @returns Promise resolving to paginated campaign claims
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * // Get user's claims with campaign details
217
+ * const { data: claims } = await getUserClaims({ include: ['campaign', 'business'] });
218
+ * claims.forEach(claim => {
219
+ * console.log('Campaign:', claim.included?.campaign?.name);
220
+ * });
221
+ *
222
+ * // With pagination
223
+ * const { data } = await getUserClaims({ page: 1, limit: 10, include: ['campaign'] });
224
+ * ```
225
+ */
226
+ const getUserClaims = useCallback(async (options?: {
227
+ page?: number;
228
+ limit?: number;
229
+ include?: CampaignClaimIncludeRelation[];
230
+ }): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
231
+ if (!isInitialized || !sdk) {
232
+ throw new Error('SDK not initialized. Call initialize() first.');
233
+ }
234
+ if (!isAuthenticated) {
235
+ console.warn('SDK not authenticated. getUserClaims requires authentication.');
236
+ return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
237
+ }
238
+
239
+ try {
240
+ const result = await sdk.campaigns.getUserClaims(options);
241
+ return result;
242
+ } catch (error) {
243
+ console.error('Failed to fetch user claims:', error);
244
+ throw error;
245
+ }
246
+ }, [sdk, isInitialized, isAuthenticated]);
247
+
248
+ const getCampaignTriggers = useCallback(async (): Promise<PaginatedResponseDTO<CampaignTriggerDTO>> => {
249
+ if (!isInitialized || !sdk) {
250
+ throw new Error('SDK not initialized. Call initialize() first.');
251
+ }
252
+
253
+ try {
254
+ const result = await sdk.campaigns.getCampaignTriggers();
255
+ return result;
256
+ } catch (error) {
257
+ console.error('Failed to fetch campaign triggers:', error);
258
+ throw error;
259
+ }
260
+ }, [sdk, isInitialized]);
261
+
262
+ // Admin methods
263
+ /**
264
+ * Admin: Get all campaigns with optional filters and include relations
265
+ *
266
+ * @param options - Filter, pagination, and include options (same as getActiveCampaigns but active is optional)
267
+ * @returns Promise resolving to paginated campaigns
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * // Get all campaigns
272
+ * const { data } = await getAllCampaigns();
273
+ *
274
+ * // Get only active campaigns with trigger sources
275
+ * const { data } = await getAllCampaigns({ active: true, include: ['triggerSources'] });
276
+ *
277
+ * // Filter by date range with claim count
278
+ * const { data } = await getAllCampaigns({
279
+ * startDate: '2026-05-01',
280
+ * endDate: '2026-12-31',
281
+ * include: ['claimCount']
282
+ * });
283
+ * data.forEach(c => console.log(`${c.name}: ${c.claimCount} claims`));
284
+ *
285
+ * // With pagination and sorting
286
+ * const { data } = await getAllCampaigns({
287
+ * page: 1,
288
+ * limit: 20,
289
+ * sortBy: 'createdAt',
290
+ * sortOrder: 'DESC'
291
+ * });
292
+ * ```
293
+ */
294
+ const getAllCampaigns = useCallback(async (
295
+ options?: GetCampaignsOptions & { active?: boolean }
296
+ ): Promise<PaginatedResponseDTO<CampaignDTO>> => {
297
+ if (!isInitialized || !sdk) {
298
+ throw new Error('SDK not initialized. Call initialize() first.');
299
+ }
300
+
301
+ try {
302
+ const result = await sdk.campaigns.getCampaigns(options);
303
+ return result;
304
+ } catch (error) {
305
+ console.error('Failed to fetch all campaigns:', error);
306
+ throw error;
307
+ }
308
+ }, [sdk, isInitialized]);
309
+
310
+ /**
311
+ * Admin: Get campaign claims with optional filters and include relations
312
+ *
313
+ * @param filters - Optional filters for campaign, user, business, and date range
314
+ * @param filters.campaignId - Filter by specific campaign ID
315
+ * @param filters.userId - Filter by user ID
316
+ * @param filters.businessId - Filter by business ID
317
+ * @param filters.dateFrom - Filter claims created on or after this date (ISO 8601 format)
318
+ * @param filters.dateTo - Filter claims created on or before this date (ISO 8601 format)
319
+ * @param filters.page - Page number (1-indexed)
320
+ * @param filters.limit - Items per page
321
+ * @param filters.sortBy - Field to sort by
322
+ * @param filters.sortOrder - Sort direction
323
+ * @param include - Relations to include: 'campaign', 'user', 'business'
324
+ * @returns Promise resolving to paginated campaign claims
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * // Get all claims with user details
329
+ * const { data: claims } = await getCampaignClaims({}, ['user']);
330
+ *
331
+ * // Get claims for a specific campaign
332
+ * const { data } = await getCampaignClaims({ campaignId: 'campaign-123' }, ['user', 'business']);
333
+ *
334
+ * // Filter by date range
335
+ * const { data } = await getCampaignClaims({
336
+ * dateFrom: '2026-01-01',
337
+ * dateTo: '2026-03-31'
338
+ * }, ['campaign']);
339
+ * ```
340
+ */
341
+ const getCampaignClaims = useCallback(async (
342
+ filters?: CampaignClaimFilters,
343
+ include?: CampaignClaimIncludeRelation[]
344
+ ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
345
+ if (!isInitialized || !sdk) {
346
+ throw new Error('SDK not initialized. Call initialize() first.');
347
+ }
348
+
349
+ try {
350
+ const result = await sdk.campaigns.getCampaignClaims(filters, include);
351
+ return result;
352
+ } catch (error) {
353
+ console.error('Failed to fetch campaign claims:', error);
354
+ throw error;
355
+ }
356
+ }, [sdk, isInitialized]);
357
+
358
+ /**
359
+ * Admin: Get campaign claims by user ID with optional include relations
360
+ *
361
+ * @param userId - The user ID
362
+ * @param include - Relations to include: 'campaign', 'user', 'business'
363
+ * @returns Promise resolving to paginated campaign claims
364
+ */
365
+ const getCampaignClaimsByUserId = useCallback(async (
366
+ userId: string,
367
+ include?: CampaignClaimIncludeRelation[]
368
+ ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
369
+ if (!isInitialized || !sdk) {
370
+ throw new Error('SDK not initialized. Call initialize() first.');
371
+ }
372
+
373
+ try {
374
+ const result = await sdk.campaigns.getCampaignClaimsByUserId(userId, undefined, include);
375
+ return result;
376
+ } catch (error) {
377
+ console.error('Failed to fetch campaign claims by user ID:', error);
378
+ throw error;
379
+ }
380
+ }, [sdk, isInitialized]);
381
+
382
+ /**
383
+ * Admin: Get campaign claims by business ID with optional include relations
384
+ *
385
+ * @param businessId - The business ID
386
+ * @param include - Relations to include: 'campaign', 'user', 'business'
387
+ * @returns Promise resolving to paginated campaign claims
388
+ */
389
+ const getCampaignClaimsByBusinessId = useCallback(async (
390
+ businessId: string,
391
+ include?: CampaignClaimIncludeRelation[]
392
+ ): Promise<PaginatedResponseDTO<CampaignClaimDTO>> => {
393
+ if (!isInitialized || !sdk) {
394
+ throw new Error('SDK not initialized. Call initialize() first.');
395
+ }
396
+
397
+ try {
398
+ const result = await sdk.campaigns.getCampaignClaimsByBusinessId(businessId, undefined, include);
399
+ return result;
400
+ } catch (error) {
401
+ console.error('Failed to fetch campaign claims by business ID:', error);
402
+ throw error;
403
+ }
404
+ }, [sdk, isInitialized]);
405
+
406
+ // ==========================================
407
+ // TRIGGER SOURCE ASSIGNMENT (Admin)
408
+ // Note: TriggerSource CRUD is in useTriggerSources hook
409
+ // ==========================================
410
+
411
+ /**
412
+ * Admin: Assign a trigger source to a campaign
413
+ *
414
+ * Associates a trigger source (QR code, NFC tag, etc.) with a campaign.
415
+ * A campaign can have multiple trigger sources.
416
+ *
417
+ * Note: To create/update/delete trigger sources, use `useTriggerSources` hook.
418
+ *
419
+ * @param campaignId - Campaign UUID
420
+ * @param triggerSourceId - Trigger source UUID
421
+ * @returns Promise resolving to updated campaign
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * const { create } = useTriggerSources();
426
+ * const { assignTriggerSource } = useCampaigns();
427
+ *
428
+ * // Create trigger source first
429
+ * const source = await create({ type: 'QR_CODE', name: 'Store QR' });
430
+ *
431
+ * // Then assign to campaign
432
+ * const updated = await assignTriggerSource('campaign-123', source.id);
433
+ * ```
434
+ */
435
+ const assignTriggerSource = useCallback(async (
436
+ campaignId: string,
437
+ triggerSourceId: string
438
+ ): Promise<CampaignDTO> => {
439
+ if (!isInitialized || !sdk) {
440
+ throw new Error('SDK not initialized. Call initialize() first.');
441
+ }
442
+ if (!isAuthenticated) {
443
+ throw new Error('SDK not authenticated. assignTriggerSource requires admin authentication.');
444
+ }
445
+
446
+ try {
447
+ const result = await sdk.campaigns.assignTriggerSource(campaignId, triggerSourceId);
448
+ return result;
449
+ } catch (error) {
450
+ console.error('Failed to assign trigger source:', error);
451
+ throw error;
452
+ }
453
+ }, [sdk, isInitialized, isAuthenticated]);
454
+
455
+ /**
456
+ * Admin: Remove a trigger source from a campaign
457
+ *
458
+ * Removes the association between a trigger source and a campaign.
459
+ * The trigger source itself is not deleted.
460
+ *
461
+ * @param campaignId - Campaign UUID
462
+ * @param triggerSourceId - Trigger source UUID
463
+ * @returns Promise resolving to updated campaign
464
+ */
465
+ const removeTriggerSource = useCallback(async (
466
+ campaignId: string,
467
+ triggerSourceId: string
468
+ ): Promise<CampaignDTO> => {
469
+ if (!isInitialized || !sdk) {
470
+ throw new Error('SDK not initialized. Call initialize() first.');
471
+ }
472
+ if (!isAuthenticated) {
473
+ throw new Error('SDK not authenticated. removeTriggerSource requires admin authentication.');
474
+ }
475
+
476
+ try {
477
+ const result = await sdk.campaigns.removeTriggerSource(campaignId, triggerSourceId);
478
+ return result;
479
+ } catch (error) {
480
+ console.error('Failed to remove trigger source:', error);
481
+ throw error;
482
+ }
483
+ }, [sdk, isInitialized, isAuthenticated]);
484
+
485
+ return {
486
+ getActiveCampaigns,
487
+ getCampaignById,
488
+ claimCampaign,
489
+ getUserClaims,
490
+ getCampaignTriggers,
491
+ getAllCampaigns,
492
+ getCampaignClaims,
493
+ getCampaignClaimsByUserId,
494
+ getCampaignClaimsByBusinessId,
495
+ assignTriggerSource,
496
+ removeTriggerSource,
497
+ isAvailable: isInitialized && !!sdk?.campaigns,
498
+ };
499
+ };
500
+
501
501
  export type CampaignHook = ReturnType<typeof useCampaigns>;