@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,424 @@
|
|
|
1
|
+
# useSubscriptionStatus Hook
|
|
2
|
+
|
|
3
|
+
Hook for accessing detailed subscription status information.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useSubscriptionStatus } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useSubscriptionStatus(): {
|
|
15
|
+
status: SubscriptionStatus | null;
|
|
16
|
+
tier: UserTier;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
isExpired: boolean;
|
|
19
|
+
willRenew: boolean;
|
|
20
|
+
expirationDate: Date | null;
|
|
21
|
+
daysUntilExpiration: number | null;
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
refetch: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Returns
|
|
29
|
+
|
|
30
|
+
| Property | Type | Description |
|
|
31
|
+
|----------|------|-------------|
|
|
32
|
+
| `status` | `SubscriptionStatus \| null` | Full status object |
|
|
33
|
+
| `tier` | `UserTier` | User tier (guest/free/premium) |
|
|
34
|
+
| `isActive` | `boolean` | Subscription is currently active |
|
|
35
|
+
| `isExpired` | `boolean` | Subscription has expired |
|
|
36
|
+
| `willRenew` | `boolean` | Subscription will auto-renew |
|
|
37
|
+
| `expirationDate` | `Date \| null` | When subscription expires |
|
|
38
|
+
| `daysUntilExpiration` | `number \| null` | Days until expiration |
|
|
39
|
+
| `isLoading` | `boolean` | Loading state |
|
|
40
|
+
| `error` | `Error \| null` | Error if any |
|
|
41
|
+
|
|
42
|
+
## Basic Usage
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
function SubscriptionStatusDisplay() {
|
|
46
|
+
const {
|
|
47
|
+
status,
|
|
48
|
+
tier,
|
|
49
|
+
isActive,
|
|
50
|
+
expirationDate,
|
|
51
|
+
daysUntilExpiration,
|
|
52
|
+
isLoading,
|
|
53
|
+
} = useSubscriptionStatus();
|
|
54
|
+
|
|
55
|
+
if (isLoading) return <ActivityIndicator />;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View>
|
|
59
|
+
<Text>Tier: {tier}</Text>
|
|
60
|
+
<Text>Status: {isActive ? 'Active' : 'Inactive'}</Text>
|
|
61
|
+
|
|
62
|
+
{expirationDate && (
|
|
63
|
+
<Text>
|
|
64
|
+
Expires: {expirationDate.toLocaleDateString()}
|
|
65
|
+
({daysUntilExpiration} days)
|
|
66
|
+
</Text>
|
|
67
|
+
)}
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Advanced Usage
|
|
74
|
+
|
|
75
|
+
### With Expiration Warning
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
function ExpirationWarning() {
|
|
79
|
+
const { daysUntilExpiration, willRenew } = useSubscriptionStatus();
|
|
80
|
+
|
|
81
|
+
if (willRenew) {
|
|
82
|
+
return <Text>Auto-renewal is on</Text>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (daysUntilExpiration === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (daysUntilExpiration <= 3) {
|
|
90
|
+
return (
|
|
91
|
+
<WarningCard
|
|
92
|
+
type={daysUntilExpiration <= 1 ? 'critical' : 'warning'}
|
|
93
|
+
message={`Your subscription expires in ${daysUntilExpiration} day${daysUntilExpiration > 1 ? 's' : ''}`}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (daysUntilExpiration <= 7) {
|
|
99
|
+
return <InfoCard message={`Subscription expires in ${daysUntilExpiration} days`} />;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### With Status Badge
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
function StatusBadge() {
|
|
110
|
+
const { isActive, isExpired } = useSubscriptionStatus();
|
|
111
|
+
|
|
112
|
+
const getStatusConfig = () => {
|
|
113
|
+
if (isExpired) {
|
|
114
|
+
return { color: 'red', icon: '⚠️', text: 'Expired' };
|
|
115
|
+
}
|
|
116
|
+
if (isActive) {
|
|
117
|
+
return { color: 'green', icon: '✅', text: 'Active' };
|
|
118
|
+
}
|
|
119
|
+
return { color: 'gray', icon: '⏸️', text: 'Inactive' };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const config = getStatusConfig();
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<View style={[styles.badge, { backgroundColor: config.color }]}>
|
|
126
|
+
<Text>{config.icon}</Text>
|
|
127
|
+
<Text>{config.text}</Text>
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### With Auto-Refresh
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
function AutoRefreshStatus() {
|
|
137
|
+
const { status, refetch, isLoading } = useSubscriptionStatus();
|
|
138
|
+
|
|
139
|
+
// Refetch every 5 minutes
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const interval = setInterval(() => {
|
|
142
|
+
refetch();
|
|
143
|
+
}, 5 * 60 * 1000);
|
|
144
|
+
|
|
145
|
+
return () => clearInterval(interval);
|
|
146
|
+
}, [refetch]);
|
|
147
|
+
|
|
148
|
+
// Refetch when app comes to foreground
|
|
149
|
+
useFocusEffect(
|
|
150
|
+
useCallback(() => {
|
|
151
|
+
refetch();
|
|
152
|
+
}, [refetch])
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<View>
|
|
157
|
+
<Text>Status: {status?.type}</Text>
|
|
158
|
+
<Button
|
|
159
|
+
onPress={refetch}
|
|
160
|
+
disabled={isLoading}
|
|
161
|
+
title="Refresh"
|
|
162
|
+
/>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### With Detailed Info Card
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
function DetailedSubscriptionCard() {
|
|
172
|
+
const {
|
|
173
|
+
status,
|
|
174
|
+
tier,
|
|
175
|
+
isActive,
|
|
176
|
+
willRenew,
|
|
177
|
+
expirationDate,
|
|
178
|
+
daysUntilExpiration,
|
|
179
|
+
} = useSubscriptionStatus();
|
|
180
|
+
|
|
181
|
+
if (!status) return null;
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<Card style={styles.card}>
|
|
185
|
+
<View style={styles.header}>
|
|
186
|
+
<Badge tier={tier} />
|
|
187
|
+
<Badge
|
|
188
|
+
variant={isActive ? 'success' : 'default'}
|
|
189
|
+
text={isActive ? 'Active' : 'Inactive'}
|
|
190
|
+
/>
|
|
191
|
+
</View>
|
|
192
|
+
|
|
193
|
+
<Divider />
|
|
194
|
+
|
|
195
|
+
<View style={styles.section}>
|
|
196
|
+
<DetailRow
|
|
197
|
+
label="Plan"
|
|
198
|
+
value={status.productId?.replace('_', ' ').toUpperCase()}
|
|
199
|
+
/>
|
|
200
|
+
<DetailRow
|
|
201
|
+
label="Auto-renew"
|
|
202
|
+
value={willRenew ? 'Enabled' : 'Disabled'}
|
|
203
|
+
/>
|
|
204
|
+
</View>
|
|
205
|
+
|
|
206
|
+
{expirationDate && (
|
|
207
|
+
<>
|
|
208
|
+
<Divider />
|
|
209
|
+
<View style={styles.section}>
|
|
210
|
+
<DetailRow
|
|
211
|
+
label="Expires"
|
|
212
|
+
value={expirationDate.toLocaleDateString()}
|
|
213
|
+
/>
|
|
214
|
+
{daysUntilExpiration !== null && (
|
|
215
|
+
<DetailRow
|
|
216
|
+
label="Days remaining"
|
|
217
|
+
value={daysUntilExpiration.toString()}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
</View>
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{daysUntilExpiration !== null &&
|
|
225
|
+
daysUntilExpiration <= 7 && (
|
|
226
|
+
<Alert
|
|
227
|
+
severity={daysUntilExpiration <= 3 ? 'warning' : 'info'}
|
|
228
|
+
message={`Subscription expires in ${daysUntilExpiration} days`}
|
|
229
|
+
/>
|
|
230
|
+
)}
|
|
231
|
+
</Card>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Examples
|
|
237
|
+
|
|
238
|
+
### Subscription Countdown
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
function SubscriptionCountdown() {
|
|
242
|
+
const { expirationDate, daysUntilExpiration, isActive } =
|
|
243
|
+
useSubscriptionStatus();
|
|
244
|
+
|
|
245
|
+
if (!isActive || !expirationDate) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const [timeLeft, setTimeLeft] = useState('');
|
|
250
|
+
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
const updateCountdown = () => {
|
|
253
|
+
const now = new Date();
|
|
254
|
+
const diff = expirationDate.getTime() - now.getTime();
|
|
255
|
+
|
|
256
|
+
if (diff <= 0) {
|
|
257
|
+
setTimeLeft('Expired');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
262
|
+
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
263
|
+
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
264
|
+
|
|
265
|
+
setTimeLeft(`${days}d ${hours}h ${minutes}m`);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
updateCountdown();
|
|
269
|
+
const interval = setInterval(updateCountdown, 60000); // Every minute
|
|
270
|
+
|
|
271
|
+
return () => clearInterval(interval);
|
|
272
|
+
}, [expirationDate]);
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<View style={styles.countdown}>
|
|
276
|
+
<Text>Expires in:</Text>
|
|
277
|
+
<Text style={styles.timeLeft}>{timeLeft}</Text>
|
|
278
|
+
</View>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Status History Tracker
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
function StatusHistoryTracker() {
|
|
287
|
+
const { status } = useSubscriptionStatus();
|
|
288
|
+
const previousStatus = useRef(status);
|
|
289
|
+
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (previousStatus.current?.type !== status?.type) {
|
|
292
|
+
analytics.track('subscription_status_changed', {
|
|
293
|
+
from: previousStatus.current?.type,
|
|
294
|
+
to: status?.type,
|
|
295
|
+
timestamp: Date.now(),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Log to database for audit trail
|
|
299
|
+
logStatusChange(previousStatus.current, status);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
previousStatus.current = status;
|
|
303
|
+
}, [status]);
|
|
304
|
+
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Lifecycle Management
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
function SubscriptionLifecycleManager() {
|
|
313
|
+
const { status, isActive, daysUntilExpiration, willRenew } =
|
|
314
|
+
useSubscriptionStatus();
|
|
315
|
+
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (!isActive) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Expiration warning
|
|
322
|
+
if (daysUntilExpiration === 3) {
|
|
323
|
+
notifications.send({
|
|
324
|
+
title: 'Subscription Expiring Soon',
|
|
325
|
+
body: 'Your subscription expires in 3 days',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Renewal reminder
|
|
330
|
+
if (daysUntilExpiration === 1 && !willRenew) {
|
|
331
|
+
notifications.send({
|
|
332
|
+
title: 'Last Chance!',
|
|
333
|
+
body: 'Your subscription expires tomorrow. Renew now to maintain access.',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}, [daysUntilExpiration, willRenew, isActive]);
|
|
337
|
+
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## State Machine
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
function SubscriptionStateMachine() {
|
|
346
|
+
const { status, isActive, isExpired } = useSubscriptionStatus();
|
|
347
|
+
|
|
348
|
+
const getState = () => {
|
|
349
|
+
if (!status) return 'unknown';
|
|
350
|
+
if (!isActive) return 'inactive';
|
|
351
|
+
if (isExpired) return 'expired';
|
|
352
|
+
return 'active';
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const state = getState();
|
|
356
|
+
|
|
357
|
+
switch (state) {
|
|
358
|
+
case 'unknown':
|
|
359
|
+
return <LoadingState />;
|
|
360
|
+
case 'inactive':
|
|
361
|
+
return <UpgradePrompt />;
|
|
362
|
+
case 'expired':
|
|
363
|
+
return <RenewPrompt />;
|
|
364
|
+
case 'active':
|
|
365
|
+
return <PremiumContent />;
|
|
366
|
+
default:
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Visual Status Indicator
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
function StatusIndicator() {
|
|
376
|
+
const { isActive, isExpired, willRenew } = useSubscriptionStatus();
|
|
377
|
+
|
|
378
|
+
const getStatusColor = () => {
|
|
379
|
+
if (isExpired) return '#F44336'; // red
|
|
380
|
+
if (isActive) return '#4CAF50'; // green
|
|
381
|
+
return '#9E9E9E'; // gray
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const getStatusIcon = () => {
|
|
385
|
+
if (isExpired) return '⚠️';
|
|
386
|
+
if (isActive && willRenew) return '♻️';
|
|
387
|
+
if (isActive && !willRenew) return '⏳';
|
|
388
|
+
return '⏸️';
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<View style={[styles.indicator, { borderColor: getStatusColor() }]}>
|
|
393
|
+
<Text>{getStatusIcon()}</Text>
|
|
394
|
+
<ActivityIndicator
|
|
395
|
+
size={8}
|
|
396
|
+
color={getStatusColor()}
|
|
397
|
+
animating={isActive}
|
|
398
|
+
/>
|
|
399
|
+
</View>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Best Practices
|
|
405
|
+
|
|
406
|
+
1. **Handle null status** - Check if status exists
|
|
407
|
+
2. **Show expiration warnings** - Remind users before expiry
|
|
408
|
+
3. **Display renewal status** - Show if auto-renew is on/off
|
|
409
|
+
4. **Track changes** - Monitor status transitions
|
|
410
|
+
5. **Refresh regularly** - Keep status up to date
|
|
411
|
+
6. **Handle errors** - Show user-friendly error messages
|
|
412
|
+
|
|
413
|
+
## Related Hooks
|
|
414
|
+
|
|
415
|
+
- **usePremium** - Simple premium check
|
|
416
|
+
- **useSubscription** - Subscription details
|
|
417
|
+
- **useSubscriptionDetails** - Package and plan info
|
|
418
|
+
- **useUserTier** - Tier information
|
|
419
|
+
|
|
420
|
+
## See Also
|
|
421
|
+
|
|
422
|
+
- [SubscriptionStatus Entity](../../domain/entities/README.md)
|
|
423
|
+
- [usePremium](./usePremium.md)
|
|
424
|
+
- [useUserTier](./useUserTier.md)
|