@umituz/react-native-subscription 2.14.96 → 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 +3 -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/revenuecat/presentation/hooks/usePurchasePackage.ts +1 -1
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# useSubscriptionDetails Hook
|
|
2
|
+
|
|
3
|
+
Hook for accessing detailed subscription information including package details.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useSubscriptionDetails } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useSubscriptionDetails(): {
|
|
15
|
+
subscription: SubscriptionStatus | null;
|
|
16
|
+
package: Package | null;
|
|
17
|
+
period: PackagePeriod | null;
|
|
18
|
+
price: number | null;
|
|
19
|
+
pricePerMonth: number | null;
|
|
20
|
+
features: string[] | null;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
refetch: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Returns
|
|
28
|
+
|
|
29
|
+
| Property | Type | Description |
|
|
30
|
+
|----------|------|-------------|
|
|
31
|
+
| `subscription` | `SubscriptionStatus \| null` | Subscription status object |
|
|
32
|
+
| `package` | `Package \| null` | RevenueCat package object |
|
|
33
|
+
| `period` | `'monthly' \| 'annual' \| 'lifetime' \| null` | Subscription period |
|
|
34
|
+
| `price` | `number \| null` | Current price |
|
|
35
|
+
| `pricePerMonth` | `number \| null` | Monthly equivalent price |
|
|
36
|
+
| `features` | `string[] \| null` | List of features |
|
|
37
|
+
| `isLoading` | `boolean` | Loading state |
|
|
38
|
+
| `error` | `Error \| null` | Error if any |
|
|
39
|
+
|
|
40
|
+
## Basic Usage
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
function SubscriptionDetails() {
|
|
44
|
+
const {
|
|
45
|
+
subscription,
|
|
46
|
+
package,
|
|
47
|
+
period,
|
|
48
|
+
price,
|
|
49
|
+
features,
|
|
50
|
+
isLoading,
|
|
51
|
+
} = useSubscriptionDetails();
|
|
52
|
+
|
|
53
|
+
if (isLoading) return <ActivityIndicator />;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View>
|
|
57
|
+
<Text>Plan: {package?.product.title}</Text>
|
|
58
|
+
<Text>Price: {price} / {period}</Text>
|
|
59
|
+
<Text>Features:</Text>
|
|
60
|
+
{features?.map((feature, index) => (
|
|
61
|
+
<Text key={index}>• {feature}</Text>
|
|
62
|
+
))}
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Advanced Usage
|
|
69
|
+
|
|
70
|
+
### Detailed Subscription Card
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
function DetailedSubscriptionCard() {
|
|
74
|
+
const {
|
|
75
|
+
subscription,
|
|
76
|
+
package,
|
|
77
|
+
period,
|
|
78
|
+
price,
|
|
79
|
+
pricePerMonth,
|
|
80
|
+
features,
|
|
81
|
+
} = useSubscriptionDetails();
|
|
82
|
+
|
|
83
|
+
if (!subscription || !package) return null;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Card>
|
|
87
|
+
<Card.Title>
|
|
88
|
+
{package.product.title}
|
|
89
|
+
</Card.Title>
|
|
90
|
+
|
|
91
|
+
<Card.Body>
|
|
92
|
+
<DetailRow
|
|
93
|
+
label="Status"
|
|
94
|
+
value={subscription.isActive ? 'Active' : 'Inactive'}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<DetailRow
|
|
98
|
+
label="Price"
|
|
99
|
+
value={`${price} ${package?.product.currencyCode}`}
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
{period !== 'lifetime' && pricePerMonth && (
|
|
103
|
+
<DetailRow
|
|
104
|
+
label="Per Month"
|
|
105
|
+
value={`${pricePerMonth.toFixed(2)} ${package?.product.currencyCode}`}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<Divider />
|
|
110
|
+
|
|
111
|
+
<Text>Features:</Text>
|
|
112
|
+
{features?.map((feature, index) => (
|
|
113
|
+
<FeatureItem key={index} feature={feature} />
|
|
114
|
+
))}
|
|
115
|
+
</Card.Body>
|
|
116
|
+
</Card>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Price Comparison
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
function PriceComparison() {
|
|
125
|
+
const { price, period, pricePerMonth } = useSubscriptionDetails();
|
|
126
|
+
|
|
127
|
+
if (!price || !period) return null;
|
|
128
|
+
|
|
129
|
+
const monthlyPrices = {
|
|
130
|
+
monthly: price,
|
|
131
|
+
annual: pricePerMonth ? pricePerMonth * 12 : price,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<View>
|
|
136
|
+
<Text>Total: ${price}</Text>
|
|
137
|
+
<Text>Per Month: ${pricePerMonth?.toFixed(2) || 'N/A'}</Text>
|
|
138
|
+
|
|
139
|
+
{period === 'annual' && (
|
|
140
|
+
<SavingsBadge
|
|
141
|
+
original={monthlyPrices.monthly * 12}
|
|
142
|
+
current={price}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
</View>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### With Upgrade Suggestion
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
function UpgradeSuggestion() {
|
|
154
|
+
const {
|
|
155
|
+
subscription,
|
|
156
|
+
package: currentPackage,
|
|
157
|
+
price,
|
|
158
|
+
} = useSubscriptionDetails();
|
|
159
|
+
|
|
160
|
+
const { packages: allPackages } = useSubscriptionPackages();
|
|
161
|
+
|
|
162
|
+
// Find upgrade options
|
|
163
|
+
const upgradeOptions = allPackages.filter(
|
|
164
|
+
(pkg) =>
|
|
165
|
+
pkg.product.price > (currentPackage?.product.price || 0)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!subscription?.isPremium) {
|
|
169
|
+
return (
|
|
170
|
+
<View>
|
|
171
|
+
<Text>Upgrade to Premium for full access</Text>
|
|
172
|
+
<Button
|
|
173
|
+
onPress={() => navigation.navigate('Paywall')}
|
|
174
|
+
title="View Plans"
|
|
175
|
+
/>
|
|
176
|
+
</View>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<View>
|
|
182
|
+
<Text>Current Plan: {currentPackage?.product.title}</Text>
|
|
183
|
+
<Button
|
|
184
|
+
onPress={() => navigation.navigate('ManageSubscription')}
|
|
185
|
+
title="Manage Subscription"
|
|
186
|
+
/>
|
|
187
|
+
</View>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### With Feature List
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
function FeatureList() {
|
|
196
|
+
const { features } = useSubscriptionDetails();
|
|
197
|
+
|
|
198
|
+
if (!features) return null;
|
|
199
|
+
|
|
200
|
+
const featureIcons = {
|
|
201
|
+
'unlimited_access': '∞',
|
|
202
|
+
'ai_tools': '🤖',
|
|
203
|
+
'ad_free': '🛡️',
|
|
204
|
+
'priority_support': '💬',
|
|
205
|
+
'advanced_filters': '🔍',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<View>
|
|
210
|
+
<Text style={styles.title}>Your Premium Features:</Text>
|
|
211
|
+
|
|
212
|
+
{features.map((feature) => (
|
|
213
|
+
<FeatureItem
|
|
214
|
+
key={feature}
|
|
215
|
+
icon={featureIcons[feature] || '✓'}
|
|
216
|
+
text={formatFeatureName(feature)}
|
|
217
|
+
/>
|
|
218
|
+
))}
|
|
219
|
+
</View>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Examples
|
|
225
|
+
|
|
226
|
+
### Subscription Info Screen
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
function SubscriptionInfoScreen() {
|
|
230
|
+
const {
|
|
231
|
+
subscription,
|
|
232
|
+
package,
|
|
233
|
+
period,
|
|
234
|
+
price,
|
|
235
|
+
features,
|
|
236
|
+
isLoading,
|
|
237
|
+
refetch,
|
|
238
|
+
} = useSubscriptionDetails();
|
|
239
|
+
|
|
240
|
+
useFocusEffect(
|
|
241
|
+
useCallback(() => {
|
|
242
|
+
refetch();
|
|
243
|
+
}, [refetch])
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (isLoading) {
|
|
247
|
+
return <LoadingScreen />;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!subscription || !package) {
|
|
251
|
+
return (
|
|
252
|
+
<View>
|
|
253
|
+
<Text>No subscription found</Text>
|
|
254
|
+
<Button
|
|
255
|
+
onPress={() => navigation.navigate('Paywall')}
|
|
256
|
+
title="Subscribe"
|
|
257
|
+
/>
|
|
258
|
+
</View>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<ScrollView>
|
|
264
|
+
{/* Status Banner */}
|
|
265
|
+
<StatusBanner
|
|
266
|
+
isActive={subscription.isActive}
|
|
267
|
+
type={subscription.type}
|
|
268
|
+
/>
|
|
269
|
+
|
|
270
|
+
{/* Package Details */}
|
|
271
|
+
<Card style={styles.section}>
|
|
272
|
+
<Card.Title>Current Plan</Card.Title>
|
|
273
|
+
<Card.Body>
|
|
274
|
+
<Text style={styles.planName}>{package.product.title}</Text>
|
|
275
|
+
<Text style={styles.price}>
|
|
276
|
+
{price} {package.product.currencyCode}
|
|
277
|
+
</Text>
|
|
278
|
+
|
|
279
|
+
{period !== 'lifetime' && (
|
|
280
|
+
<Text style={styles.period}>
|
|
281
|
+
per {period.slice(0, -2)} // "month" -> "month"
|
|
282
|
+
</Text>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
{subscription.expirationDate && (
|
|
286
|
+
<Text style={styles.expires}>
|
|
287
|
+
Renews on {new Date(subscription.expirationDate).toLocaleDateString()}
|
|
288
|
+
</Text>
|
|
289
|
+
)}
|
|
290
|
+
</Card.Body>
|
|
291
|
+
</Card>
|
|
292
|
+
|
|
293
|
+
{/* Features */}
|
|
294
|
+
{features && features.length > 0 && (
|
|
295
|
+
<Card style={styles.section}>
|
|
296
|
+
<Card.Title>Features</Card.Title>
|
|
297
|
+
<Card.Body>
|
|
298
|
+
{features.map((feature, index) => (
|
|
299
|
+
<FeatureItem
|
|
300
|
+
key={index}
|
|
301
|
+
icon="✓"
|
|
302
|
+
text={formatFeatureName(feature)}
|
|
303
|
+
/>
|
|
304
|
+
))}
|
|
305
|
+
</Card.Body>
|
|
306
|
+
</Card>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
{/* Actions */}
|
|
310
|
+
<Card style={styles.section}>
|
|
311
|
+
<Card.Body>
|
|
312
|
+
<Button
|
|
313
|
+
onPress={() => navigation.navigate('ManageSubscription')}
|
|
314
|
+
title="Manage Subscription"
|
|
315
|
+
/>
|
|
316
|
+
|
|
317
|
+
<Button
|
|
318
|
+
onPress={refetch}
|
|
319
|
+
title="Refresh"
|
|
320
|
+
variant="outline"
|
|
321
|
+
/>
|
|
322
|
+
</Card.Body>
|
|
323
|
+
</Card>
|
|
324
|
+
</ScrollView>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Billing History
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
function BillingHistory() {
|
|
333
|
+
const { subscription, package, price } = useSubscriptionDetails();
|
|
334
|
+
|
|
335
|
+
if (!subscription || !package) return null;
|
|
336
|
+
|
|
337
|
+
const transactions = [
|
|
338
|
+
{
|
|
339
|
+
date: subscription.expirationDate,
|
|
340
|
+
description: `Renewal - ${package.product.title}`,
|
|
341
|
+
amount: price,
|
|
342
|
+
status: 'completed',
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<View>
|
|
348
|
+
<Text style={styles.title}>Billing History</Text>
|
|
349
|
+
|
|
350
|
+
{transactions.map((tx, index) => (
|
|
351
|
+
<TransactionItem
|
|
352
|
+
key={index}
|
|
353
|
+
date={tx.date}
|
|
354
|
+
description={tx.description}
|
|
355
|
+
amount={tx.amount}
|
|
356
|
+
status={tx.status}
|
|
357
|
+
/>
|
|
358
|
+
))}
|
|
359
|
+
</View>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Cancellation Flow
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
function CancellationFlow() {
|
|
368
|
+
const { subscription, package, willRenew } = useSubscriptionDetails();
|
|
369
|
+
|
|
370
|
+
const handleCancel = async () => {
|
|
371
|
+
if (!willRenew) {
|
|
372
|
+
Alert.alert('Info', 'Your subscription will not renew');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
Alert.alert(
|
|
377
|
+
'Cancel Subscription',
|
|
378
|
+
'Your subscription will expire at the end of the current period',
|
|
379
|
+
[
|
|
380
|
+
{ text: 'Keep', style: 'cancel' },
|
|
381
|
+
{
|
|
382
|
+
text: 'Cancel',
|
|
383
|
+
style: 'destructive',
|
|
384
|
+
onPress: async () => {
|
|
385
|
+
// Cancel with RevenueCat
|
|
386
|
+
try {
|
|
387
|
+
// Link to platform-specific subscription management
|
|
388
|
+
if (Platform.OS === 'ios') {
|
|
389
|
+
Linking.openURL('https://apps.apple.com/account/subscriptions');
|
|
390
|
+
} else {
|
|
391
|
+
Linking.openURL('https://play.google.com/store/account/subscriptions');
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
Alert.alert('Error', 'Could not open subscription settings');
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
]
|
|
399
|
+
);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<View>
|
|
404
|
+
<Text>Plan: {package?.product.title}</Text>
|
|
405
|
+
<Text>Auto-renew: {willRenew ? 'Yes' : 'No'}</Text>
|
|
406
|
+
|
|
407
|
+
<Button
|
|
408
|
+
onPress={handleCancel}
|
|
409
|
+
title="Cancel Subscription"
|
|
410
|
+
color="#F44336"
|
|
411
|
+
/>
|
|
412
|
+
</View>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Best Practices
|
|
418
|
+
|
|
419
|
+
1. **Handle null values** - Check if subscription and package exist
|
|
420
|
+
2. **Format prices** - Use currency formatters
|
|
421
|
+
3. **Display all info** - Show status, price, features
|
|
422
|
+
4. **Provide actions** - Add manage, cancel, upgrade buttons
|
|
423
|
+
5. **Refresh on focus** - Update when screen gains focus
|
|
424
|
+
6. **Handle errors** - Show user-friendly error messages
|
|
425
|
+
7. **Test different plans** - Monthly, annual, lifetime
|
|
426
|
+
|
|
427
|
+
## Related Hooks
|
|
428
|
+
|
|
429
|
+
- **useSubscription** - Basic subscription status
|
|
430
|
+
- **useSubscriptionStatus** - Detailed status
|
|
431
|
+
- **usePremium** - Simple premium check
|
|
432
|
+
- **useSubscriptionPackages** - Available packages
|
|
433
|
+
|
|
434
|
+
## See Also
|
|
435
|
+
|
|
436
|
+
- [useSubscriptionStatus](./useSubscriptionStatus.md)
|
|
437
|
+
- [Subscription Details Screen](../screens/README.md)
|
|
438
|
+
- [Package Utilities](../../../utils/README.md#package-utilities)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# useSubscriptionGate Hook
|
|
2
|
+
|
|
3
|
+
Subscription-only feature gating with simple, focused API.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useSubscriptionGate } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useSubscriptionGate(params: {
|
|
15
|
+
hasSubscription: boolean;
|
|
16
|
+
onSubscriptionRequired: () => void;
|
|
17
|
+
}): {
|
|
18
|
+
hasSubscription: boolean;
|
|
19
|
+
requireSubscription: (action: () => void | Promise<void>) => boolean;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Parameters
|
|
24
|
+
|
|
25
|
+
| Parameter | Type | Default | Description |
|
|
26
|
+
|-----------|------|---------|-------------|
|
|
27
|
+
| `hasSubscription` | `boolean` | **Required** | Whether user has active subscription |
|
|
28
|
+
| `onSubscriptionRequired` | `() => void` | **Required** | Callback when subscription is required |
|
|
29
|
+
|
|
30
|
+
## Returns
|
|
31
|
+
|
|
32
|
+
| Property | Type | Description |
|
|
33
|
+
|----------|------|-------------|
|
|
34
|
+
| `hasSubscription` | `boolean` | User has active subscription |
|
|
35
|
+
| `requireSubscription` | `(action) => boolean` | Gate action behind subscription |
|
|
36
|
+
|
|
37
|
+
## Basic Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
function PremiumFeature() {
|
|
41
|
+
const { isPremium } = usePremium();
|
|
42
|
+
|
|
43
|
+
const { requireSubscription, hasSubscription } = useSubscriptionGate({
|
|
44
|
+
hasSubscription: isPremium,
|
|
45
|
+
onSubscriptionRequired: () => {
|
|
46
|
+
showPaywall();
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const handlePremiumAction = () => {
|
|
51
|
+
const allowed = requireSubscription(() => {
|
|
52
|
+
executePremiumFeature();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!allowed) {
|
|
56
|
+
console.log('User was shown paywall');
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Button
|
|
62
|
+
onPress={handlePremiumAction}
|
|
63
|
+
title={hasSubscription ? 'Access' : 'Upgrade to Premium'}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Advanced Usage
|
|
70
|
+
|
|
71
|
+
### With Conditional Rendering
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
function ConditionalPremiumFeature() {
|
|
75
|
+
const { isPremium } = usePremium();
|
|
76
|
+
|
|
77
|
+
const { requireSubscription, hasSubscription } = useSubscriptionGate({
|
|
78
|
+
hasSubscription: isPremium,
|
|
79
|
+
onSubscriptionRequired: () => {
|
|
80
|
+
navigation.navigate('Paywall');
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!hasSubscription) {
|
|
85
|
+
return (
|
|
86
|
+
<View>
|
|
87
|
+
<PremiumLockedIcon />
|
|
88
|
+
<Text>This feature requires Premium</Text>
|
|
89
|
+
<Button
|
|
90
|
+
onPress={() => requireSubscription(() => {})}
|
|
91
|
+
title="Upgrade Now"
|
|
92
|
+
/>
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<View>
|
|
99
|
+
<Text>Premium Content</Text>
|
|
100
|
+
<PremiumFeatureComponent />
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### With Async Actions
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
function AsyncPremiumFeature() {
|
|
110
|
+
const { isPremium } = usePremium();
|
|
111
|
+
|
|
112
|
+
const { requireSubscription } = useSubscriptionGate({
|
|
113
|
+
hasSubscription: isPremium,
|
|
114
|
+
onSubscriptionRequired: () => {
|
|
115
|
+
showPaywall({
|
|
116
|
+
feature: 'Advanced Analytics',
|
|
117
|
+
onSubscribe: () => {
|
|
118
|
+
// Action will execute after subscription
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const handleExportData = () => {
|
|
125
|
+
requireSubscription(async () => {
|
|
126
|
+
await exportLargeDataset();
|
|
127
|
+
Alert.alert('Success', 'Data exported successfully');
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return <Button onPress={handleExportData} title="Export Data" />;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Best Practices
|
|
136
|
+
|
|
137
|
+
1. **Simple checks** - Keep gating logic straightforward
|
|
138
|
+
2. **Clear messaging** - Tell users what they're missing
|
|
139
|
+
3. **Smooth upgrades** - Make subscription path obvious
|
|
140
|
+
4. **Track access** - Monitor gate triggers for optimization
|
|
141
|
+
5. **Value first** - Show benefits before locking
|
|
142
|
+
6. **Graceful fallbacks** - Show preview or limited version
|
|
143
|
+
7. **Context-aware** - Customize paywall based on feature
|
|
144
|
+
|
|
145
|
+
## Development Logging
|
|
146
|
+
|
|
147
|
+
In development mode, the hook logs useful information:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// User not premium
|
|
151
|
+
[useSubscriptionGate] No subscription, showing paywall
|
|
152
|
+
|
|
153
|
+
// User has subscription
|
|
154
|
+
[useSubscriptionGate] Has subscription, proceeding
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Related Hooks
|
|
158
|
+
|
|
159
|
+
- **useAuthGate** - Add authentication requirements
|
|
160
|
+
- **useCreditsGate** - Credits-based gating
|
|
161
|
+
- **useFeatureGate** - Combined auth + subscription + credits
|
|
162
|
+
- **usePremiumGate** - Premium feature gating with auth option
|
|
163
|
+
|
|
164
|
+
## See Also
|
|
165
|
+
|
|
166
|
+
- [Subscription Gating](../../../docs/SUBSCRIPTION_GATING.md)
|
|
167
|
+
- [Paywall Triggers](../screens/README.md#triggers)
|
|
168
|
+
- [usePremium](./usePremium.md)
|