@umituz/react-native-subscription 3.0.1 → 3.1.0
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/paywall/components/PaywallScreen.tsx +1 -8
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.states.tsx +9 -9
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +3 -3
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.parts.tsx +1 -2
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.tsx +2 -2
- package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +3 -5
- package/src/index.hooks.ts +0 -4
- package/src/domains/paywall/hooks/usePaywallOrchestrator.ts +0 -166
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
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",
|
|
@@ -75,7 +75,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
75
75
|
selectedPlanId,
|
|
76
76
|
setSelectedPlanId,
|
|
77
77
|
isProcessing,
|
|
78
|
-
handlePurchase,
|
|
79
78
|
handleRestore,
|
|
80
79
|
resetState
|
|
81
80
|
} = usePaywallActions({
|
|
@@ -228,14 +227,8 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
228
227
|
translations={translations}
|
|
229
228
|
legalUrls={legalUrls}
|
|
230
229
|
isProcessing={isProcessing}
|
|
231
|
-
selectedPlanId={selectedPlanId}
|
|
232
|
-
packages={packages}
|
|
233
|
-
onPurchase={handlePurchase}
|
|
234
230
|
onRestore={handleRestore}
|
|
235
|
-
|
|
236
|
-
insets={insets}
|
|
237
|
-
tokens={tokens}
|
|
238
|
-
styles={styles}
|
|
231
|
+
onLegalClick={handleLegalUrl}
|
|
239
232
|
/>
|
|
240
233
|
</View>
|
|
241
234
|
);
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import type {
|
|
7
|
+
import type { PurchasesPackage } from "react-native-purchases";
|
|
8
|
+
import type { UserCredits } from "../../../credits/core/Credits";
|
|
8
9
|
import { SplashScreen } from "@umituz/react-native-design-system/molecules";
|
|
9
10
|
import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
|
|
10
|
-
import { useSubscriptionFlowStore } from "../useSubscriptionFlow";
|
|
11
11
|
import type { ManagedSubscriptionFlowProps } from "./ManagedSubscriptionFlow";
|
|
12
12
|
import { PaywallScreen } from "../../../paywall/components/PaywallScreen";
|
|
13
13
|
import { PaywallFeedbackScreen } from "./feedback/PaywallFeedbackScreen";
|
|
@@ -57,12 +57,12 @@ export const OnboardingState: React.FC<OnboardingStateProps> = ({ config, onComp
|
|
|
57
57
|
|
|
58
58
|
interface PaywallStateProps {
|
|
59
59
|
config: ManagedSubscriptionFlowProps["paywall"];
|
|
60
|
-
packages:
|
|
60
|
+
packages: PurchasesPackage[];
|
|
61
61
|
isPremium: boolean;
|
|
62
|
-
credits:
|
|
62
|
+
credits: UserCredits | null;
|
|
63
63
|
isSyncing: boolean;
|
|
64
|
-
onPurchase: (
|
|
65
|
-
onRestore: () => Promise<
|
|
64
|
+
onPurchase: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
65
|
+
onRestore: () => Promise<boolean>;
|
|
66
66
|
onClose: (purchased: boolean) => void;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -78,9 +78,9 @@ export const PaywallState: React.FC<PaywallStateProps> = ({
|
|
|
78
78
|
}) => {
|
|
79
79
|
const [purchaseSuccessful, setPurchaseSuccessful] = React.useState(false);
|
|
80
80
|
|
|
81
|
-
const handlePurchase = async (
|
|
82
|
-
const result = await onPurchase(
|
|
83
|
-
if (result
|
|
81
|
+
const handlePurchase = async (pkg: PurchasesPackage) => {
|
|
82
|
+
const result = await onPurchase(pkg);
|
|
83
|
+
if (result) {
|
|
84
84
|
setPurchaseSuccessful(true);
|
|
85
85
|
}
|
|
86
86
|
return result;
|
|
@@ -91,7 +91,7 @@ const ManagedSubscriptionFlowInner: React.FC<ManagedSubscriptionFlowProps> = ({
|
|
|
91
91
|
const status = useSubscriptionFlowStatus();
|
|
92
92
|
|
|
93
93
|
// Premium hooks
|
|
94
|
-
const { isPremium, isSyncing, credits
|
|
94
|
+
const { isPremium, isSyncing, credits } = usePremiumStatus();
|
|
95
95
|
const { packages } = usePremiumPackages();
|
|
96
96
|
const { purchasePackage, restorePurchase } = usePremiumActions();
|
|
97
97
|
|
|
@@ -108,7 +108,7 @@ const ManagedSubscriptionFlowInner: React.FC<ManagedSubscriptionFlowProps> = ({
|
|
|
108
108
|
// ========================================================================
|
|
109
109
|
|
|
110
110
|
useEffect(() => {
|
|
111
|
-
if (status === SubscriptionFlowStatus.CHECK_PREMIUM && !
|
|
111
|
+
if (status === SubscriptionFlowStatus.CHECK_PREMIUM && !isSyncing) {
|
|
112
112
|
const paywallShown = useSubscriptionFlowStore.getState().paywallShown;
|
|
113
113
|
|
|
114
114
|
if (isPremium) {
|
|
@@ -119,7 +119,7 @@ const ManagedSubscriptionFlowInner: React.FC<ManagedSubscriptionFlowProps> = ({
|
|
|
119
119
|
completePaywall(false);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
}, [status, isPremium,
|
|
122
|
+
}, [status, isPremium, isSyncing, showPaywall, completePaywall]);
|
|
123
123
|
|
|
124
124
|
useEffect(() => {
|
|
125
125
|
if (status === SubscriptionFlowStatus.READY && showFeedback) {
|
package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.parts.tsx
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View, TouchableOpacity
|
|
7
|
+
import { View, TouchableOpacity } from "react-native";
|
|
8
8
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
9
|
-
import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
|
|
10
9
|
import { FeedbackOption } from "./FeedbackOption";
|
|
11
10
|
import type { PaywallFeedbackTranslations } from "./PaywallFeedbackScreen.types";
|
|
12
11
|
|
|
@@ -38,7 +38,7 @@ export const PaywallFeedbackScreen: React.FC<PaywallFeedbackScreenProps> = React
|
|
|
38
38
|
canSubmit,
|
|
39
39
|
} = usePaywallFeedback({ onSubmit, onClose });
|
|
40
40
|
|
|
41
|
-
const screenStyles = useMemo(() => createScreenStyles(
|
|
41
|
+
const screenStyles = useMemo(() => createScreenStyles(), []);
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
44
|
<View style={[screenStyles.container, { backgroundColor: 'white', opacity: 1 }]}>
|
|
@@ -96,7 +96,7 @@ PaywallFeedbackScreen.displayName = "PaywallFeedbackScreen";
|
|
|
96
96
|
// STYLES
|
|
97
97
|
// ============================================================================
|
|
98
98
|
|
|
99
|
-
const createScreenStyles = (
|
|
99
|
+
const createScreenStyles = () => ({
|
|
100
100
|
container: {
|
|
101
101
|
flex: 1,
|
|
102
102
|
},
|
|
@@ -16,7 +16,6 @@ export const SubscriptionFlowProvider: React.FC<{ children: React.ReactNode }> =
|
|
|
16
16
|
// Selectors for stable references and only what we need
|
|
17
17
|
const isInitialized = useSubscriptionFlowStore((state) => state.isInitialized);
|
|
18
18
|
const isOnboardingComplete = useSubscriptionFlowStore((state) => state.isOnboardingComplete);
|
|
19
|
-
const showPostOnboardingPaywall = useSubscriptionFlowStore((state) => state.showPostOnboardingPaywall);
|
|
20
19
|
const status = useSubscriptionFlowStore((state) => state.status);
|
|
21
20
|
const setInitialized = useSubscriptionFlowStore((state) => state.setInitialized);
|
|
22
21
|
const setStatus = useSubscriptionFlowStore((state) => state.setStatus);
|
|
@@ -49,7 +48,6 @@ export const SubscriptionFlowProvider: React.FC<{ children: React.ReactNode }> =
|
|
|
49
48
|
console.log('[SubscriptionFlowProvider] 🧠 Calculating Status Transition', {
|
|
50
49
|
isInitialized,
|
|
51
50
|
isOnboardingComplete,
|
|
52
|
-
showPostOnboardingPaywall,
|
|
53
51
|
currentStatus: status
|
|
54
52
|
});
|
|
55
53
|
}
|
|
@@ -60,8 +58,9 @@ export const SubscriptionFlowProvider: React.FC<{ children: React.ReactNode }> =
|
|
|
60
58
|
nextStatus = SubscriptionFlowStatus.INITIALIZING;
|
|
61
59
|
} else if (!isOnboardingComplete) {
|
|
62
60
|
nextStatus = SubscriptionFlowStatus.ONBOARDING;
|
|
63
|
-
} else
|
|
64
|
-
|
|
61
|
+
} else {
|
|
62
|
+
// Onboarding complete - let ManagedSubscriptionFlow handle CHECK_PREMIUM
|
|
63
|
+
nextStatus = SubscriptionFlowStatus.CHECK_PREMIUM;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
if (nextStatus !== status) {
|
|
@@ -71,7 +70,6 @@ export const SubscriptionFlowProvider: React.FC<{ children: React.ReactNode }> =
|
|
|
71
70
|
}, [
|
|
72
71
|
isInitialized,
|
|
73
72
|
isOnboardingComplete,
|
|
74
|
-
showPostOnboardingPaywall,
|
|
75
73
|
status,
|
|
76
74
|
setStatus
|
|
77
75
|
]);
|
package/src/index.hooks.ts
CHANGED
|
@@ -35,7 +35,3 @@ export type {
|
|
|
35
35
|
SettingsFeedbackData,
|
|
36
36
|
UseSettingsFeedbackSubmitOptions,
|
|
37
37
|
} from "./presentation/hooks/feedback/useFeedbackSubmit";
|
|
38
|
-
|
|
39
|
-
// Paywall Hooks
|
|
40
|
-
export { usePaywallOrchestrator } from "./domains/paywall/hooks/usePaywallOrchestrator";
|
|
41
|
-
export type { PaywallOrchestratorOptions } from "./domains/paywall/hooks/usePaywallOrchestrator";
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import type { NavigationProp } from "@react-navigation/native";
|
|
3
|
-
import type { ImageSourcePropType } from "react-native";
|
|
4
|
-
import { usePremiumStatus } from "../../subscription/presentation/usePremiumStatus";
|
|
5
|
-
import { usePremiumPackages } from "../../subscription/presentation/usePremiumPackages";
|
|
6
|
-
import { usePremiumActions } from "../../subscription/presentation/usePremiumActions";
|
|
7
|
-
import { useSubscriptionFlowStore } from "../../subscription/presentation/useSubscriptionFlow";
|
|
8
|
-
import { usePaywallVisibility } from "../../subscription/presentation/usePaywallVisibility";
|
|
9
|
-
import { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
|
|
10
|
-
|
|
11
|
-
export interface PaywallOrchestratorOptions {
|
|
12
|
-
navigation: NavigationProp<any>;
|
|
13
|
-
translations: PaywallTranslations;
|
|
14
|
-
features: SubscriptionFeature[];
|
|
15
|
-
legalUrls: PaywallLegalUrls;
|
|
16
|
-
heroImage: ImageSourcePropType;
|
|
17
|
-
isNavReady?: boolean;
|
|
18
|
-
isLocalizationReady?: boolean;
|
|
19
|
-
bestValueIdentifier?: string;
|
|
20
|
-
creditsLabel?: string;
|
|
21
|
-
/** Disable manual navigation (used when paywall is rendered inline) */
|
|
22
|
-
disableNavigation?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function usePaywallOrchestrator({
|
|
26
|
-
navigation,
|
|
27
|
-
translations,
|
|
28
|
-
features,
|
|
29
|
-
legalUrls,
|
|
30
|
-
heroImage,
|
|
31
|
-
isNavReady = true,
|
|
32
|
-
isLocalizationReady = true,
|
|
33
|
-
bestValueIdentifier = "yearly",
|
|
34
|
-
creditsLabel,
|
|
35
|
-
disableNavigation = false,
|
|
36
|
-
}: PaywallOrchestratorOptions) {
|
|
37
|
-
const { isPremium, isSyncing, credits } = usePremiumStatus();
|
|
38
|
-
const { packages } = usePremiumPackages();
|
|
39
|
-
const { purchasePackage, restorePurchase } = usePremiumActions();
|
|
40
|
-
|
|
41
|
-
const isOnboardingComplete = useSubscriptionFlowStore((state) => state.isOnboardingComplete);
|
|
42
|
-
const showPostOnboardingPaywall = useSubscriptionFlowStore((state) => state.showPostOnboardingPaywall);
|
|
43
|
-
const paywallShown = useSubscriptionFlowStore((state) => state.paywallShown);
|
|
44
|
-
const isAuthModalOpen = useSubscriptionFlowStore((state) => state.isAuthModalOpen);
|
|
45
|
-
const showFeedback = useSubscriptionFlowStore((state) => state.showFeedback);
|
|
46
|
-
const markPaywallShown = useSubscriptionFlowStore((state) => state.markPaywallShown);
|
|
47
|
-
const closePostOnboardingPaywall = useSubscriptionFlowStore((state) => state.closePostOnboardingPaywall);
|
|
48
|
-
const setShowFeedback = useSubscriptionFlowStore((state) => state.setShowFeedback);
|
|
49
|
-
|
|
50
|
-
const { showPaywall, closePaywall } = usePaywallVisibility();
|
|
51
|
-
const hasNavigatedRef = useRef(false);
|
|
52
|
-
|
|
53
|
-
const handleClose = () => {
|
|
54
|
-
closePaywall();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!isNavReady || !isLocalizationReady) return;
|
|
59
|
-
|
|
60
|
-
const shouldShowPostOnboarding =
|
|
61
|
-
isOnboardingComplete &&
|
|
62
|
-
showPostOnboardingPaywall &&
|
|
63
|
-
!paywallShown &&
|
|
64
|
-
!isAuthModalOpen &&
|
|
65
|
-
!isPremium;
|
|
66
|
-
|
|
67
|
-
const shouldShowManual = showPaywall && !isPremium && !isAuthModalOpen;
|
|
68
|
-
|
|
69
|
-
if (shouldShowPostOnboarding || shouldShowManual) {
|
|
70
|
-
if (hasNavigatedRef.current) return;
|
|
71
|
-
hasNavigatedRef.current = true;
|
|
72
|
-
|
|
73
|
-
// Skip navigation if disabled (paywall rendered inline)
|
|
74
|
-
if (disableNavigation) {
|
|
75
|
-
if (__DEV__) {
|
|
76
|
-
console.log('[usePaywallOrchestrator] ⏭️ Skipping navigation (disableNavigation=true)', {
|
|
77
|
-
source: shouldShowPostOnboarding ? "onboarding" : "manual",
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (shouldShowPostOnboarding) {
|
|
82
|
-
markPaywallShown();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (showPaywall) {
|
|
86
|
-
closePaywall();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (__DEV__) console.log('[usePaywallOrchestrator] 🚀 Navigating to Paywall', {
|
|
93
|
-
source: shouldShowPostOnboarding ? "onboarding" : "manual",
|
|
94
|
-
packagesCount: packages.length
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
navigation.navigate("PaywallScreen", {
|
|
98
|
-
translations,
|
|
99
|
-
legalUrls,
|
|
100
|
-
features,
|
|
101
|
-
bestValueIdentifier,
|
|
102
|
-
creditsLabel,
|
|
103
|
-
heroImage,
|
|
104
|
-
source: shouldShowPostOnboarding ? "onboarding" : "manual",
|
|
105
|
-
packages,
|
|
106
|
-
isPremium,
|
|
107
|
-
credits,
|
|
108
|
-
isSyncing,
|
|
109
|
-
onPurchase: purchasePackage,
|
|
110
|
-
onRestore: restorePurchase,
|
|
111
|
-
onClose: handleClose,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
if (shouldShowPostOnboarding) {
|
|
115
|
-
markPaywallShown();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (showPaywall) {
|
|
119
|
-
closePaywall();
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
hasNavigatedRef.current = false;
|
|
123
|
-
}
|
|
124
|
-
}, [
|
|
125
|
-
isNavReady,
|
|
126
|
-
isLocalizationReady,
|
|
127
|
-
isOnboardingComplete,
|
|
128
|
-
showPostOnboardingPaywall,
|
|
129
|
-
paywallShown,
|
|
130
|
-
isAuthModalOpen,
|
|
131
|
-
isPremium,
|
|
132
|
-
showPaywall,
|
|
133
|
-
navigation,
|
|
134
|
-
translations,
|
|
135
|
-
legalUrls,
|
|
136
|
-
features,
|
|
137
|
-
heroImage,
|
|
138
|
-
packages,
|
|
139
|
-
markPaywallShown,
|
|
140
|
-
closePaywall,
|
|
141
|
-
bestValueIdentifier,
|
|
142
|
-
creditsLabel,
|
|
143
|
-
credits,
|
|
144
|
-
isSyncing,
|
|
145
|
-
purchasePackage,
|
|
146
|
-
restorePurchase,
|
|
147
|
-
handleClose,
|
|
148
|
-
disableNavigation,
|
|
149
|
-
]);
|
|
150
|
-
|
|
151
|
-
const completeOnboarding = useSubscriptionFlowStore((state) => state.completeOnboarding);
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
flowState: {
|
|
155
|
-
isOnboardingComplete,
|
|
156
|
-
showPostOnboardingPaywall,
|
|
157
|
-
paywallShown,
|
|
158
|
-
isAuthModalOpen,
|
|
159
|
-
showFeedback
|
|
160
|
-
},
|
|
161
|
-
markPaywallShown,
|
|
162
|
-
closePostOnboardingPaywall,
|
|
163
|
-
completeOnboarding,
|
|
164
|
-
setShowFeedback
|
|
165
|
-
};
|
|
166
|
-
}
|