@umituz/react-native-subscription 2.14.97 → 2.14.98
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 +462 -0
- package/package.json +1 -3
- package/src/application/README.md +229 -0
- package/src/application/ports/README.md +103 -0
- package/src/domain/README.md +402 -0
- package/src/domain/constants/README.md +80 -0
- package/src/domain/entities/README.md +176 -0
- package/src/domain/errors/README.md +307 -0
- package/src/domain/value-objects/README.md +186 -0
- package/src/domains/config/README.md +390 -0
- package/src/domains/paywall/README.md +371 -0
- package/src/domains/paywall/components/PaywallHeader.tsx +8 -11
- package/src/domains/paywall/components/README.md +185 -0
- package/src/domains/paywall/entities/README.md +199 -0
- package/src/domains/paywall/hooks/README.md +129 -0
- package/src/domains/wallet/README.md +292 -0
- package/src/domains/wallet/domain/README.md +108 -0
- package/src/domains/wallet/domain/entities/README.md +122 -0
- package/src/domains/wallet/domain/errors/README.md +157 -0
- package/src/domains/wallet/infrastructure/README.md +96 -0
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +6 -12
- package/src/domains/wallet/presentation/components/README.md +231 -0
- package/src/domains/wallet/presentation/hooks/README.md +255 -0
- package/src/infrastructure/README.md +514 -0
- package/src/infrastructure/mappers/README.md +34 -0
- package/src/infrastructure/models/README.md +26 -0
- package/src/infrastructure/repositories/README.md +385 -0
- package/src/infrastructure/services/README.md +374 -0
- package/src/presentation/README.md +410 -0
- package/src/presentation/components/README.md +183 -0
- package/src/presentation/components/details/CreditRow.md +337 -0
- package/src/presentation/components/details/DetailRow.md +283 -0
- package/src/presentation/components/details/PremiumDetailsCard.md +266 -0
- package/src/presentation/components/details/PremiumStatusBadge.md +266 -0
- package/src/presentation/components/details/README.md +449 -0
- package/src/presentation/components/feedback/PaywallFeedbackModal.md +314 -0
- package/src/presentation/components/feedback/README.md +447 -0
- package/src/presentation/components/paywall/PaywallModal.md +444 -0
- package/src/presentation/components/paywall/README.md +190 -0
- package/src/presentation/components/sections/README.md +468 -0
- package/src/presentation/components/sections/SubscriptionSection.md +246 -0
- package/src/presentation/hooks/README.md +743 -0
- package/src/presentation/hooks/useAuthAwarePurchase.md +359 -0
- package/src/presentation/hooks/useAuthGate.md +403 -0
- package/src/presentation/hooks/useAuthSubscriptionSync.md +398 -0
- package/src/presentation/hooks/useCreditChecker.md +407 -0
- package/src/presentation/hooks/useCredits.md +342 -0
- package/src/presentation/hooks/useCreditsGate.md +346 -0
- package/src/presentation/hooks/useDeductCredit.md +160 -0
- package/src/presentation/hooks/useDevTestCallbacks.md +422 -0
- package/src/presentation/hooks/useFeatureGate.md +157 -0
- package/src/presentation/hooks/useInitializeCredits.md +458 -0
- package/src/presentation/hooks/usePaywall.md +334 -0
- package/src/presentation/hooks/usePaywallOperations.md +486 -0
- package/src/presentation/hooks/usePaywallVisibility.md +344 -0
- package/src/presentation/hooks/usePremium.md +230 -0
- package/src/presentation/hooks/usePremiumGate.md +423 -0
- package/src/presentation/hooks/usePremiumWithCredits.md +429 -0
- package/src/presentation/hooks/useSubscription.md +450 -0
- package/src/presentation/hooks/useSubscriptionDetails.md +438 -0
- package/src/presentation/hooks/useSubscriptionGate.md +168 -0
- package/src/presentation/hooks/useSubscriptionSettingsConfig.md +374 -0
- package/src/presentation/hooks/useSubscriptionStatus.md +424 -0
- package/src/presentation/hooks/useUserTier.md +356 -0
- package/src/presentation/hooks/useUserTierWithRepository.md +452 -0
- package/src/presentation/screens/README.md +194 -0
- package/src/presentation/types/README.md +38 -0
- package/src/presentation/utils/README.md +52 -0
- package/src/revenuecat/README.md +523 -0
- package/src/revenuecat/domain/README.md +147 -0
- package/src/revenuecat/domain/errors/README.md +197 -0
- package/src/revenuecat/infrastructure/config/README.md +40 -0
- package/src/revenuecat/infrastructure/managers/README.md +49 -0
- package/src/revenuecat/presentation/hooks/README.md +56 -0
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# useCredits Hook
|
|
2
|
+
|
|
3
|
+
Hook for accessing and managing credits balance.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useCredits } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useCredits(): {
|
|
15
|
+
credits: number;
|
|
16
|
+
balance: number;
|
|
17
|
+
transactions: Transaction[];
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
refetch: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Returns
|
|
25
|
+
|
|
26
|
+
| Property | Type | Description |
|
|
27
|
+
|----------|------|-------------|
|
|
28
|
+
| `credits` | `number` | Current credit balance |
|
|
29
|
+
| `balance` | `number` | Balance in currency (USD, etc.) |
|
|
30
|
+
| `transactions` | `Transaction[]` | Transaction history |
|
|
31
|
+
| `isLoading` | `boolean` | Loading state |
|
|
32
|
+
| `error` | `Error \| null` | Error if any |
|
|
33
|
+
| `refetch` | `() => Promise<void>` | Manually refetch data |
|
|
34
|
+
|
|
35
|
+
## Basic Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
function CreditsDisplay() {
|
|
39
|
+
const { credits, isLoading } = useCredits();
|
|
40
|
+
|
|
41
|
+
if (isLoading) return <ActivityIndicator />;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<View>
|
|
45
|
+
<Text>Your Credits: {credits}</Text>
|
|
46
|
+
<Text>Balance: ${balance.toFixed(2)}</Text>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Advanced Usage
|
|
53
|
+
|
|
54
|
+
### With Refresh Control
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
function CreditsWithRefresh() {
|
|
58
|
+
const { credits, refetch, isLoading } = useCredits();
|
|
59
|
+
|
|
60
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
61
|
+
|
|
62
|
+
const handleRefresh = async () => {
|
|
63
|
+
setRefreshing(true);
|
|
64
|
+
await refetch();
|
|
65
|
+
setRefreshing(false);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<View>
|
|
70
|
+
<Text>Credits: {credits}</Text>
|
|
71
|
+
|
|
72
|
+
<Button
|
|
73
|
+
onPress={handleRefresh}
|
|
74
|
+
disabled={refreshing || isLoading}
|
|
75
|
+
title={refreshing ? 'Refreshing...' : 'Refresh'}
|
|
76
|
+
/>
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### With Auto-Refresh
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
function AutoRefreshCredits() {
|
|
86
|
+
const { credits, refetch } = useCredits();
|
|
87
|
+
|
|
88
|
+
// Refresh every 30 seconds
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const interval = setInterval(() => {
|
|
91
|
+
refetch();
|
|
92
|
+
}, 30000);
|
|
93
|
+
|
|
94
|
+
return () => clearInterval(interval);
|
|
95
|
+
}, [refetch]);
|
|
96
|
+
|
|
97
|
+
// Refresh when app comes to foreground
|
|
98
|
+
useFocusEffect(
|
|
99
|
+
useCallback(() => {
|
|
100
|
+
refetch();
|
|
101
|
+
}, [refetch])
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return <Text>Credits: {credits}</Text>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### With Credit History
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
function CreditsWithHistory() {
|
|
112
|
+
const { credits, transactions, isLoading } = useCredits();
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<View>
|
|
116
|
+
<BalanceDisplay credits={credits} />
|
|
117
|
+
|
|
118
|
+
<Text style={styles.historyTitle}>Recent Transactions</Text>
|
|
119
|
+
|
|
120
|
+
{isLoading ? (
|
|
121
|
+
<ActivityIndicator />
|
|
122
|
+
) : (
|
|
123
|
+
<FlatList
|
|
124
|
+
data={transactions}
|
|
125
|
+
keyExtractor={(item) => item.id}
|
|
126
|
+
renderItem={({ item }) => (
|
|
127
|
+
<TransactionItem
|
|
128
|
+
amount={item.amount}
|
|
129
|
+
reason={item.reason}
|
|
130
|
+
timestamp={item.timestamp}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### With Balance Conversion
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function CreditsWithConversion() {
|
|
144
|
+
const { credits, balance } = useCredits();
|
|
145
|
+
|
|
146
|
+
const creditValue = 0.01; // 1 credit = $0.01
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<View>
|
|
150
|
+
<Text style={styles.credits}>{credits} Credits</Text>
|
|
151
|
+
<Text style={styles.balance}>
|
|
152
|
+
Worth approximately ${(balance || 0).toFixed(2)}
|
|
153
|
+
</Text>
|
|
154
|
+
<Text style={styles.info}>
|
|
155
|
+
(1 credit = ${(creditValue).toFixed(2)})
|
|
156
|
+
</Text>
|
|
157
|
+
</View>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Examples
|
|
163
|
+
|
|
164
|
+
### Credits Balance Card
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
function CreditsBalanceCard() {
|
|
168
|
+
const { credits, balance, isLoading } = useCredits();
|
|
169
|
+
|
|
170
|
+
if (isLoading) {
|
|
171
|
+
return <SkeletonLoader />;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Card style={styles.card}>
|
|
176
|
+
<Card.Header>
|
|
177
|
+
<Card.Title>Your Balance</Card.Title>
|
|
178
|
+
</Card.Header>
|
|
179
|
+
|
|
180
|
+
<Card.Body>
|
|
181
|
+
<View style={styles.balanceContainer}>
|
|
182
|
+
<Text style={styles.credits}>{credits}</Text>
|
|
183
|
+
<Text style={styles.label}>Credits</Text>
|
|
184
|
+
</View>
|
|
185
|
+
|
|
186
|
+
<View style={styles.balanceContainer}>
|
|
187
|
+
<Text style={styles.balance}>
|
|
188
|
+
${(balance || 0).toFixed(2)}
|
|
189
|
+
</Text>
|
|
190
|
+
<Text style={styles.label}>Value</Text>
|
|
191
|
+
</View>
|
|
192
|
+
</Card.Body>
|
|
193
|
+
|
|
194
|
+
<Card.Footer>
|
|
195
|
+
<Button
|
|
196
|
+
onPress={() => navigation.navigate('CreditPackages')}
|
|
197
|
+
title="Get More Credits"
|
|
198
|
+
size="sm"
|
|
199
|
+
/>
|
|
200
|
+
</Card.Footer>
|
|
201
|
+
</Card>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Credits Indicator
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
function CreditsIndicator() {
|
|
210
|
+
const { credits } = useCredits();
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<TouchableOpacity
|
|
214
|
+
onPress={() => navigation.navigate('Wallet')}
|
|
215
|
+
style={styles.indicator}
|
|
216
|
+
>
|
|
217
|
+
<Icon name="coins" size={20} color="#FFD700" />
|
|
218
|
+
<Text style={styles.credits}>{credits}</Text>
|
|
219
|
+
</TouchableOpacity>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Low Credits Warning
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
function LowCreditsWarning() {
|
|
228
|
+
const { credits } = useCredits();
|
|
229
|
+
const lowCreditThreshold = 10;
|
|
230
|
+
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (credits <= lowCreditThreshold && credits > 0) {
|
|
233
|
+
notifications.show({
|
|
234
|
+
title: 'Low Credits',
|
|
235
|
+
body: `You have ${credits} credits remaining. Purchase more to continue using features.`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}, [credits]);
|
|
239
|
+
|
|
240
|
+
if (credits <= lowCreditThreshold) {
|
|
241
|
+
return (
|
|
242
|
+
<Alert
|
|
243
|
+
severity={credits === 0 ? 'error' : 'warning'}
|
|
244
|
+
message={
|
|
245
|
+
credits === 0
|
|
246
|
+
? 'No credits remaining. Purchase more to continue.'
|
|
247
|
+
: `Low credits: ${credits} remaining`
|
|
248
|
+
}
|
|
249
|
+
action={
|
|
250
|
+
<Button onPress={() => navigation.navigate('CreditPackages')}>
|
|
251
|
+
Get More
|
|
252
|
+
</Button>
|
|
253
|
+
}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Credits Usage Chart
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
function CreditsUsageChart() {
|
|
266
|
+
const { transactions } = useCredits();
|
|
267
|
+
|
|
268
|
+
// Group transactions by date
|
|
269
|
+
const usageByDate = useMemo(() => {
|
|
270
|
+
const grouped = transactions.reduce((acc, tx) => {
|
|
271
|
+
const date = new Date(tx.timestamp).toDateString();
|
|
272
|
+
acc[date] = (acc[date] || 0) + Math.abs(tx.amount);
|
|
273
|
+
return acc;
|
|
274
|
+
}, {});
|
|
275
|
+
|
|
276
|
+
return Object.entries(grouped)
|
|
277
|
+
.map(([date, amount]) => ({ date, amount }))
|
|
278
|
+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
|
|
279
|
+
.slice(-7); // Last 7 days
|
|
280
|
+
}, [transactions]);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<View>
|
|
284
|
+
<Text>Credits Usage (Last 7 Days)</Text>
|
|
285
|
+
|
|
286
|
+
<LineChart
|
|
287
|
+
data={usageByDate}
|
|
288
|
+
xKey="date"
|
|
289
|
+
yKey="amount"
|
|
290
|
+
height={200}
|
|
291
|
+
/>
|
|
292
|
+
</View>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Real-Time Balance Updates
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
function RealtimeCreditsBalance() {
|
|
301
|
+
const { credits, refetch } = useCredits();
|
|
302
|
+
|
|
303
|
+
// Listen to credit updates
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
const unsubscribe = creditsListener.on((newBalance) => {
|
|
306
|
+
console.log('Credits updated:', newBalance);
|
|
307
|
+
refetch();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return () => unsubscribe?.();
|
|
311
|
+
}, []);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<View>
|
|
315
|
+
<Text>Credits: {credits}</Text>
|
|
316
|
+
</View>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Best Practices
|
|
322
|
+
|
|
323
|
+
1. **Handle loading** - Show skeleton or spinner
|
|
324
|
+
2. **Display balance** - Show both credits and value
|
|
325
|
+
3. **Provide purchase option** - Link to credit packages
|
|
326
|
+
4. **Show warnings** - Alert when credits are low
|
|
327
|
+
5. **Track usage** - Display transaction history
|
|
328
|
+
6. **Auto-refresh** - Keep balance up to date
|
|
329
|
+
7. **Handle errors** - Show user-friendly messages
|
|
330
|
+
|
|
331
|
+
## Related Hooks
|
|
332
|
+
|
|
333
|
+
- **useCreditsGate** - For gating features with credits
|
|
334
|
+
- **useDeductCredit** - For deducting credits
|
|
335
|
+
- **useTransactionHistory** - For transaction list
|
|
336
|
+
- **useWallet** - Wallet domain hook
|
|
337
|
+
|
|
338
|
+
## See Also
|
|
339
|
+
|
|
340
|
+
- [CreditsGate](./useCreditsGate.md)
|
|
341
|
+
- [DeductCredit](./useDeductCredit.md)
|
|
342
|
+
- [Wallet Domain](../../../domains/wallet/README.md)
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# useCreditsGate Hook
|
|
2
|
+
|
|
3
|
+
Hook for gating features behind credit requirements.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useCreditsGate } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useCreditsGate(config: {
|
|
15
|
+
creditCost: number;
|
|
16
|
+
featureId: string;
|
|
17
|
+
}): {
|
|
18
|
+
hasCredits: boolean;
|
|
19
|
+
credits: number;
|
|
20
|
+
consumeCredit: () => Promise<CreditsResult>;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
showPurchasePrompt: () => void;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Parameters
|
|
27
|
+
|
|
28
|
+
| Parameter | Type | Required | Description |
|
|
29
|
+
|-----------|------|----------|-------------|
|
|
30
|
+
| `config.creditCost` | `number` | Yes | Number of credits required |
|
|
31
|
+
| `config.featureId` | `string` | Yes | Unique identifier for the feature |
|
|
32
|
+
|
|
33
|
+
## Returns
|
|
34
|
+
|
|
35
|
+
| Property | Type | Description |
|
|
36
|
+
|----------|------|-------------|
|
|
37
|
+
| `hasCredits` | `boolean` | Whether user has enough credits |
|
|
38
|
+
| `credits` | `number` | Current credit balance |
|
|
39
|
+
| `consumeCredit` | `() => Promise<CreditsResult>` | Function to consume credits |
|
|
40
|
+
| `isLoading` | `boolean` | Whether hook is loading |
|
|
41
|
+
| `showPurchasePrompt` | `() => void` | Show purchase prompt UI |
|
|
42
|
+
|
|
43
|
+
## Basic Usage
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
function CreditFeature() {
|
|
47
|
+
const { hasCredits, credits, consumeCredit } = useCreditsGate({
|
|
48
|
+
creditCost: 5,
|
|
49
|
+
featureId: 'export_data',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const handleExport = async () => {
|
|
53
|
+
if (!hasCredits) {
|
|
54
|
+
Alert.alert('Insufficient Credits', `You need 5 credits, have ${credits}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = await consumeCredit();
|
|
59
|
+
|
|
60
|
+
if (result.success) {
|
|
61
|
+
await exportData();
|
|
62
|
+
Alert.alert('Success', `Data exported! ${result.newBalance} credits remaining`);
|
|
63
|
+
} else {
|
|
64
|
+
Alert.alert('Error', result.error?.message || 'Failed to consume credits');
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Button
|
|
70
|
+
onPress={handleExport}
|
|
71
|
+
title={`Export Data (5 credits)`}
|
|
72
|
+
disabled={!hasCredits}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Advanced Usage
|
|
79
|
+
|
|
80
|
+
### With Purchase Prompt
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
function AdvancedCreditFeature() {
|
|
84
|
+
const { hasCredits, credits, consumeCredit, showPurchasePrompt } =
|
|
85
|
+
useCreditsGate({
|
|
86
|
+
creditCost: 10,
|
|
87
|
+
featureId: 'ai_analysis',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const handleAnalyze = async () => {
|
|
91
|
+
if (!hasCredits) {
|
|
92
|
+
showPurchasePrompt();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = await consumeCredit();
|
|
97
|
+
|
|
98
|
+
if (result.success) {
|
|
99
|
+
await performAIAnalysis();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<View>
|
|
105
|
+
<Text>Credits: {credits}</Text>
|
|
106
|
+
<Text>Cost: 10 credits</Text>
|
|
107
|
+
|
|
108
|
+
<Button
|
|
109
|
+
onPress={handleAnalyze}
|
|
110
|
+
title={hasCredits ? 'Analyze (10 credits)' : 'Get More Credits'}
|
|
111
|
+
disabled={!hasCredits && credits < 10}
|
|
112
|
+
/>
|
|
113
|
+
</View>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### With Transaction Tracking
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
function TrackedCreditFeature() {
|
|
122
|
+
const { consumeCredit, hasCredits } = useCreditsGate({
|
|
123
|
+
creditCost: 3,
|
|
124
|
+
featureId: 'filter_apply',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const handleApplyFilter = async () => {
|
|
128
|
+
const result = await consumeCredit();
|
|
129
|
+
|
|
130
|
+
if (result.success) {
|
|
131
|
+
// Track usage
|
|
132
|
+
analytics.track('credit_consumed', {
|
|
133
|
+
feature_id: 'filter_apply',
|
|
134
|
+
amount: 3,
|
|
135
|
+
transaction_id: result.transaction?.id,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Apply filter
|
|
139
|
+
await applyFilter();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return <Button onPress={handleApplyFilter} title="Apply Filter (3 credits)" />;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### With Loading State
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
function CreditFeatureWithLoading() {
|
|
151
|
+
const { consumeCredit, isLoading, hasCredits } = useCreditsGate({
|
|
152
|
+
creditCost: 5,
|
|
153
|
+
featureId: 'premium_template',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const handleUse = async () => {
|
|
157
|
+
if (!hasCredits) return;
|
|
158
|
+
|
|
159
|
+
const result = await consumeCredit();
|
|
160
|
+
|
|
161
|
+
if (result.success) {
|
|
162
|
+
// Show loading while processing
|
|
163
|
+
await processFeature();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<View>
|
|
169
|
+
<Button
|
|
170
|
+
onPress={handleUse}
|
|
171
|
+
disabled={isLoading || !hasCredits}
|
|
172
|
+
title={isLoading ? 'Processing...' : 'Use Feature (5 credits)'}
|
|
173
|
+
/>
|
|
174
|
+
</View>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Examples
|
|
180
|
+
|
|
181
|
+
### Credit-Based Action Button
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
function CreditActionButton({ featureId, cost, action, label }) {
|
|
185
|
+
const { hasCredits, credits, consumeCredit } = useCreditsGate({
|
|
186
|
+
creditCost: cost,
|
|
187
|
+
featureId,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const handlePress = async () => {
|
|
191
|
+
if (!hasCredits) {
|
|
192
|
+
showInsufficientCreditsDialog(cost, credits);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await consumeCredit();
|
|
197
|
+
if (result.success) {
|
|
198
|
+
await action();
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<TouchableOpacity
|
|
204
|
+
onPress={handlePress}
|
|
205
|
+
disabled={!hasCredits}
|
|
206
|
+
style={hasCredits ? styles.buttonEnabled : styles.buttonDisabled}
|
|
207
|
+
>
|
|
208
|
+
<Text style={styles.buttonText}>{label}</Text>
|
|
209
|
+
<Text style={styles.costText}>{cost} credits</Text>
|
|
210
|
+
</TouchableOpacity>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Credit Balance Display
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
function CreditBalanceDisplay() {
|
|
219
|
+
const { credits, isLoading } = useCreditsGate({
|
|
220
|
+
creditCost: 1,
|
|
221
|
+
featureId: 'any',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<View style={styles.balanceContainer}>
|
|
226
|
+
<Text style={styles.balanceLabel}>Your Credits:</Text>
|
|
227
|
+
|
|
228
|
+
{isLoading ? (
|
|
229
|
+
<ActivityIndicator size="small" />
|
|
230
|
+
) : (
|
|
231
|
+
<Text style={styles.balanceValue}>{credits}</Text>
|
|
232
|
+
)}
|
|
233
|
+
</View>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Multiple Credit Tiers
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
function TieredFeatureAccess() {
|
|
242
|
+
const { credits: basicCredits, hasCredits: hasBasic } = useCreditsGate({
|
|
243
|
+
creditCost: 5,
|
|
244
|
+
featureId: 'basic_feature',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const { credits: proCredits, hasCredits: hasPro } = useCreditsGate({
|
|
248
|
+
creditCost: 20,
|
|
249
|
+
featureId: 'pro_feature',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<View>
|
|
254
|
+
<Button
|
|
255
|
+
onPress={handleBasicFeature}
|
|
256
|
+
disabled={!hasBasic}
|
|
257
|
+
title={`Basic Feature (5 credits)`}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
<Button
|
|
261
|
+
onPress={handleProFeature}
|
|
262
|
+
disabled={!hasPro}
|
|
263
|
+
title={`Pro Feature (20 credits)`}
|
|
264
|
+
/>
|
|
265
|
+
|
|
266
|
+
<Text>Balance: {basicCredits} credits</Text>
|
|
267
|
+
</View>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Result Type
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
interface CreditsResult {
|
|
276
|
+
success: boolean;
|
|
277
|
+
newBalance?: number;
|
|
278
|
+
transaction?: {
|
|
279
|
+
id: string;
|
|
280
|
+
amount: number;
|
|
281
|
+
reason: string;
|
|
282
|
+
timestamp: string;
|
|
283
|
+
};
|
|
284
|
+
error?: Error;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Error Handling
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
function RobustCreditFeature() {
|
|
292
|
+
const { consumeCredit } = useCreditsGate({
|
|
293
|
+
creditCost: 10,
|
|
294
|
+
featureId: 'robust_feature',
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const handleAction = async () => {
|
|
298
|
+
const result = await consumeCredit();
|
|
299
|
+
|
|
300
|
+
if (!result.success) {
|
|
301
|
+
if (result.error?.message.includes('Insufficient')) {
|
|
302
|
+
Alert.alert('Insufficient Credits', 'Please purchase more credits');
|
|
303
|
+
} else if (result.error?.message.includes('Network')) {
|
|
304
|
+
Alert.alert('Network Error', 'Please check your connection');
|
|
305
|
+
} else {
|
|
306
|
+
Alert.alert('Error', 'Failed to process. Please try again');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Log error
|
|
310
|
+
analytics.track('credit_consumption_failed', {
|
|
311
|
+
feature_id: 'robust_feature',
|
|
312
|
+
error: result.error?.message,
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Success
|
|
318
|
+
await executeFeature();
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return <Button onPress={handleAction} title="Execute (10 credits)" />;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Best Practices
|
|
326
|
+
|
|
327
|
+
1. **Always check `hasCredits`** before enabling actions
|
|
328
|
+
2. **Show credit cost** in UI
|
|
329
|
+
3. **Handle errors gracefully**
|
|
330
|
+
4. **Provide purchase prompt** when credits are insufficient
|
|
331
|
+
5. **Display current balance**
|
|
332
|
+
6. **Track credit consumption** in analytics
|
|
333
|
+
7. **Refetch balance** after consumption
|
|
334
|
+
|
|
335
|
+
## Related Hooks
|
|
336
|
+
|
|
337
|
+
- **useCredits** - For credit balance only
|
|
338
|
+
- **useDeductCredit** - For manual credit deduction
|
|
339
|
+
- **usePremiumWithCredits** - For premium OR credits access
|
|
340
|
+
- **useCreditChecker** - For simple credit checks
|
|
341
|
+
|
|
342
|
+
## See Also
|
|
343
|
+
|
|
344
|
+
- [useCredits](./useCredits.md)
|
|
345
|
+
- [useDeductCredit](./useDeductCredit.md)
|
|
346
|
+
- [usePremiumWithCredits](./usePremiumWithCredits.md)
|