@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.
- package/LICENSE +21 -0
- package/README.md +1266 -1244
- package/dist/__tests__/PersSDKProvider.test.js +33 -0
- package/dist/hooks/useBusiness.d.ts +2 -2
- package/dist/hooks/useBusiness.d.ts.map +1 -1
- package/dist/hooks/useBusiness.js +2 -2
- package/dist/hooks/useIPFSUrl.d.ts +3 -3
- package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
- package/dist/hooks/useIPFSUrl.js +7 -7
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/hooks/useTransactions.js +13 -5
- package/dist/hooks/useWeb3.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2412 -492
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +4 -0
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +14 -0
- package/dist/providers/react-native-http-client.d.ts +2 -0
- package/dist/providers/react-native-http-client.d.ts.map +1 -1
- package/dist/providers/react-native-http-client.js +5 -2
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +21 -8
- package/package.json +3 -3
- package/src/__tests__/PersSDKProvider.test.tsx +484 -420
- package/src/__tests__/setup.ts +40 -40
- package/src/hooks/index.ts +60 -60
- package/src/hooks/useAnalytics.ts +327 -327
- package/src/hooks/useAuth.ts +265 -265
- package/src/hooks/useBookings.ts +191 -191
- package/src/hooks/useBusiness.ts +251 -251
- package/src/hooks/useCampaigns.ts +500 -500
- package/src/hooks/useDonations.ts +66 -66
- package/src/hooks/useEvents.ts +372 -372
- package/src/hooks/useFiles.ts +155 -155
- package/src/hooks/useIPFSUrl.ts +192 -192
- package/src/hooks/usePurchases.ts +65 -65
- package/src/hooks/useRedemptions.ts +467 -467
- package/src/hooks/useTenants.ts +72 -72
- package/src/hooks/useTokenBalances.ts +385 -385
- package/src/hooks/useTokens.ts +313 -313
- package/src/hooks/useTransactionSigner.ts +611 -611
- package/src/hooks/useTransactions.ts +299 -290
- package/src/hooks/useTriggerSources.ts +301 -301
- package/src/hooks/useUserStatus.ts +61 -61
- package/src/hooks/useUsers.ts +167 -167
- package/src/hooks/useWeb3.ts +355 -355
- package/src/index.ts +777 -774
- package/src/polyfills/index.ts +160 -160
- package/src/polyfills/web3-polyfills.ts +97 -97
- package/src/providers/PersSDKProvider.tsx +393 -374
- package/src/providers/react-native-auth-provider.ts +110 -110
- package/src/providers/react-native-http-client.ts +150 -145
- package/src/providers/rn-dpop-provider.ts +190 -190
- package/src/storage/async-storage-token-storage.ts +185 -185
- package/src/storage/rn-secure-storage.ts +182 -170
- package/src/types/react-native-globals.d.ts +45 -45
- package/src/utils/index.ts +8 -8
- package/src/utils/platform-detection.ts +86 -86
- package/dist/index-CzLxqePZ.js +0 -7717
- package/dist/index-CzLxqePZ.js.map +0 -1
|
@@ -1,468 +1,468 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import { usePersSDK } from '../providers/PersSDKProvider';
|
|
3
|
-
import { SubmissionResult, useTransactionSigner, OnStatusUpdateFn } from './useTransactionSigner';
|
|
4
|
-
import type {
|
|
5
|
-
RedemptionCreateRequestDTO,
|
|
6
|
-
RedemptionDTO,
|
|
7
|
-
RedemptionRedeemDTO,
|
|
8
|
-
RedemptionRedeemRequestResponseDTO,
|
|
9
|
-
RedemptionTypeDTO,
|
|
10
|
-
PaginatedResponseDTO,
|
|
11
|
-
RedemptionRedeemIncludeRelation,
|
|
12
|
-
RedemptionIncludeRelation,
|
|
13
|
-
ProcessRecordStatus,
|
|
14
|
-
DynamicContext,
|
|
15
|
-
SortOrder
|
|
16
|
-
} from '@explorins/pers-sdk';
|
|
17
|
-
import type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
|
|
18
|
-
import type { PaginationOptions } from '@explorins/pers-sdk';
|
|
19
|
-
|
|
20
|
-
// Re-export for consumers (new names + deprecated aliases)
|
|
21
|
-
export type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
|
|
22
|
-
/** @deprecated Use RedemptionRedeemQueryParams instead */
|
|
23
|
-
export type RedemptionRedeemFilters = RedemptionRedeemQueryParams;
|
|
24
|
-
/** @deprecated Use RedemptionQueryParams instead */
|
|
25
|
-
export type RedemptionFilterOptions = RedemptionQueryParams;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Options for fetching redemptions with filtering and pagination
|
|
29
|
-
*/
|
|
30
|
-
export interface GetRedemptionsOptions {
|
|
31
|
-
/** Filter by active status (Admin only - regular users always see active offers) */
|
|
32
|
-
active?: boolean;
|
|
33
|
-
/** Free text search across name, description, tags (case-insensitive) */
|
|
34
|
-
search?: string;
|
|
35
|
-
/** Filter by tag(s) - single tag or array for OR match */
|
|
36
|
-
tags?: string | string[];
|
|
37
|
-
/** Filter redemptions starting on or after this date */
|
|
38
|
-
startDate?: Date | string;
|
|
39
|
-
/** Filter redemptions ending on or before this date */
|
|
40
|
-
endDate?: Date | string;
|
|
41
|
-
/** Relations to include: 'redeemCount' */
|
|
42
|
-
include?: RedemptionIncludeRelation[];
|
|
43
|
-
/** Page number (1-indexed) */
|
|
44
|
-
page?: number;
|
|
45
|
-
/** Results per page */
|
|
46
|
-
limit?: number;
|
|
47
|
-
/** Field to sort by */
|
|
48
|
-
sortBy?: 'name' | 'createdAt' | 'startDate';
|
|
49
|
-
/** Sort direction */
|
|
50
|
-
sortOrder?: SortOrder;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const useRedemptions = () => {
|
|
54
|
-
const { sdk, isInitialized, isAuthenticated } = usePersSDK();
|
|
55
|
-
const { signAndSubmitTransactionWithJWT, isSignerAvailable, currentStatus, statusMessage } = useTransactionSigner();
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get redemption offers based on user permissions
|
|
59
|
-
*
|
|
60
|
-
* **Regular Users:** Automatically see only active redemptions available for purchase
|
|
61
|
-
* **Administrators:** Can see all redemptions and filter by active/inactive status
|
|
62
|
-
*
|
|
63
|
-
* The backend enforces permission-based filtering automatically.
|
|
64
|
-
*
|
|
65
|
-
* @param options - Filter and pagination options
|
|
66
|
-
* @param options.active - Filter by active status (Admin only)
|
|
67
|
-
* @param options.search - Free text search across name, description, tags
|
|
68
|
-
* @param options.startDate - Filter redemptions starting on or after this date
|
|
69
|
-
* @param options.endDate - Filter redemptions ending on or before this date
|
|
70
|
-
* @param options.include - Relations to include: 'redeemCount'
|
|
71
|
-
* @param options.page - Page number (1-indexed)
|
|
72
|
-
* @param options.limit - Results per page
|
|
73
|
-
* @param options.sortBy - Field to sort by ('name' | 'createdAt')
|
|
74
|
-
* @param options.sortOrder - Sort direction ('ASC' | 'DESC')
|
|
75
|
-
*
|
|
76
|
-
* @example Basic usage
|
|
77
|
-
* ```typescript
|
|
78
|
-
* const { data: offers } = await getRedemptions();
|
|
79
|
-
* ```
|
|
80
|
-
*
|
|
81
|
-
* @example Filter by date range
|
|
82
|
-
* ```typescript
|
|
83
|
-
* const { data: dateFiltered } = await getRedemptions({
|
|
84
|
-
* startDate: '2026-05-01',
|
|
85
|
-
* endDate: '2026-06-30'
|
|
86
|
-
* });
|
|
87
|
-
* ```
|
|
88
|
-
*
|
|
89
|
-
* @example Get redemptions with redeem count
|
|
90
|
-
* ```typescript
|
|
91
|
-
* const { data: offers } = await getRedemptions({ include: ['redeemCount'] });
|
|
92
|
-
* offers.forEach(o => console.log(`${o.name}: ${o.redeemCount} redeems`));
|
|
93
|
-
* ```
|
|
94
|
-
*
|
|
95
|
-
* @example Search with pagination
|
|
96
|
-
* ```typescript
|
|
97
|
-
* const { data, pagination } = await getRedemptions({
|
|
98
|
-
* search: 'discount',
|
|
99
|
-
* page: 1,
|
|
100
|
-
* limit: 10,
|
|
101
|
-
* sortBy: 'name',
|
|
102
|
-
* sortOrder: 'ASC'
|
|
103
|
-
* });
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
const getRedemptions = useCallback(async (options?: GetRedemptionsOptions): Promise<PaginatedResponseDTO<RedemptionDTO>> => {
|
|
107
|
-
if (!isInitialized || !sdk) {
|
|
108
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// Convert Date to ISO string for API compatibility
|
|
113
|
-
const apiOptions = options ? {
|
|
114
|
-
...options,
|
|
115
|
-
startDate: options.startDate instanceof Date ? options.startDate.toISOString() : options.startDate,
|
|
116
|
-
endDate: options.endDate instanceof Date ? options.endDate.toISOString() : options.endDate,
|
|
117
|
-
} : undefined;
|
|
118
|
-
|
|
119
|
-
const result = await sdk.redemptions.getRedemptions(apiOptions);
|
|
120
|
-
return result;
|
|
121
|
-
} catch (error) {
|
|
122
|
-
console.error('Failed to fetch redemptions:', error);
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
}, [sdk, isInitialized]);
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get user's redemption history with optional filters, pagination and include relations
|
|
129
|
-
*
|
|
130
|
-
* @param filters - Optional filters (redemptionId, status)
|
|
131
|
-
* @param options - Pagination options (page, limit, sortBy, sortOrder)
|
|
132
|
-
* @param include - Relations to include: 'redemption', 'user', 'business', 'transactions'
|
|
133
|
-
* @returns Promise resolving to paginated redemption history
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```typescript
|
|
137
|
-
* // Get first page of user redemptions
|
|
138
|
-
* const { data: redeems, pagination } = await getUserRedemptions({}, { page: 1, limit: 10 });
|
|
139
|
-
* console.log(`Page ${pagination.page} of ${pagination.pages}`);
|
|
140
|
-
*
|
|
141
|
-
* // Get only completed redemptions
|
|
142
|
-
* import { ProcessRecordStatus } from '@explorins/pers-shared';
|
|
143
|
-
* const { data: completed } = await getUserRedemptions({ status: ProcessRecordStatus.COMPLETED });
|
|
144
|
-
*
|
|
145
|
-
* // Get user redemptions with full redemption details
|
|
146
|
-
* const { data: redeems } = await getUserRedemptions({}, {}, ['redemption', 'business']);
|
|
147
|
-
* redeems.forEach(redeem => {
|
|
148
|
-
* console.log('Redemption:', redeem.included?.redemption?.name);
|
|
149
|
-
* console.log('Business:', redeem.included?.business?.displayName);
|
|
150
|
-
* });
|
|
151
|
-
* ```
|
|
152
|
-
*/
|
|
153
|
-
const getUserRedemptions = useCallback(async (
|
|
154
|
-
filters?: { redemptionId?: string; status?: ProcessRecordStatus },
|
|
155
|
-
options?: PaginationOptions,
|
|
156
|
-
include?: RedemptionRedeemIncludeRelation[]
|
|
157
|
-
): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
|
|
158
|
-
if (!isInitialized || !sdk) {
|
|
159
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
160
|
-
}
|
|
161
|
-
if (!isAuthenticated) {
|
|
162
|
-
console.warn('SDK not authenticated. getUserRedemptions requires authentication.');
|
|
163
|
-
return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const result = await sdk.redemptions.getUserRedemptions(filters, options, include);
|
|
168
|
-
return result;
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('Failed to fetch user redemptions:', error);
|
|
171
|
-
throw error;
|
|
172
|
-
}
|
|
173
|
-
}, [sdk, isInitialized, isAuthenticated]);
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Redeem a redemption offer with optional dynamic context
|
|
177
|
-
*
|
|
178
|
-
* ## When to use `redeem()` vs `createTransaction()`
|
|
179
|
-
*
|
|
180
|
-
* | Use Case | Method | Auto-Signs |
|
|
181
|
-
* |----------|--------|------------|
|
|
182
|
-
* | **Voucher redemption** | `redeem()` | ✅ Yes |
|
|
183
|
-
* | **Reward redemption** | `redeem()` | ✅ Yes |
|
|
184
|
-
* | **Direct token transfer** | `createTransaction()` | ⚠️ Depends |
|
|
185
|
-
* | **POS burn with QR** | `buildPOSBurnRequest()` | ❌ Manual |
|
|
186
|
-
*
|
|
187
|
-
* **Use `redeem()` for voucher/reward redemptions.** It automatically:
|
|
188
|
-
* 1. Creates redemption record
|
|
189
|
-
* 2. Triggers passkey/biometric prompt
|
|
190
|
-
* 3. Signs the transaction
|
|
191
|
-
* 4. Submits to blockchain
|
|
192
|
-
* 5. Returns result with `transactionHash`
|
|
193
|
-
*
|
|
194
|
-
* **No admin approval required** - The "PENDING" state is brief (seconds).
|
|
195
|
-
* The user's passkey signature IS the approval.
|
|
196
|
-
*
|
|
197
|
-
* Supports dynamic context for personalized NFT generation when the redemption
|
|
198
|
-
* issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
|
|
199
|
-
* and custom metadata.
|
|
200
|
-
*
|
|
201
|
-
* @param redemptionId - ID of the redemption to redeem
|
|
202
|
-
* @param options - Optional settings
|
|
203
|
-
* @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
|
|
204
|
-
* @param options.onStatusUpdate - Callback for transaction signing status updates
|
|
205
|
-
* @returns Promise resolving to redemption result with `transactionHash` on success
|
|
206
|
-
*
|
|
207
|
-
* @example Basic redemption (most common)
|
|
208
|
-
* ```typescript
|
|
209
|
-
* const { redeem } = useRedemptions();
|
|
210
|
-
*
|
|
211
|
-
* const result = await redeem('redemption-123', {
|
|
212
|
-
* onStatusUpdate: (status, message) => {
|
|
213
|
-
* console.log(`Status: ${status} - ${message}`);
|
|
214
|
-
* }
|
|
215
|
-
* });
|
|
216
|
-
*
|
|
217
|
-
* if (result.transactionHash) {
|
|
218
|
-
* console.log('Redemption complete!', result.transactionHash);
|
|
219
|
-
* }
|
|
220
|
-
* ```
|
|
221
|
-
*
|
|
222
|
-
* @example Redemption with dynamic context (personalized NFT)
|
|
223
|
-
* ```typescript
|
|
224
|
-
* const result = await redeem('event-ticket-123', {
|
|
225
|
-
* context: {
|
|
226
|
-
* // Validity - token valid for event duration
|
|
227
|
-
* validityDate: new Date().toISOString(),
|
|
228
|
-
* validityDuration: 8, // 8 hours
|
|
229
|
-
*
|
|
230
|
-
* // AI prompt context - available as {{context.xxx}} in prompts
|
|
231
|
-
* attendeeName: 'John Doe',
|
|
232
|
-
* ticketTier: 'VIP',
|
|
233
|
-
* seatSection: 'A12',
|
|
234
|
-
* }
|
|
235
|
-
* });
|
|
236
|
-
* ```
|
|
237
|
-
*
|
|
238
|
-
* @example Hotel stay NFT with check-in/check-out dates
|
|
239
|
-
* ```typescript
|
|
240
|
-
* const result = await redeem('hotel-nft-456', {
|
|
241
|
-
* context: {
|
|
242
|
-
* validityDate: '2026-04-15T14:00:00Z', // Check-in
|
|
243
|
-
* validityEndDate: '2026-04-20T11:00:00Z', // Check-out
|
|
244
|
-
* guestName: 'Jane Smith',
|
|
245
|
-
* roomNumber: '305',
|
|
246
|
-
* roomType: 'Suite',
|
|
247
|
-
* }
|
|
248
|
-
* });
|
|
249
|
-
* ```
|
|
250
|
-
*
|
|
251
|
-
* @see {@link useTransactions.createTransaction | createTransaction()} for direct transfers without redemption flow
|
|
252
|
-
*/
|
|
253
|
-
const redeem = useCallback(async (
|
|
254
|
-
redemptionId: string,
|
|
255
|
-
options?: { context?: DynamicContext; onStatusUpdate?: OnStatusUpdateFn }
|
|
256
|
-
): Promise<RedemptionRedeemRequestResponseDTO> => {
|
|
257
|
-
if (!isInitialized || !sdk) {
|
|
258
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
259
|
-
}
|
|
260
|
-
if (!isAuthenticated) {
|
|
261
|
-
throw new Error('SDK not authenticated. redeem requires authentication.');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const result = await sdk.redemptions.redeem(redemptionId, options?.context);
|
|
266
|
-
|
|
267
|
-
const txToken = result.senderTransaction?.actionable?.authToken;
|
|
268
|
-
if (txToken && isSignerAvailable) {
|
|
269
|
-
try {
|
|
270
|
-
const signingResult: SubmissionResult = await signAndSubmitTransactionWithJWT(txToken, options?.onStatusUpdate);
|
|
271
|
-
if (signingResult.success) {
|
|
272
|
-
// Return enhanced result with signing information
|
|
273
|
-
return {
|
|
274
|
-
...result,
|
|
275
|
-
transactionHash: signingResult.transactionHash,
|
|
276
|
-
// signature: signingResult.signature,
|
|
277
|
-
isSigned: true,
|
|
278
|
-
signedAt: new Date().toISOString()
|
|
279
|
-
} as RedemptionRedeemRequestResponseDTO;
|
|
280
|
-
} else {
|
|
281
|
-
console.error('Transaction signing failed:', signingResult.error);
|
|
282
|
-
throw new Error(signingResult.error || 'Transaction signing failed');
|
|
283
|
-
}
|
|
284
|
-
} catch (signingError) {
|
|
285
|
-
console.error('Blockchain signing error:', signingError);
|
|
286
|
-
throw new Error(`Transaction signing failed: ${signingError}`);
|
|
287
|
-
}
|
|
288
|
-
} else if (txToken && !isSignerAvailable) {
|
|
289
|
-
console.warn('Transaction requires signature but signer is not available');
|
|
290
|
-
throw new Error('Transaction requires signature but blockchain signer is not initialized');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Return original result if no signing was required
|
|
294
|
-
return result;
|
|
295
|
-
} catch (error) {
|
|
296
|
-
console.error('Failed to redeem redemption:', error);
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
}, [sdk, isInitialized, isAuthenticated, signAndSubmitTransactionWithJWT, isSignerAvailable]);
|
|
300
|
-
|
|
301
|
-
// Admin methods
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Admin: Get all redemption redeems with filters and include relations
|
|
305
|
-
*
|
|
306
|
-
* Retrieves all redemption redeems across the platform with filtering.
|
|
307
|
-
*
|
|
308
|
-
* @param filters - Optional filters for user, redemption, date range, and pagination
|
|
309
|
-
* @param filters.userId - Filter by user ID
|
|
310
|
-
* @param filters.redemptionId - Filter by redemption offer ID
|
|
311
|
-
* @param filters.businessId - Filter by business ID (admin only)
|
|
312
|
-
* @param filters.status - Filter by processing status (PENDING, PROCESSING, COMPLETED, FAILED)
|
|
313
|
-
* @param filters.dateFrom - Filter redeems created on or after this date (ISO 8601 format)
|
|
314
|
-
* @param filters.dateTo - Filter redeems created on or before this date (ISO 8601 format)
|
|
315
|
-
* @param filters.page - Page number (1-indexed)
|
|
316
|
-
* @param filters.limit - Items per page
|
|
317
|
-
* @param filters.sortBy - Field to sort by
|
|
318
|
-
* @param filters.sortOrder - Sort direction
|
|
319
|
-
* @param include - Relations to include: 'redemption', 'user', 'business'
|
|
320
|
-
* @returns Promise resolving to paginated redemption redeems
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* ```typescript
|
|
324
|
-
* // Get all redeems with user details
|
|
325
|
-
* const { data: redeems } = await getRedemptionRedeems({}, ['user', 'redemption']);
|
|
326
|
-
*
|
|
327
|
-
* // Filter by specific user
|
|
328
|
-
* const { data: userRedeems } = await getRedemptionRedeems(
|
|
329
|
-
* { userId: 'user-123' },
|
|
330
|
-
* ['redemption']
|
|
331
|
-
* );
|
|
332
|
-
*
|
|
333
|
-
* // Filter by date range
|
|
334
|
-
* const { data } = await getRedemptionRedeems({
|
|
335
|
-
* dateFrom: '2026-01-01',
|
|
336
|
-
* dateTo: '2026-03-31'
|
|
337
|
-
* }, ['user', 'redemption']);
|
|
338
|
-
* ```
|
|
339
|
-
*/
|
|
340
|
-
const getRedemptionRedeems = useCallback(async (
|
|
341
|
-
filters?: RedemptionRedeemQueryParams,
|
|
342
|
-
include?: RedemptionRedeemIncludeRelation[]
|
|
343
|
-
): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
|
|
344
|
-
if (!isInitialized || !sdk) {
|
|
345
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const result = await sdk.redemptions.getRedemptionRedeems(filters, include);
|
|
350
|
-
return result;
|
|
351
|
-
} catch (error) {
|
|
352
|
-
console.error('Failed to fetch redemption redeems:', error);
|
|
353
|
-
throw error;
|
|
354
|
-
}
|
|
355
|
-
}, [sdk, isInitialized]);
|
|
356
|
-
|
|
357
|
-
const createRedemption = useCallback(async (redemptionData: RedemptionCreateRequestDTO): Promise<RedemptionDTO> => {
|
|
358
|
-
if (!isInitialized || !sdk) {
|
|
359
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
try {
|
|
363
|
-
const result = await sdk.redemptions.createRedemption(redemptionData);
|
|
364
|
-
return result;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
console.error('Failed to create redemption offer:', error);
|
|
367
|
-
throw error;
|
|
368
|
-
}
|
|
369
|
-
}, [sdk, isInitialized]);
|
|
370
|
-
|
|
371
|
-
const getRedemptionTypes = useCallback(async (): Promise<PaginatedResponseDTO<RedemptionTypeDTO>> => {
|
|
372
|
-
if (!isInitialized || !sdk) {
|
|
373
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const result = await sdk.redemptions.getRedemptionTypes();
|
|
378
|
-
return result;
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('Failed to fetch redemption types:', error);
|
|
381
|
-
throw error;
|
|
382
|
-
}
|
|
383
|
-
}, [sdk, isInitialized]);
|
|
384
|
-
|
|
385
|
-
const updateRedemption = useCallback(async (redemptionId: string, redemptionData: RedemptionCreateRequestDTO): Promise<RedemptionDTO> => {
|
|
386
|
-
if (!isInitialized || !sdk) {
|
|
387
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
const result = await sdk.redemptions.updateRedemption(redemptionId, redemptionData);
|
|
392
|
-
return result;
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.error('Failed to update redemption:', error);
|
|
395
|
-
throw error;
|
|
396
|
-
}
|
|
397
|
-
}, [sdk, isInitialized]);
|
|
398
|
-
|
|
399
|
-
const toggleRedemptionStatus = useCallback(async (redemptionId: string): Promise<RedemptionDTO> => {
|
|
400
|
-
if (!isInitialized || !sdk) {
|
|
401
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
try {
|
|
405
|
-
const result = await sdk.redemptions.toggleRedemptionStatus(redemptionId);
|
|
406
|
-
return result;
|
|
407
|
-
} catch (error) {
|
|
408
|
-
console.error('Failed to toggle redemption status:', error);
|
|
409
|
-
throw error;
|
|
410
|
-
}
|
|
411
|
-
}, [sdk, isInitialized]);
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Get a specific redemption by ID
|
|
415
|
-
*
|
|
416
|
-
* Retrieves detailed information about a specific redemption offer.
|
|
417
|
-
*
|
|
418
|
-
* @param id - Redemption UUID
|
|
419
|
-
* @param include - Optional relations to include: 'redeemCount'
|
|
420
|
-
* @returns Promise resolving to the redemption
|
|
421
|
-
*
|
|
422
|
-
* @example Basic usage
|
|
423
|
-
* ```typescript
|
|
424
|
-
* const redemption = await getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
|
|
425
|
-
* console.log(redemption.name);
|
|
426
|
-
* ```
|
|
427
|
-
*
|
|
428
|
-
* @example With redeem count
|
|
429
|
-
* ```typescript
|
|
430
|
-
* const redemption = await getRedemptionById('redemption-123', ['redeemCount']);
|
|
431
|
-
* console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
|
|
432
|
-
* ```
|
|
433
|
-
*/
|
|
434
|
-
const getRedemptionById = useCallback(async (
|
|
435
|
-
id: string,
|
|
436
|
-
include?: RedemptionIncludeRelation[]
|
|
437
|
-
): Promise<RedemptionDTO> => {
|
|
438
|
-
if (!isInitialized || !sdk) {
|
|
439
|
-
throw new Error('SDK not initialized. Call initialize() first.');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
const result = await sdk.redemptions.getRedemptionById(id, include);
|
|
444
|
-
return result;
|
|
445
|
-
} catch (error) {
|
|
446
|
-
console.error('Failed to fetch redemption by ID:', error);
|
|
447
|
-
throw error;
|
|
448
|
-
}
|
|
449
|
-
}, [sdk, isInitialized]);
|
|
450
|
-
|
|
451
|
-
return {
|
|
452
|
-
getRedemptions,
|
|
453
|
-
getRedemptionById,
|
|
454
|
-
getUserRedemptions,
|
|
455
|
-
redeem,
|
|
456
|
-
getRedemptionTypes,
|
|
457
|
-
getRedemptionRedeems,
|
|
458
|
-
createRedemption,
|
|
459
|
-
updateRedemption,
|
|
460
|
-
toggleRedemptionStatus,
|
|
461
|
-
isAvailable: isInitialized && !!sdk?.redemptions,
|
|
462
|
-
// Expose signing status for UI feedback
|
|
463
|
-
signingStatus: currentStatus,
|
|
464
|
-
signingStatusMessage: statusMessage,
|
|
465
|
-
};
|
|
466
|
-
};
|
|
467
|
-
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { usePersSDK } from '../providers/PersSDKProvider';
|
|
3
|
+
import { SubmissionResult, useTransactionSigner, OnStatusUpdateFn } from './useTransactionSigner';
|
|
4
|
+
import type {
|
|
5
|
+
RedemptionCreateRequestDTO,
|
|
6
|
+
RedemptionDTO,
|
|
7
|
+
RedemptionRedeemDTO,
|
|
8
|
+
RedemptionRedeemRequestResponseDTO,
|
|
9
|
+
RedemptionTypeDTO,
|
|
10
|
+
PaginatedResponseDTO,
|
|
11
|
+
RedemptionRedeemIncludeRelation,
|
|
12
|
+
RedemptionIncludeRelation,
|
|
13
|
+
ProcessRecordStatus,
|
|
14
|
+
DynamicContext,
|
|
15
|
+
SortOrder
|
|
16
|
+
} from '@explorins/pers-sdk';
|
|
17
|
+
import type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
|
|
18
|
+
import type { PaginationOptions } from '@explorins/pers-sdk';
|
|
19
|
+
|
|
20
|
+
// Re-export for consumers (new names + deprecated aliases)
|
|
21
|
+
export type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
|
|
22
|
+
/** @deprecated Use RedemptionRedeemQueryParams instead */
|
|
23
|
+
export type RedemptionRedeemFilters = RedemptionRedeemQueryParams;
|
|
24
|
+
/** @deprecated Use RedemptionQueryParams instead */
|
|
25
|
+
export type RedemptionFilterOptions = RedemptionQueryParams;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Options for fetching redemptions with filtering and pagination
|
|
29
|
+
*/
|
|
30
|
+
export interface GetRedemptionsOptions {
|
|
31
|
+
/** Filter by active status (Admin only - regular users always see active offers) */
|
|
32
|
+
active?: boolean;
|
|
33
|
+
/** Free text search across name, description, tags (case-insensitive) */
|
|
34
|
+
search?: string;
|
|
35
|
+
/** Filter by tag(s) - single tag or array for OR match */
|
|
36
|
+
tags?: string | string[];
|
|
37
|
+
/** Filter redemptions starting on or after this date */
|
|
38
|
+
startDate?: Date | string;
|
|
39
|
+
/** Filter redemptions ending on or before this date */
|
|
40
|
+
endDate?: Date | string;
|
|
41
|
+
/** Relations to include: 'redeemCount' */
|
|
42
|
+
include?: RedemptionIncludeRelation[];
|
|
43
|
+
/** Page number (1-indexed) */
|
|
44
|
+
page?: number;
|
|
45
|
+
/** Results per page */
|
|
46
|
+
limit?: number;
|
|
47
|
+
/** Field to sort by */
|
|
48
|
+
sortBy?: 'name' | 'createdAt' | 'startDate';
|
|
49
|
+
/** Sort direction */
|
|
50
|
+
sortOrder?: SortOrder;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const useRedemptions = () => {
|
|
54
|
+
const { sdk, isInitialized, isAuthenticated } = usePersSDK();
|
|
55
|
+
const { signAndSubmitTransactionWithJWT, isSignerAvailable, currentStatus, statusMessage } = useTransactionSigner();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get redemption offers based on user permissions
|
|
59
|
+
*
|
|
60
|
+
* **Regular Users:** Automatically see only active redemptions available for purchase
|
|
61
|
+
* **Administrators:** Can see all redemptions and filter by active/inactive status
|
|
62
|
+
*
|
|
63
|
+
* The backend enforces permission-based filtering automatically.
|
|
64
|
+
*
|
|
65
|
+
* @param options - Filter and pagination options
|
|
66
|
+
* @param options.active - Filter by active status (Admin only)
|
|
67
|
+
* @param options.search - Free text search across name, description, tags
|
|
68
|
+
* @param options.startDate - Filter redemptions starting on or after this date
|
|
69
|
+
* @param options.endDate - Filter redemptions ending on or before this date
|
|
70
|
+
* @param options.include - Relations to include: 'redeemCount'
|
|
71
|
+
* @param options.page - Page number (1-indexed)
|
|
72
|
+
* @param options.limit - Results per page
|
|
73
|
+
* @param options.sortBy - Field to sort by ('name' | 'createdAt')
|
|
74
|
+
* @param options.sortOrder - Sort direction ('ASC' | 'DESC')
|
|
75
|
+
*
|
|
76
|
+
* @example Basic usage
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const { data: offers } = await getRedemptions();
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example Filter by date range
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const { data: dateFiltered } = await getRedemptions({
|
|
84
|
+
* startDate: '2026-05-01',
|
|
85
|
+
* endDate: '2026-06-30'
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @example Get redemptions with redeem count
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const { data: offers } = await getRedemptions({ include: ['redeemCount'] });
|
|
92
|
+
* offers.forEach(o => console.log(`${o.name}: ${o.redeemCount} redeems`));
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @example Search with pagination
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const { data, pagination } = await getRedemptions({
|
|
98
|
+
* search: 'discount',
|
|
99
|
+
* page: 1,
|
|
100
|
+
* limit: 10,
|
|
101
|
+
* sortBy: 'name',
|
|
102
|
+
* sortOrder: 'ASC'
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
const getRedemptions = useCallback(async (options?: GetRedemptionsOptions): Promise<PaginatedResponseDTO<RedemptionDTO>> => {
|
|
107
|
+
if (!isInitialized || !sdk) {
|
|
108
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Convert Date to ISO string for API compatibility
|
|
113
|
+
const apiOptions = options ? {
|
|
114
|
+
...options,
|
|
115
|
+
startDate: options.startDate instanceof Date ? options.startDate.toISOString() : options.startDate,
|
|
116
|
+
endDate: options.endDate instanceof Date ? options.endDate.toISOString() : options.endDate,
|
|
117
|
+
} : undefined;
|
|
118
|
+
|
|
119
|
+
const result = await sdk.redemptions.getRedemptions(apiOptions);
|
|
120
|
+
return result;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to fetch redemptions:', error);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}, [sdk, isInitialized]);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get user's redemption history with optional filters, pagination and include relations
|
|
129
|
+
*
|
|
130
|
+
* @param filters - Optional filters (redemptionId, status)
|
|
131
|
+
* @param options - Pagination options (page, limit, sortBy, sortOrder)
|
|
132
|
+
* @param include - Relations to include: 'redemption', 'user', 'business', 'transactions'
|
|
133
|
+
* @returns Promise resolving to paginated redemption history
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* // Get first page of user redemptions
|
|
138
|
+
* const { data: redeems, pagination } = await getUserRedemptions({}, { page: 1, limit: 10 });
|
|
139
|
+
* console.log(`Page ${pagination.page} of ${pagination.pages}`);
|
|
140
|
+
*
|
|
141
|
+
* // Get only completed redemptions
|
|
142
|
+
* import { ProcessRecordStatus } from '@explorins/pers-shared';
|
|
143
|
+
* const { data: completed } = await getUserRedemptions({ status: ProcessRecordStatus.COMPLETED });
|
|
144
|
+
*
|
|
145
|
+
* // Get user redemptions with full redemption details
|
|
146
|
+
* const { data: redeems } = await getUserRedemptions({}, {}, ['redemption', 'business']);
|
|
147
|
+
* redeems.forEach(redeem => {
|
|
148
|
+
* console.log('Redemption:', redeem.included?.redemption?.name);
|
|
149
|
+
* console.log('Business:', redeem.included?.business?.displayName);
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
const getUserRedemptions = useCallback(async (
|
|
154
|
+
filters?: { redemptionId?: string; status?: ProcessRecordStatus },
|
|
155
|
+
options?: PaginationOptions,
|
|
156
|
+
include?: RedemptionRedeemIncludeRelation[]
|
|
157
|
+
): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
|
|
158
|
+
if (!isInitialized || !sdk) {
|
|
159
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
160
|
+
}
|
|
161
|
+
if (!isAuthenticated) {
|
|
162
|
+
console.warn('SDK not authenticated. getUserRedemptions requires authentication.');
|
|
163
|
+
return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result = await sdk.redemptions.getUserRedemptions(filters, options, include);
|
|
168
|
+
return result;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to fetch user redemptions:', error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}, [sdk, isInitialized, isAuthenticated]);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Redeem a redemption offer with optional dynamic context
|
|
177
|
+
*
|
|
178
|
+
* ## When to use `redeem()` vs `createTransaction()`
|
|
179
|
+
*
|
|
180
|
+
* | Use Case | Method | Auto-Signs |
|
|
181
|
+
* |----------|--------|------------|
|
|
182
|
+
* | **Voucher redemption** | `redeem()` | ✅ Yes |
|
|
183
|
+
* | **Reward redemption** | `redeem()` | ✅ Yes |
|
|
184
|
+
* | **Direct token transfer** | `createTransaction()` | ⚠️ Depends |
|
|
185
|
+
* | **POS burn with QR** | `buildPOSBurnRequest()` | ❌ Manual |
|
|
186
|
+
*
|
|
187
|
+
* **Use `redeem()` for voucher/reward redemptions.** It automatically:
|
|
188
|
+
* 1. Creates redemption record
|
|
189
|
+
* 2. Triggers passkey/biometric prompt
|
|
190
|
+
* 3. Signs the transaction
|
|
191
|
+
* 4. Submits to blockchain
|
|
192
|
+
* 5. Returns result with `transactionHash`
|
|
193
|
+
*
|
|
194
|
+
* **No admin approval required** - The "PENDING" state is brief (seconds).
|
|
195
|
+
* The user's passkey signature IS the approval.
|
|
196
|
+
*
|
|
197
|
+
* Supports dynamic context for personalized NFT generation when the redemption
|
|
198
|
+
* issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
|
|
199
|
+
* and custom metadata.
|
|
200
|
+
*
|
|
201
|
+
* @param redemptionId - ID of the redemption to redeem
|
|
202
|
+
* @param options - Optional settings
|
|
203
|
+
* @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
|
|
204
|
+
* @param options.onStatusUpdate - Callback for transaction signing status updates
|
|
205
|
+
* @returns Promise resolving to redemption result with `transactionHash` on success
|
|
206
|
+
*
|
|
207
|
+
* @example Basic redemption (most common)
|
|
208
|
+
* ```typescript
|
|
209
|
+
* const { redeem } = useRedemptions();
|
|
210
|
+
*
|
|
211
|
+
* const result = await redeem('redemption-123', {
|
|
212
|
+
* onStatusUpdate: (status, message) => {
|
|
213
|
+
* console.log(`Status: ${status} - ${message}`);
|
|
214
|
+
* }
|
|
215
|
+
* });
|
|
216
|
+
*
|
|
217
|
+
* if (result.transactionHash) {
|
|
218
|
+
* console.log('Redemption complete!', result.transactionHash);
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @example Redemption with dynamic context (personalized NFT)
|
|
223
|
+
* ```typescript
|
|
224
|
+
* const result = await redeem('event-ticket-123', {
|
|
225
|
+
* context: {
|
|
226
|
+
* // Validity - token valid for event duration
|
|
227
|
+
* validityDate: new Date().toISOString(),
|
|
228
|
+
* validityDuration: 8, // 8 hours
|
|
229
|
+
*
|
|
230
|
+
* // AI prompt context - available as {{context.xxx}} in prompts
|
|
231
|
+
* attendeeName: 'John Doe',
|
|
232
|
+
* ticketTier: 'VIP',
|
|
233
|
+
* seatSection: 'A12',
|
|
234
|
+
* }
|
|
235
|
+
* });
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* @example Hotel stay NFT with check-in/check-out dates
|
|
239
|
+
* ```typescript
|
|
240
|
+
* const result = await redeem('hotel-nft-456', {
|
|
241
|
+
* context: {
|
|
242
|
+
* validityDate: '2026-04-15T14:00:00Z', // Check-in
|
|
243
|
+
* validityEndDate: '2026-04-20T11:00:00Z', // Check-out
|
|
244
|
+
* guestName: 'Jane Smith',
|
|
245
|
+
* roomNumber: '305',
|
|
246
|
+
* roomType: 'Suite',
|
|
247
|
+
* }
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @see {@link useTransactions.createTransaction | createTransaction()} for direct transfers without redemption flow
|
|
252
|
+
*/
|
|
253
|
+
const redeem = useCallback(async (
|
|
254
|
+
redemptionId: string,
|
|
255
|
+
options?: { context?: DynamicContext; onStatusUpdate?: OnStatusUpdateFn }
|
|
256
|
+
): Promise<RedemptionRedeemRequestResponseDTO> => {
|
|
257
|
+
if (!isInitialized || !sdk) {
|
|
258
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
259
|
+
}
|
|
260
|
+
if (!isAuthenticated) {
|
|
261
|
+
throw new Error('SDK not authenticated. redeem requires authentication.');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await sdk.redemptions.redeem(redemptionId, options?.context);
|
|
266
|
+
|
|
267
|
+
const txToken = result.senderTransaction?.actionable?.authToken;
|
|
268
|
+
if (txToken && isSignerAvailable) {
|
|
269
|
+
try {
|
|
270
|
+
const signingResult: SubmissionResult = await signAndSubmitTransactionWithJWT(txToken, options?.onStatusUpdate);
|
|
271
|
+
if (signingResult.success) {
|
|
272
|
+
// Return enhanced result with signing information
|
|
273
|
+
return {
|
|
274
|
+
...result,
|
|
275
|
+
transactionHash: signingResult.transactionHash,
|
|
276
|
+
// signature: signingResult.signature,
|
|
277
|
+
isSigned: true,
|
|
278
|
+
signedAt: new Date().toISOString()
|
|
279
|
+
} as RedemptionRedeemRequestResponseDTO;
|
|
280
|
+
} else {
|
|
281
|
+
console.error('Transaction signing failed:', signingResult.error);
|
|
282
|
+
throw new Error(signingResult.error || 'Transaction signing failed');
|
|
283
|
+
}
|
|
284
|
+
} catch (signingError) {
|
|
285
|
+
console.error('Blockchain signing error:', signingError);
|
|
286
|
+
throw new Error(`Transaction signing failed: ${signingError}`);
|
|
287
|
+
}
|
|
288
|
+
} else if (txToken && !isSignerAvailable) {
|
|
289
|
+
console.warn('Transaction requires signature but signer is not available');
|
|
290
|
+
throw new Error('Transaction requires signature but blockchain signer is not initialized');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Return original result if no signing was required
|
|
294
|
+
return result;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('Failed to redeem redemption:', error);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}, [sdk, isInitialized, isAuthenticated, signAndSubmitTransactionWithJWT, isSignerAvailable]);
|
|
300
|
+
|
|
301
|
+
// Admin methods
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Admin: Get all redemption redeems with filters and include relations
|
|
305
|
+
*
|
|
306
|
+
* Retrieves all redemption redeems across the platform with filtering.
|
|
307
|
+
*
|
|
308
|
+
* @param filters - Optional filters for user, redemption, date range, and pagination
|
|
309
|
+
* @param filters.userId - Filter by user ID
|
|
310
|
+
* @param filters.redemptionId - Filter by redemption offer ID
|
|
311
|
+
* @param filters.businessId - Filter by business ID (admin only)
|
|
312
|
+
* @param filters.status - Filter by processing status (PENDING, PROCESSING, COMPLETED, FAILED)
|
|
313
|
+
* @param filters.dateFrom - Filter redeems created on or after this date (ISO 8601 format)
|
|
314
|
+
* @param filters.dateTo - Filter redeems created on or before this date (ISO 8601 format)
|
|
315
|
+
* @param filters.page - Page number (1-indexed)
|
|
316
|
+
* @param filters.limit - Items per page
|
|
317
|
+
* @param filters.sortBy - Field to sort by
|
|
318
|
+
* @param filters.sortOrder - Sort direction
|
|
319
|
+
* @param include - Relations to include: 'redemption', 'user', 'business'
|
|
320
|
+
* @returns Promise resolving to paginated redemption redeems
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* // Get all redeems with user details
|
|
325
|
+
* const { data: redeems } = await getRedemptionRedeems({}, ['user', 'redemption']);
|
|
326
|
+
*
|
|
327
|
+
* // Filter by specific user
|
|
328
|
+
* const { data: userRedeems } = await getRedemptionRedeems(
|
|
329
|
+
* { userId: 'user-123' },
|
|
330
|
+
* ['redemption']
|
|
331
|
+
* );
|
|
332
|
+
*
|
|
333
|
+
* // Filter by date range
|
|
334
|
+
* const { data } = await getRedemptionRedeems({
|
|
335
|
+
* dateFrom: '2026-01-01',
|
|
336
|
+
* dateTo: '2026-03-31'
|
|
337
|
+
* }, ['user', 'redemption']);
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
const getRedemptionRedeems = useCallback(async (
|
|
341
|
+
filters?: RedemptionRedeemQueryParams,
|
|
342
|
+
include?: RedemptionRedeemIncludeRelation[]
|
|
343
|
+
): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
|
|
344
|
+
if (!isInitialized || !sdk) {
|
|
345
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const result = await sdk.redemptions.getRedemptionRedeems(filters, include);
|
|
350
|
+
return result;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error('Failed to fetch redemption redeems:', error);
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}, [sdk, isInitialized]);
|
|
356
|
+
|
|
357
|
+
const createRedemption = useCallback(async (redemptionData: RedemptionCreateRequestDTO): Promise<RedemptionDTO> => {
|
|
358
|
+
if (!isInitialized || !sdk) {
|
|
359
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const result = await sdk.redemptions.createRedemption(redemptionData);
|
|
364
|
+
return result;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error('Failed to create redemption offer:', error);
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}, [sdk, isInitialized]);
|
|
370
|
+
|
|
371
|
+
const getRedemptionTypes = useCallback(async (): Promise<PaginatedResponseDTO<RedemptionTypeDTO>> => {
|
|
372
|
+
if (!isInitialized || !sdk) {
|
|
373
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const result = await sdk.redemptions.getRedemptionTypes();
|
|
378
|
+
return result;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Failed to fetch redemption types:', error);
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}, [sdk, isInitialized]);
|
|
384
|
+
|
|
385
|
+
const updateRedemption = useCallback(async (redemptionId: string, redemptionData: RedemptionCreateRequestDTO): Promise<RedemptionDTO> => {
|
|
386
|
+
if (!isInitialized || !sdk) {
|
|
387
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const result = await sdk.redemptions.updateRedemption(redemptionId, redemptionData);
|
|
392
|
+
return result;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error('Failed to update redemption:', error);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}, [sdk, isInitialized]);
|
|
398
|
+
|
|
399
|
+
const toggleRedemptionStatus = useCallback(async (redemptionId: string): Promise<RedemptionDTO> => {
|
|
400
|
+
if (!isInitialized || !sdk) {
|
|
401
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const result = await sdk.redemptions.toggleRedemptionStatus(redemptionId);
|
|
406
|
+
return result;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.error('Failed to toggle redemption status:', error);
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}, [sdk, isInitialized]);
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get a specific redemption by ID
|
|
415
|
+
*
|
|
416
|
+
* Retrieves detailed information about a specific redemption offer.
|
|
417
|
+
*
|
|
418
|
+
* @param id - Redemption UUID
|
|
419
|
+
* @param include - Optional relations to include: 'redeemCount'
|
|
420
|
+
* @returns Promise resolving to the redemption
|
|
421
|
+
*
|
|
422
|
+
* @example Basic usage
|
|
423
|
+
* ```typescript
|
|
424
|
+
* const redemption = await getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
|
|
425
|
+
* console.log(redemption.name);
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* @example With redeem count
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const redemption = await getRedemptionById('redemption-123', ['redeemCount']);
|
|
431
|
+
* console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
const getRedemptionById = useCallback(async (
|
|
435
|
+
id: string,
|
|
436
|
+
include?: RedemptionIncludeRelation[]
|
|
437
|
+
): Promise<RedemptionDTO> => {
|
|
438
|
+
if (!isInitialized || !sdk) {
|
|
439
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const result = await sdk.redemptions.getRedemptionById(id, include);
|
|
444
|
+
return result;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.error('Failed to fetch redemption by ID:', error);
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}, [sdk, isInitialized]);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
getRedemptions,
|
|
453
|
+
getRedemptionById,
|
|
454
|
+
getUserRedemptions,
|
|
455
|
+
redeem,
|
|
456
|
+
getRedemptionTypes,
|
|
457
|
+
getRedemptionRedeems,
|
|
458
|
+
createRedemption,
|
|
459
|
+
updateRedemption,
|
|
460
|
+
toggleRedemptionStatus,
|
|
461
|
+
isAvailable: isInitialized && !!sdk?.redemptions,
|
|
462
|
+
// Expose signing status for UI feedback
|
|
463
|
+
signingStatus: currentStatus,
|
|
464
|
+
signingStatusMessage: statusMessage,
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
|
|
468
468
|
export type RedemptionHook = ReturnType<typeof useRedemptions>;
|