@umituz/react-native-subscription 2.40.13 → 2.40.15
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.40.
|
|
3
|
+
"version": "2.40.15",
|
|
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",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"url": "git+https://github.com/umituz/react-native-subscription.git"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
+
"@react-native-async-storage/async-storage": ">=2.0.0",
|
|
41
42
|
"@tanstack/react-query": ">=5.0.0",
|
|
42
43
|
"@umituz/react-native-auth": "*",
|
|
43
44
|
"expo": ">=54.0.0",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useEffect, useCallback, useRef } from "react";
|
|
2
|
+
import { usePremium } from "../../subscription/presentation/usePremium";
|
|
3
|
+
import { useSubscriptionFlow } from "../../subscription/presentation/useSubscriptionFlow";
|
|
4
|
+
import { usePaywallVisibility } from "../../subscription/presentation/usePaywallVisibility";
|
|
5
|
+
import { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
|
|
6
|
+
|
|
7
|
+
export interface PaywallOrchestratorOptions {
|
|
8
|
+
navigation: any;
|
|
9
|
+
translations: PaywallTranslations;
|
|
10
|
+
features: SubscriptionFeature[];
|
|
11
|
+
legalUrls: PaywallLegalUrls;
|
|
12
|
+
heroImage: any;
|
|
13
|
+
isNavReady?: boolean;
|
|
14
|
+
isLocalizationReady?: boolean;
|
|
15
|
+
onAuthRequired?: () => void;
|
|
16
|
+
onPurchaseSuccess?: () => void;
|
|
17
|
+
bestValueIdentifier?: string;
|
|
18
|
+
creditsLabel?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* High-level orchestrator for Paywall navigation.
|
|
23
|
+
* Handles automatic triggers (post-onboarding) and manual triggers (showPaywall state).
|
|
24
|
+
* Centralizes handlers for success, close, and feedback triggers.
|
|
25
|
+
*/
|
|
26
|
+
export function usePaywallOrchestrator({
|
|
27
|
+
navigation,
|
|
28
|
+
translations,
|
|
29
|
+
features,
|
|
30
|
+
legalUrls,
|
|
31
|
+
heroImage,
|
|
32
|
+
isNavReady = true,
|
|
33
|
+
isLocalizationReady = true,
|
|
34
|
+
onAuthRequired,
|
|
35
|
+
onPurchaseSuccess,
|
|
36
|
+
bestValueIdentifier = "yearly",
|
|
37
|
+
creditsLabel,
|
|
38
|
+
}: PaywallOrchestratorOptions) {
|
|
39
|
+
const {
|
|
40
|
+
isPremium,
|
|
41
|
+
packages,
|
|
42
|
+
purchasePackage,
|
|
43
|
+
restorePurchase
|
|
44
|
+
} = usePremium();
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
state,
|
|
48
|
+
markPaywallShown,
|
|
49
|
+
closePostOnboardingPaywall,
|
|
50
|
+
setShowFeedback
|
|
51
|
+
} = useSubscriptionFlow();
|
|
52
|
+
|
|
53
|
+
const { showPaywall, closePaywall } = usePaywallVisibility();
|
|
54
|
+
const purchasedRef = useRef(false);
|
|
55
|
+
|
|
56
|
+
const handleClose = useCallback(async () => {
|
|
57
|
+
await closePostOnboardingPaywall();
|
|
58
|
+
closePaywall();
|
|
59
|
+
|
|
60
|
+
// Trigger feedback if user declined and isn't premium
|
|
61
|
+
if (!isPremium && !purchasedRef.current) {
|
|
62
|
+
setTimeout(() => setShowFeedback(true), 300);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
purchasedRef.current = false;
|
|
66
|
+
|
|
67
|
+
if (navigation.canGoBack()) {
|
|
68
|
+
navigation.goBack();
|
|
69
|
+
}
|
|
70
|
+
}, [closePostOnboardingPaywall, closePaywall, isPremium, navigation, setShowFeedback]);
|
|
71
|
+
|
|
72
|
+
const handleSuccess = useCallback(async () => {
|
|
73
|
+
purchasedRef.current = true;
|
|
74
|
+
await markPaywallShown();
|
|
75
|
+
await closePostOnboardingPaywall();
|
|
76
|
+
|
|
77
|
+
onPurchaseSuccess?.();
|
|
78
|
+
|
|
79
|
+
if (navigation.canGoBack()) {
|
|
80
|
+
navigation.goBack();
|
|
81
|
+
}
|
|
82
|
+
}, [markPaywallShown, closePostOnboardingPaywall, onPurchaseSuccess, navigation]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!isNavReady || !isLocalizationReady) return;
|
|
86
|
+
|
|
87
|
+
const shouldShowPostOnboarding =
|
|
88
|
+
state.isOnboardingComplete &&
|
|
89
|
+
state.showPostOnboardingPaywall &&
|
|
90
|
+
!state.paywallShown &&
|
|
91
|
+
!state.isAuthModalOpen &&
|
|
92
|
+
!isPremium;
|
|
93
|
+
|
|
94
|
+
const shouldShowManual = showPaywall && !isPremium && !state.isAuthModalOpen;
|
|
95
|
+
|
|
96
|
+
if (shouldShowPostOnboarding || shouldShowManual) {
|
|
97
|
+
navigation.navigate("PaywallScreen", {
|
|
98
|
+
onClose: handleClose,
|
|
99
|
+
translations,
|
|
100
|
+
legalUrls,
|
|
101
|
+
features,
|
|
102
|
+
bestValueIdentifier,
|
|
103
|
+
creditsLabel,
|
|
104
|
+
onAuthRequired,
|
|
105
|
+
onPurchaseSuccess: handleSuccess,
|
|
106
|
+
heroImage,
|
|
107
|
+
source: shouldShowPostOnboarding ? "onboarding" : "manual",
|
|
108
|
+
packages,
|
|
109
|
+
onPurchase: purchasePackage,
|
|
110
|
+
onRestore: restorePurchase,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (shouldShowPostOnboarding) {
|
|
114
|
+
markPaywallShown();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (showPaywall) {
|
|
118
|
+
closePaywall();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}, [
|
|
122
|
+
isNavReady,
|
|
123
|
+
isLocalizationReady,
|
|
124
|
+
state.isOnboardingComplete,
|
|
125
|
+
state.showPostOnboardingPaywall,
|
|
126
|
+
state.paywallShown,
|
|
127
|
+
state.isAuthModalOpen,
|
|
128
|
+
isPremium,
|
|
129
|
+
showPaywall,
|
|
130
|
+
navigation,
|
|
131
|
+
handleClose,
|
|
132
|
+
handleSuccess,
|
|
133
|
+
translations,
|
|
134
|
+
legalUrls,
|
|
135
|
+
features,
|
|
136
|
+
heroImage,
|
|
137
|
+
packages,
|
|
138
|
+
purchasePackage,
|
|
139
|
+
restorePurchase,
|
|
140
|
+
markPaywallShown,
|
|
141
|
+
closePaywall,
|
|
142
|
+
bestValueIdentifier,
|
|
143
|
+
creditsLabel,
|
|
144
|
+
onAuthRequired
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
isPremium,
|
|
149
|
+
packages,
|
|
150
|
+
flowState: state,
|
|
151
|
+
markPaywallShown,
|
|
152
|
+
closePostOnboardingPaywall,
|
|
153
|
+
setShowFeedback
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PaywallTranslations } from "../entities/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates standardized paywall translations from a translation function.
|
|
5
|
+
* Matches the structure used across the App Factory ecosystem.
|
|
6
|
+
*/
|
|
7
|
+
export const createPaywallTranslations = (
|
|
8
|
+
t: (key: string) => string,
|
|
9
|
+
overrides?: Partial<PaywallTranslations>
|
|
10
|
+
): PaywallTranslations => ({
|
|
11
|
+
title: t("premium.title"),
|
|
12
|
+
subtitle: t("premium.subtitle"),
|
|
13
|
+
loadingText: t("paywall.loading"),
|
|
14
|
+
emptyText: t("paywall.empty"),
|
|
15
|
+
purchaseButtonText: t("paywall.purchase"),
|
|
16
|
+
restoreButtonText: t("paywall.restore"),
|
|
17
|
+
processingText: t("paywall.processing"),
|
|
18
|
+
privacyText: t("auth.privacyPolicy"),
|
|
19
|
+
termsOfServiceText: t("auth.termsOfService"),
|
|
20
|
+
bestValueBadgeText: t("paywall.bestValue"),
|
|
21
|
+
featuresTitle: t("paywall.featuresTitle"),
|
|
22
|
+
plansTitle: t("paywall.plansTitle"),
|
|
23
|
+
...overrides,
|
|
24
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Flow Store
|
|
3
|
+
* Manages the high-level application flow: Splash -> Onboarding -> Paywall -> Main App.
|
|
4
|
+
* Uses @umituz/react-native-design-system's storage utility for standardized persistence.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DeviceEventEmitter } from "react-native";
|
|
8
|
+
import { createStore } from "@umituz/react-native-design-system/storage";
|
|
9
|
+
|
|
10
|
+
export interface SubscriptionFlowState {
|
|
11
|
+
isInitialized: boolean;
|
|
12
|
+
isOnboardingComplete: boolean;
|
|
13
|
+
showPostOnboardingPaywall: boolean;
|
|
14
|
+
showFeedback: boolean;
|
|
15
|
+
paywallShown: boolean;
|
|
16
|
+
isAuthModalOpen: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SubscriptionFlowActions {
|
|
20
|
+
completeOnboarding: () => Promise<void>;
|
|
21
|
+
closePostOnboardingPaywall: () => Promise<void>;
|
|
22
|
+
closeFeedback: () => void;
|
|
23
|
+
setAuthModalOpen: (open: boolean) => void;
|
|
24
|
+
markPaywallShown: () => Promise<void>;
|
|
25
|
+
setShowFeedback: (show: boolean) => void;
|
|
26
|
+
resetFlow: () => Promise<void>;
|
|
27
|
+
setInitialized: (initialized: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type SubscriptionFlowStore = SubscriptionFlowState & SubscriptionFlowActions;
|
|
31
|
+
|
|
32
|
+
const initialState: SubscriptionFlowState = {
|
|
33
|
+
isInitialized: false,
|
|
34
|
+
isOnboardingComplete: false,
|
|
35
|
+
showPostOnboardingPaywall: false,
|
|
36
|
+
showFeedback: false,
|
|
37
|
+
paywallShown: false,
|
|
38
|
+
isAuthModalOpen: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const useSubscriptionFlowStore = createStore<SubscriptionFlowState, SubscriptionFlowActions>({
|
|
42
|
+
name: "subscription-flow-storage",
|
|
43
|
+
initialState,
|
|
44
|
+
persist: true,
|
|
45
|
+
// Only persist onboarding and paywall status, other states are transient
|
|
46
|
+
partialize: (state) => ({
|
|
47
|
+
isOnboardingComplete: state.isOnboardingComplete,
|
|
48
|
+
paywallShown: state.paywallShown,
|
|
49
|
+
}),
|
|
50
|
+
onRehydrate: (state) => {
|
|
51
|
+
state.setInitialized(true);
|
|
52
|
+
},
|
|
53
|
+
actions: (set) => ({
|
|
54
|
+
completeOnboarding: async () => {
|
|
55
|
+
set({
|
|
56
|
+
isOnboardingComplete: true,
|
|
57
|
+
showPostOnboardingPaywall: true,
|
|
58
|
+
});
|
|
59
|
+
DeviceEventEmitter.emit("onboarding-complete");
|
|
60
|
+
},
|
|
61
|
+
closePostOnboardingPaywall: async () => {
|
|
62
|
+
set({
|
|
63
|
+
showPostOnboardingPaywall: false,
|
|
64
|
+
paywallShown: true,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
closeFeedback: () => set({ showFeedback: false }),
|
|
68
|
+
setAuthModalOpen: (open: boolean) => set({ isAuthModalOpen: open }),
|
|
69
|
+
setShowFeedback: (show: boolean) => set({ showFeedback: show }),
|
|
70
|
+
markPaywallShown: async () => set({ paywallShown: true }),
|
|
71
|
+
setInitialized: (initialized: boolean) => set({ isInitialized: initialized }),
|
|
72
|
+
resetFlow: async () => {
|
|
73
|
+
set({
|
|
74
|
+
isOnboardingComplete: false,
|
|
75
|
+
showPostOnboardingPaywall: false,
|
|
76
|
+
showFeedback: false,
|
|
77
|
+
paywallShown: false,
|
|
78
|
+
isAuthModalOpen: false,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Hook for backward compatibility and easier consumption.
|
|
86
|
+
* Provides a unified object structure matching the previous implementation.
|
|
87
|
+
*/
|
|
88
|
+
export function useSubscriptionFlow(_userId?: string) {
|
|
89
|
+
const store = useSubscriptionFlowStore();
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
state: store,
|
|
93
|
+
completeOnboarding: store.completeOnboarding,
|
|
94
|
+
closePostOnboardingPaywall: store.closePostOnboardingPaywall,
|
|
95
|
+
closeFeedback: store.closeFeedback,
|
|
96
|
+
setAuthModalOpen: store.setAuthModalOpen,
|
|
97
|
+
markPaywallShown: store.markPaywallShown,
|
|
98
|
+
setShowFeedback: store.setShowFeedback,
|
|
99
|
+
resetFlow: store.resetFlow,
|
|
100
|
+
};
|
|
101
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,8 @@ export { useDeductCredit } from "./domains/credits/presentation/deduct-credit/us
|
|
|
45
45
|
export { useFeatureGate } from "./domains/subscription/presentation/useFeatureGate";
|
|
46
46
|
export { usePaywallVisibility, paywallControl } from "./domains/subscription/presentation/usePaywallVisibility";
|
|
47
47
|
export { usePremium } from "./domains/subscription/presentation/usePremium";
|
|
48
|
+
export { useSubscriptionFlow, useSubscriptionFlowStore } from "./domains/subscription/presentation/useSubscriptionFlow";
|
|
49
|
+
export type { SubscriptionFlowState, SubscriptionFlowActions, SubscriptionFlowStore } from "./domains/subscription/presentation/useSubscriptionFlow";
|
|
48
50
|
export { useSubscriptionStatus } from "./domains/subscription/presentation/useSubscriptionStatus";
|
|
49
51
|
export * from "./domains/subscription/presentation/useSubscriptionStatus.types";
|
|
50
52
|
export * from "./presentation/hooks/feedback/usePaywallFeedback";
|
|
@@ -65,6 +67,8 @@ export type {
|
|
|
65
67
|
} from "./domains/subscription/presentation/screens/SubscriptionDetailScreen.types";
|
|
66
68
|
export { PaywallScreen } from "./domains/paywall/components/PaywallScreen";
|
|
67
69
|
export type { PaywallScreenProps } from "./domains/paywall/components/PaywallScreen.types";
|
|
70
|
+
export { usePaywallOrchestrator } from "./domains/paywall/hooks/usePaywallOrchestrator";
|
|
71
|
+
export type { PaywallOrchestratorOptions } from "./domains/paywall/hooks/usePaywallOrchestrator";
|
|
68
72
|
|
|
69
73
|
export type {
|
|
70
74
|
CreditType,
|
|
@@ -86,8 +90,9 @@ export { toDate, toISOString, toTimestamp, getCurrentISOString } from "./shared/
|
|
|
86
90
|
// Credits Query Keys
|
|
87
91
|
export { creditsQueryKeys } from "./domains/credits/presentation/creditsQueryKeys";
|
|
88
92
|
|
|
89
|
-
// Paywall Types
|
|
90
|
-
export type { PaywallTranslations, PaywallLegalUrls } from "./domains/paywall/entities/types";
|
|
93
|
+
// Paywall Types & Utils
|
|
94
|
+
export type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "./domains/paywall/entities/types";
|
|
95
|
+
export { createPaywallTranslations } from "./domains/paywall/utils/paywallTranslationUtils";
|
|
91
96
|
|
|
92
97
|
// Purchase Loading Overlay
|
|
93
98
|
export { PurchaseLoadingOverlay } from "./domains/subscription/presentation/components/overlay/PurchaseLoadingOverlay";
|