@umituz/react-native-subscription 2.41.4 → 2.41.6
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/package.json +1 -1
- package/src/domains/credits/application/PurchaseMetadataGenerator.ts +4 -1
- package/src/domains/paywall/components/PaywallScreen.tsx +1 -1
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +2 -2
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +2 -2
- package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +34 -31
- package/src/domains/subscription/presentation/useSubscriptionFlow.ts +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.41.
|
|
3
|
+
"version": "2.41.6",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -8,6 +8,9 @@ import type {
|
|
|
8
8
|
import { detectPackageType } from "../../../utils/packageTypeDetector";
|
|
9
9
|
import { PACKAGE_TYPE, PURCHASE_TYPE, type Platform } from "../../subscription/core/SubscriptionConstants";
|
|
10
10
|
|
|
11
|
+
/** Maximum number of purchase history entries to retain per user */
|
|
12
|
+
const MAX_PURCHASE_HISTORY_SIZE = 10;
|
|
13
|
+
|
|
11
14
|
interface MetadataGeneratorConfig {
|
|
12
15
|
productId: string;
|
|
13
16
|
source: PurchaseSource;
|
|
@@ -44,7 +47,7 @@ export function generatePurchaseMetadata(
|
|
|
44
47
|
timestamp: Timestamp.fromDate(new Date()),
|
|
45
48
|
};
|
|
46
49
|
|
|
47
|
-
const purchaseHistory = [...existingData.purchaseHistory, newMetadata].slice(-
|
|
50
|
+
const purchaseHistory = [...existingData.purchaseHistory, newMetadata].slice(-MAX_PURCHASE_HISTORY_SIZE);
|
|
48
51
|
|
|
49
52
|
return { purchaseType, purchaseHistory };
|
|
50
53
|
}
|
|
@@ -158,7 +158,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
158
158
|
return (
|
|
159
159
|
<View key={`feat-${item.feature.text}`} style={[styles.featureRow, { marginHorizontal: 24, marginBottom: 16 }]}>
|
|
160
160
|
<View style={[styles.featureIcon, { backgroundColor: tokens.colors.primary }]}>
|
|
161
|
-
<AtomicIcon name={item.feature.icon
|
|
161
|
+
<AtomicIcon name={item.feature.icon} customSize={16} customColor={tokens.colors.onPrimary} />
|
|
162
162
|
</View>
|
|
163
163
|
<AtomicText type="bodyMedium" style={[styles.featureText, { color: tokens.colors.textPrimary }]}>
|
|
164
164
|
{item.feature.text}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
interface CacheEntry {
|
|
2
|
-
promise: Promise<boolean
|
|
2
|
+
promise: Promise<boolean> | null;
|
|
3
3
|
resolvedUserId: string | null;
|
|
4
4
|
completed: boolean;
|
|
5
5
|
}
|
|
@@ -18,7 +18,7 @@ export class InitializationCache {
|
|
|
18
18
|
|
|
19
19
|
setPromise(promise: Promise<boolean>, cacheKey: string, realUserId: string | null): void {
|
|
20
20
|
const entry: CacheEntry = {
|
|
21
|
-
promise: null
|
|
21
|
+
promise: null,
|
|
22
22
|
resolvedUserId: realUserId,
|
|
23
23
|
completed: false,
|
|
24
24
|
};
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
} from "../providers/SubscriptionFlowProvider";
|
|
77
77
|
import { SubscriptionFlowStatus } from "../useSubscriptionFlow";
|
|
78
78
|
|
|
79
|
-
const ManagedSubscriptionFlowInner
|
|
79
|
+
const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
|
|
80
80
|
children,
|
|
81
81
|
navigation,
|
|
82
82
|
islocalizationReady,
|
|
@@ -198,7 +198,7 @@ const ManagedSubscriptionFlowInner: React.FC<ManagedSubscriptionFlowProps> = ({
|
|
|
198
198
|
)}
|
|
199
199
|
</>
|
|
200
200
|
);
|
|
201
|
-
};
|
|
201
|
+
});
|
|
202
202
|
|
|
203
203
|
export const ManagedSubscriptionFlow: React.FC<ManagedSubscriptionFlowProps> = (props) => {
|
|
204
204
|
return (
|
|
@@ -9,68 +9,71 @@ interface SubscriptionFlowContextType {
|
|
|
9
9
|
const SubscriptionFlowContext = createContext<SubscriptionFlowContextType | undefined>(undefined);
|
|
10
10
|
|
|
11
11
|
export const SubscriptionFlowProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
12
|
-
|
|
12
|
+
// Selectors for stable references and only what we need
|
|
13
|
+
const isInitialized = useSubscriptionFlowStore((state) => state.isInitialized);
|
|
14
|
+
const isOnboardingComplete = useSubscriptionFlowStore((state) => state.isOnboardingComplete);
|
|
15
|
+
const showPostOnboardingPaywall = useSubscriptionFlowStore((state) => state.showPostOnboardingPaywall);
|
|
16
|
+
const status = useSubscriptionFlowStore((state) => state.status);
|
|
17
|
+
const setInitialized = useSubscriptionFlowStore((state) => state.setInitialized);
|
|
18
|
+
const setStatus = useSubscriptionFlowStore((state) => state.setStatus);
|
|
13
19
|
|
|
14
20
|
useEffect(() => {
|
|
15
21
|
// 1. Listen to background initialization state
|
|
16
22
|
const unsubscribe = initializationState.subscribe(() => {
|
|
17
23
|
const { initialized } = initializationState.getSnapshot();
|
|
18
24
|
if (__DEV__) {
|
|
19
|
-
console.log('[SubscriptionFlowProvider] 🔄 Initialization
|
|
25
|
+
console.log('[SubscriptionFlowProvider] 🔄 Initialization status updated:', { initialized });
|
|
20
26
|
}
|
|
21
|
-
if (initialized && !
|
|
22
|
-
|
|
27
|
+
if (initialized && !isInitialized) {
|
|
28
|
+
setInitialized(true);
|
|
23
29
|
}
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
// Check initial state
|
|
27
|
-
const { initialized } = initializationState.getSnapshot();
|
|
28
|
-
if (
|
|
33
|
+
const { initialized: currentlyInitialized } = initializationState.getSnapshot();
|
|
34
|
+
if (currentlyInitialized && !isInitialized) {
|
|
29
35
|
if (__DEV__) console.log('[SubscriptionFlowProvider] ✅ Already initialized on mount');
|
|
30
|
-
|
|
36
|
+
setInitialized(true);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
return () => unsubscribe();
|
|
34
|
-
}, [
|
|
40
|
+
}, [isInitialized, setInitialized]);
|
|
35
41
|
|
|
36
42
|
useEffect(() => {
|
|
37
43
|
// This effect manages the overall flow status transition
|
|
38
44
|
if (__DEV__) {
|
|
39
45
|
console.log('[SubscriptionFlowProvider] 🧠 Calculating Status Transition', {
|
|
40
|
-
isInitialized
|
|
41
|
-
isOnboardingComplete
|
|
42
|
-
showPostOnboardingPaywall
|
|
43
|
-
currentStatus:
|
|
46
|
+
isInitialized,
|
|
47
|
+
isOnboardingComplete,
|
|
48
|
+
showPostOnboardingPaywall,
|
|
49
|
+
currentStatus: status
|
|
44
50
|
});
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
store.setStatus(SubscriptionFlowStatus.INITIALIZING);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
53
|
+
let nextStatus = SubscriptionFlowStatus.READY;
|
|
51
54
|
|
|
52
|
-
if (!
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
if (!isInitialized) {
|
|
56
|
+
nextStatus = SubscriptionFlowStatus.INITIALIZING;
|
|
57
|
+
} else if (!isOnboardingComplete) {
|
|
58
|
+
nextStatus = SubscriptionFlowStatus.ONBOARDING;
|
|
59
|
+
} else if (showPostOnboardingPaywall) {
|
|
60
|
+
nextStatus = SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
if (nextStatus !== status) {
|
|
64
|
+
if (__DEV__) console.log('[SubscriptionFlowProvider] 🚀 Transitioning status to:', nextStatus);
|
|
65
|
+
setStatus(nextStatus);
|
|
60
66
|
}
|
|
61
|
-
|
|
62
|
-
if (__DEV__) console.log('[SubscriptionFlowProvider] 🏆 Flow is READY');
|
|
63
|
-
store.setStatus(SubscriptionFlowStatus.READY);
|
|
64
67
|
}, [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
isInitialized,
|
|
69
|
+
isOnboardingComplete,
|
|
70
|
+
showPostOnboardingPaywall,
|
|
71
|
+
status,
|
|
72
|
+
setStatus
|
|
70
73
|
]);
|
|
71
74
|
|
|
72
75
|
return (
|
|
73
|
-
<SubscriptionFlowContext.Provider value={{ status:
|
|
76
|
+
<SubscriptionFlowContext.Provider value={{ status: status }}>
|
|
74
77
|
{children}
|
|
75
78
|
</SubscriptionFlowContext.Provider>
|
|
76
79
|
);
|
|
@@ -93,8 +93,14 @@ export const useSubscriptionFlowStore = createStore<SubscriptionFlowState, Subsc
|
|
|
93
93
|
setAuthModalOpen: (open: boolean) => set({ isAuthModalOpen: open }),
|
|
94
94
|
setShowFeedback: (show: boolean) => set({ showFeedback: show }),
|
|
95
95
|
markPaywallShown: async () => set({ paywallShown: true }),
|
|
96
|
-
setInitialized: (initialized: boolean) => set(
|
|
97
|
-
|
|
96
|
+
setInitialized: (initialized: boolean) => set((state) => {
|
|
97
|
+
if (state.isInitialized === initialized) return state;
|
|
98
|
+
return { isInitialized: initialized };
|
|
99
|
+
}),
|
|
100
|
+
setStatus: (status: SubscriptionFlowStatus) => set((state) => {
|
|
101
|
+
if (state.status === status) return state;
|
|
102
|
+
return { status };
|
|
103
|
+
}),
|
|
98
104
|
setSyncStatus: (syncStatus: SyncStatus, syncError: string | null = null) =>
|
|
99
105
|
set({ syncStatus, syncError }),
|
|
100
106
|
resetFlow: async () => {
|