@umituz/react-native-subscription 2.27.95 → 2.27.97
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/core/Credits.ts +3 -11
- package/src/domains/credits/infrastructure/CreditsRepository.ts +28 -1
- package/src/domains/paywall/components/PaywallContainer.tsx +17 -1
- package/src/domains/paywall/components/PaywallContainer.types.ts +2 -1
- package/src/domains/paywall/hooks/usePaywallActions.ts +1 -1
- package/src/domains/subscription/application/SubscriptionInitializer.ts +1 -1
- package/src/domains/subscription/application/SubscriptionSyncService.ts +32 -5
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
- package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +32 -12
- package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +4 -4
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +1 -7
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -2
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +5 -4
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +20 -7
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +1 -1
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +55 -16
- package/src/domains/subscription/presentation/screens/components/CreditsList.tsx +14 -1
- package/src/domains/subscription/presentation/screens/components/DevTestSection.tsx +10 -2
- package/src/domains/subscription/presentation/screens/components/SubscriptionActions.tsx +6 -1
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +20 -1
- package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +13 -1
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +1 -1
- package/src/domains/subscription/presentation/useFeatureGate.ts +11 -7
- package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -5
- package/src/init/index.ts +0 -3
- package/src/presentation/hooks/index.ts +0 -4
- package/src/shared/infrastructure/SubscriptionEventBus.ts +27 -0
- package/src/types/i18next.d.ts +2 -0
- package/src/utils/packageTypeDetector.ts +0 -4
- package/src/domains/subscription/presentation/types/README.md +0 -22
- package/src/domains/subscription/presentation/types/SubscriptionDetailTypes.ts +0 -153
- package/src/domains/subscription/presentation/types/SubscriptionSettingsTypes.ts +0 -74
- package/src/domains/subscription/presentation/useAuthSubscriptionSync.ts +0 -63
- package/src/domains/subscription/presentation/usePremiumGate.ts +0 -84
- package/src/domains/subscription/presentation/useSavedPurchaseAutoExecution.ts +0 -148
- package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.ts +0 -115
- package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.utils.ts +0 -57
|
@@ -135,9 +135,9 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
135
135
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
136
136
|
console.log("[useFeatureGate] requireFeature", {
|
|
137
137
|
isAuthenticated,
|
|
138
|
-
hasSubscription,
|
|
138
|
+
hasSubscription: hasSubscriptionRef.current,
|
|
139
139
|
creditBalance: creditBalanceRef.current,
|
|
140
|
-
requiredCredits,
|
|
140
|
+
requiredCredits: requiredCreditsRef.current,
|
|
141
141
|
isCreditsLoaded,
|
|
142
142
|
});
|
|
143
143
|
}
|
|
@@ -155,25 +155,29 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
// Use ref values to avoid stale closure
|
|
159
|
+
const currentHasSubscription = hasSubscriptionRef.current;
|
|
160
|
+
const currentBalance = creditBalanceRef.current;
|
|
161
|
+
const currentRequiredCredits = requiredCreditsRef.current;
|
|
162
|
+
|
|
163
|
+
if (currentHasSubscription) {
|
|
159
164
|
action();
|
|
160
165
|
return;
|
|
161
166
|
}
|
|
162
167
|
|
|
163
|
-
|
|
164
|
-
if (currentBalance < requiredCredits) {
|
|
168
|
+
if (currentBalance < currentRequiredCredits) {
|
|
165
169
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
166
170
|
console.log("[useFeatureGate] No credits, showing paywall");
|
|
167
171
|
}
|
|
168
172
|
pendingActionRef.current = action;
|
|
169
173
|
isWaitingForPurchaseRef.current = true;
|
|
170
|
-
|
|
174
|
+
onShowPaywallRef.current(currentRequiredCredits);
|
|
171
175
|
return;
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
action();
|
|
175
179
|
},
|
|
176
|
-
[isAuthenticated,
|
|
180
|
+
[isAuthenticated, onShowAuthModal, isCreditsLoaded]
|
|
177
181
|
);
|
|
178
182
|
|
|
179
183
|
const hasCredits = creditBalance >= requiredCredits;
|
|
@@ -48,11 +48,7 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
|
|
51
|
-
|
|
52
|
-
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
53
|
-
refetchOnMount: "always",
|
|
54
|
-
refetchOnWindowFocus: true,
|
|
55
|
-
refetchOnReconnect: true,
|
|
51
|
+
|
|
56
52
|
});
|
|
57
53
|
|
|
58
54
|
return {
|
package/src/init/index.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
export * from "../../domains/subscription/presentation/useAuthAwarePurchase";
|
|
2
|
-
export * from "../../domains/subscription/presentation/useAuthSubscriptionSync";
|
|
3
|
-
export * from "../../domains/subscription/presentation/useSavedPurchaseAutoExecution";
|
|
4
2
|
export * from "../../domains/credits/presentation/useCredits";
|
|
5
3
|
export * from "../../domains/credits/presentation/useDeductCredit";
|
|
6
4
|
export * from "../../domains/subscription/presentation/useFeatureGate";
|
|
7
5
|
export * from "../../domains/subscription/presentation/usePaywallVisibility";
|
|
8
6
|
export * from "../../domains/subscription/presentation/usePremium";
|
|
9
|
-
export * from "../../domains/subscription/presentation/usePremiumGate";
|
|
10
|
-
export * from "../../domains/subscription/presentation/useSubscriptionSettingsConfig";
|
|
11
7
|
export * from "../../domains/subscription/presentation/useSubscriptionStatus";
|
|
12
8
|
export * from "./feedback/usePaywallFeedback";
|
|
13
9
|
export * from "./feedback/useFeedbackSubmit";
|
|
@@ -28,6 +28,11 @@ export class SubscriptionEventBus {
|
|
|
28
28
|
const listeners = this.listeners[event];
|
|
29
29
|
if (listeners) {
|
|
30
30
|
this.listeners[event] = listeners.filter(l => l !== callback);
|
|
31
|
+
|
|
32
|
+
// Clean up empty event arrays to prevent memory leak
|
|
33
|
+
if (this.listeners[event].length === 0) {
|
|
34
|
+
delete this.listeners[event];
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
};
|
|
33
38
|
}
|
|
@@ -42,6 +47,28 @@ export class SubscriptionEventBus {
|
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clear all listeners for a specific event or all events
|
|
53
|
+
* Useful for cleanup during testing or app state reset
|
|
54
|
+
*/
|
|
55
|
+
clear(event?: string): void {
|
|
56
|
+
if (event) {
|
|
57
|
+
delete this.listeners[event];
|
|
58
|
+
} else {
|
|
59
|
+
this.listeners = {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get listener count for debugging
|
|
65
|
+
*/
|
|
66
|
+
getListenerCount(event?: string): number {
|
|
67
|
+
if (event) {
|
|
68
|
+
return this.listeners[event]?.length ?? 0;
|
|
69
|
+
}
|
|
70
|
+
return Object.values(this.listeners).reduce((total, arr) => total + arr.length, 0);
|
|
71
|
+
}
|
|
45
72
|
}
|
|
46
73
|
|
|
47
74
|
export const subscriptionEventBus = SubscriptionEventBus.getInstance();
|
|
@@ -7,10 +7,6 @@ import { PACKAGE_TYPE, type PackageType } from "../domains/subscription/core/Sub
|
|
|
7
7
|
|
|
8
8
|
export type SubscriptionPackageType = PackageType;
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Check if identifier is a credit package (consumable purchase)
|
|
12
|
-
* Credit packages use a different system and don't need type detection
|
|
13
|
-
*/
|
|
14
10
|
/**
|
|
15
11
|
* Check if identifier is a credit package (consumable purchase)
|
|
16
12
|
* Credit packages use a different system and don't need type detection
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Presentation Types
|
|
2
|
-
|
|
3
|
-
TypeScript type definitions and interfaces for the presentation layer.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This directory contains all type definitions used by presentation components and hooks.
|
|
8
|
-
|
|
9
|
-
## Contents
|
|
10
|
-
|
|
11
|
-
### Subscription Types
|
|
12
|
-
|
|
13
|
-
- **SubscriptionSettingsTypes.ts** - Configuration types for subscription settings UI
|
|
14
|
-
- **PaywallTypes.ts** - Paywall component types
|
|
15
|
-
- **SubscriptionTypes.ts** - General subscription UI types
|
|
16
|
-
|
|
17
|
-
## Key Exports
|
|
18
|
-
|
|
19
|
-
## Related
|
|
20
|
-
|
|
21
|
-
- [Hooks](../hooks/README.md)
|
|
22
|
-
- [Components](../components/README.md)
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subscription Detail Types
|
|
3
|
-
* Type definitions for subscription detail screen and components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { SubscriptionStatusType } from "../../core/SubscriptionStatus";
|
|
7
|
-
import type { CreditInfo } from "../components/details/PremiumDetailsCardTypes";
|
|
8
|
-
|
|
9
|
-
export type { SubscriptionStatusType, CreditInfo };
|
|
10
|
-
|
|
11
|
-
/** Translation strings for subscription detail screen */
|
|
12
|
-
export interface SubscriptionDetailTranslations {
|
|
13
|
-
title: string;
|
|
14
|
-
statusLabel: string;
|
|
15
|
-
statusActive: string;
|
|
16
|
-
statusExpired: string;
|
|
17
|
-
statusInactive: string;
|
|
18
|
-
statusCanceled: string;
|
|
19
|
-
/** Free status label */
|
|
20
|
-
statusFree: string;
|
|
21
|
-
/** Trial status label (defaults to statusActive if not provided) */
|
|
22
|
-
statusTrial?: string;
|
|
23
|
-
/** Trial canceled status label (defaults to statusCanceled if not provided) */
|
|
24
|
-
statusTrialCanceled?: string;
|
|
25
|
-
expiresLabel: string;
|
|
26
|
-
purchasedLabel: string;
|
|
27
|
-
lifetimeLabel: string;
|
|
28
|
-
creditsTitle: string;
|
|
29
|
-
remainingLabel: string;
|
|
30
|
-
usageTitle?: string;
|
|
31
|
-
manageButton: string;
|
|
32
|
-
upgradeButton: string;
|
|
33
|
-
creditsResetInfo?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Dev test action callbacks */
|
|
37
|
-
export interface DevTestActions {
|
|
38
|
-
onTestRenewal: () => Promise<void>;
|
|
39
|
-
onCheckCredits: () => void;
|
|
40
|
-
onTestDuplicate: () => Promise<void>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Dev tools configuration */
|
|
44
|
-
export interface DevToolsConfig {
|
|
45
|
-
actions: DevTestActions;
|
|
46
|
-
title?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Benefit item for upgrade prompt */
|
|
50
|
-
export interface UpgradeBenefit {
|
|
51
|
-
icon?: string;
|
|
52
|
-
text: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Upgrade prompt configuration */
|
|
56
|
-
export interface UpgradePromptConfig {
|
|
57
|
-
title: string;
|
|
58
|
-
subtitle?: string;
|
|
59
|
-
benefits?: UpgradeBenefit[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Display flags - centralized UI visibility control */
|
|
63
|
-
export interface SubscriptionDisplayFlags {
|
|
64
|
-
showHeader: boolean;
|
|
65
|
-
showCredits: boolean;
|
|
66
|
-
showUpgradePrompt: boolean;
|
|
67
|
-
showExpirationDate: boolean;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Configuration for subscription detail screen */
|
|
71
|
-
export interface SubscriptionDetailConfig {
|
|
72
|
-
statusType: SubscriptionStatusType;
|
|
73
|
-
isPremium: boolean;
|
|
74
|
-
display: SubscriptionDisplayFlags;
|
|
75
|
-
expirationDate?: string | null;
|
|
76
|
-
purchaseDate?: string | null;
|
|
77
|
-
isLifetime?: boolean;
|
|
78
|
-
daysRemaining?: number | null;
|
|
79
|
-
willRenew?: boolean;
|
|
80
|
-
credits?: CreditInfo[];
|
|
81
|
-
translations: SubscriptionDetailTranslations;
|
|
82
|
-
onManageSubscription?: () => void;
|
|
83
|
-
onUpgrade?: () => void;
|
|
84
|
-
devTools?: DevToolsConfig;
|
|
85
|
-
upgradePrompt?: UpgradePromptConfig;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Props for subscription detail screen */
|
|
89
|
-
export interface SubscriptionDetailScreenProps {
|
|
90
|
-
config: SubscriptionDetailConfig;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Props for subscription header component */
|
|
94
|
-
export interface SubscriptionHeaderProps {
|
|
95
|
-
statusType: SubscriptionStatusType;
|
|
96
|
-
showExpirationDate: boolean;
|
|
97
|
-
isLifetime?: boolean;
|
|
98
|
-
expirationDate?: string | null;
|
|
99
|
-
purchaseDate?: string | null;
|
|
100
|
-
daysRemaining?: number | null;
|
|
101
|
-
translations: Pick<
|
|
102
|
-
SubscriptionDetailTranslations,
|
|
103
|
-
| "title"
|
|
104
|
-
| "statusLabel"
|
|
105
|
-
| "statusActive"
|
|
106
|
-
| "statusExpired"
|
|
107
|
-
| "statusInactive"
|
|
108
|
-
| "statusCanceled"
|
|
109
|
-
| "statusTrial"
|
|
110
|
-
| "statusTrialCanceled"
|
|
111
|
-
| "expiresLabel"
|
|
112
|
-
| "purchasedLabel"
|
|
113
|
-
| "lifetimeLabel"
|
|
114
|
-
> & { statusFree: string };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Props for credits list component */
|
|
118
|
-
export interface CreditsListProps {
|
|
119
|
-
credits: CreditInfo[];
|
|
120
|
-
title?: string;
|
|
121
|
-
description?: string;
|
|
122
|
-
remainingLabel?: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Props for credit row component */
|
|
126
|
-
export interface CreditRowProps {
|
|
127
|
-
label: string;
|
|
128
|
-
current: number;
|
|
129
|
-
total: number;
|
|
130
|
-
remainingLabel?: string;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Props for subscription actions component */
|
|
134
|
-
export interface SubscriptionActionsProps {
|
|
135
|
-
isPremium: boolean;
|
|
136
|
-
upgradeButtonLabel?: string;
|
|
137
|
-
onUpgrade?: () => void;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Props for dev test section */
|
|
141
|
-
export interface DevTestSectionProps {
|
|
142
|
-
actions: DevTestActions;
|
|
143
|
-
title?: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Props for upgrade prompt component */
|
|
147
|
-
export interface UpgradePromptProps {
|
|
148
|
-
title: string;
|
|
149
|
-
subtitle?: string;
|
|
150
|
-
benefits?: UpgradeBenefit[];
|
|
151
|
-
upgradeButtonLabel?: string;
|
|
152
|
-
onUpgrade?: () => void;
|
|
153
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subscription Settings Types
|
|
3
|
-
* Type definitions for subscription settings configuration
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { SubscriptionStatusType } from "../../core/SubscriptionConstants";
|
|
7
|
-
import type {
|
|
8
|
-
SubscriptionDetailConfig,
|
|
9
|
-
UpgradePromptConfig,
|
|
10
|
-
} from "./SubscriptionDetailTypes";
|
|
11
|
-
|
|
12
|
-
export type { SubscriptionStatusType, UpgradePromptConfig };
|
|
13
|
-
|
|
14
|
-
/** Configuration for settings list item */
|
|
15
|
-
export interface SubscriptionSettingsItemConfig {
|
|
16
|
-
title: string;
|
|
17
|
-
description?: string;
|
|
18
|
-
isPremium: boolean;
|
|
19
|
-
statusLabel: string;
|
|
20
|
-
icon?: string;
|
|
21
|
-
onPress?: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Complete subscription settings configuration */
|
|
25
|
-
export interface SubscriptionSettingsConfig {
|
|
26
|
-
/** Whether subscription section should be shown */
|
|
27
|
-
enabled: boolean;
|
|
28
|
-
/** Config for settings list item */
|
|
29
|
-
settingsItem: SubscriptionSettingsItemConfig;
|
|
30
|
-
/** Config for detail screen */
|
|
31
|
-
sectionConfig: SubscriptionDetailConfig;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Translation strings for subscription settings */
|
|
35
|
-
export interface SubscriptionSettingsTranslations {
|
|
36
|
-
/** Settings item title */
|
|
37
|
-
title: string;
|
|
38
|
-
/** Settings item description */
|
|
39
|
-
description: string;
|
|
40
|
-
/** Status labels */
|
|
41
|
-
statusActive: string;
|
|
42
|
-
statusInactive: string;
|
|
43
|
-
statusExpired: string;
|
|
44
|
-
statusCanceled: string;
|
|
45
|
-
/** Trial status label (defaults to statusActive if not provided) */
|
|
46
|
-
statusTrial?: string;
|
|
47
|
-
/** Trial canceled status label (defaults to statusCanceled if not provided) */
|
|
48
|
-
statusTrialCanceled?: string;
|
|
49
|
-
/** Detail screen translations */
|
|
50
|
-
statusLabel: string;
|
|
51
|
-
expiresLabel: string;
|
|
52
|
-
purchasedLabel: string;
|
|
53
|
-
lifetimeLabel: string;
|
|
54
|
-
creditsTitle: string;
|
|
55
|
-
remainingLabel: string;
|
|
56
|
-
manageButton: string;
|
|
57
|
-
upgradeButton: string;
|
|
58
|
-
/** Credit label (e.g., "Credits") */
|
|
59
|
-
creditsLabel: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Parameters for useSubscriptionSettingsConfig hook */
|
|
63
|
-
export interface UseSubscriptionSettingsConfigParams {
|
|
64
|
-
/** User ID (required for credits lookup) */
|
|
65
|
-
userId?: string;
|
|
66
|
-
/** Whether user is anonymous */
|
|
67
|
-
isAnonymous?: boolean;
|
|
68
|
-
/** Translation strings */
|
|
69
|
-
translations: SubscriptionSettingsTranslations;
|
|
70
|
-
/** Fixed credit limit (if not available in UserCredits) */
|
|
71
|
-
creditLimit?: number;
|
|
72
|
-
/** Upgrade prompt configuration for free users */
|
|
73
|
-
upgradePrompt?: UpgradePromptConfig;
|
|
74
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useAuthSubscriptionSync Hook
|
|
3
|
-
* Single source of truth for RevenueCat initialization
|
|
4
|
-
* Handles initial setup and auth state transitions
|
|
5
|
-
* Generic implementation for 100+ apps with auth provider abstraction
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useEffect, useRef, useCallback } from "react";
|
|
9
|
-
|
|
10
|
-
export interface AuthSubscriptionSyncConfig {
|
|
11
|
-
/** Function to subscribe to auth state changes - returns unsubscribe function */
|
|
12
|
-
onAuthStateChanged: (callback: (userId: string | null) => void) => () => void;
|
|
13
|
-
/** Function to initialize subscription for a user */
|
|
14
|
-
initializeSubscription: (userId: string) => Promise<void>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useAuthSubscriptionSync(
|
|
18
|
-
config: AuthSubscriptionSyncConfig,
|
|
19
|
-
): void {
|
|
20
|
-
const { onAuthStateChanged, initializeSubscription } = config;
|
|
21
|
-
const previousUserIdRef = useRef<string | null>(null);
|
|
22
|
-
const isInitializedRef = useRef(false);
|
|
23
|
-
|
|
24
|
-
const initialize = useCallback(
|
|
25
|
-
async (userId: string) => {
|
|
26
|
-
await initializeSubscription(userId);
|
|
27
|
-
},
|
|
28
|
-
[initializeSubscription],
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
const unsubscribe = onAuthStateChanged(async (userId: string | null) => {
|
|
33
|
-
if (!userId) {
|
|
34
|
-
previousUserIdRef.current = null;
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const previousUserId = previousUserIdRef.current;
|
|
39
|
-
|
|
40
|
-
if (userId === previousUserId) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
if (previousUserId && previousUserId !== userId) {
|
|
46
|
-
await initialize(userId);
|
|
47
|
-
} else if (!isInitializedRef.current) {
|
|
48
|
-
await initialize(userId);
|
|
49
|
-
isInitializedRef.current = true;
|
|
50
|
-
}
|
|
51
|
-
} catch (error) {
|
|
52
|
-
// Log error for debugging but don't crash the auth flow
|
|
53
|
-
if (__DEV__) {
|
|
54
|
-
console.error('[useAuthSubscriptionSync] Initialization failed:', error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
previousUserIdRef.current = userId;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return () => unsubscribe();
|
|
62
|
-
}, [onAuthStateChanged, initialize]);
|
|
63
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePremiumGate Hook
|
|
3
|
-
*
|
|
4
|
-
* Simplified hook for premium-only apps (no credit system).
|
|
5
|
-
* Provides screen-level and action-level premium gates.
|
|
6
|
-
*
|
|
7
|
-
* @example Screen-Level Gate
|
|
8
|
-
* ```tsx
|
|
9
|
-
* const { isPremium, requireScreen } = usePremiumGate();
|
|
10
|
-
*
|
|
11
|
-
* useEffect(() => {
|
|
12
|
-
* requireScreen(); // Auto-opens paywall if not premium
|
|
13
|
-
* }, [requireScreen]);
|
|
14
|
-
*
|
|
15
|
-
* if (!isPremium) return null;
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* @example Action-Level Gate
|
|
19
|
-
* ```tsx
|
|
20
|
-
* const { requirePremium } = usePremiumGate();
|
|
21
|
-
*
|
|
22
|
-
* const handleAction = () => {
|
|
23
|
-
* requirePremium(() => {
|
|
24
|
-
* // Action code here
|
|
25
|
-
* });
|
|
26
|
-
* };
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { useCallback } from "react";
|
|
31
|
-
import { useSubscriptionStatus } from "./useSubscriptionStatus";
|
|
32
|
-
import { paywallControl } from "./usePaywallVisibility";
|
|
33
|
-
|
|
34
|
-
export interface UsePremiumGateResult {
|
|
35
|
-
/** Whether user has premium access */
|
|
36
|
-
isPremium: boolean;
|
|
37
|
-
/** Whether subscription status is loading */
|
|
38
|
-
isLoading: boolean;
|
|
39
|
-
/** Action-level gate: runs callback only if user has premium */
|
|
40
|
-
requirePremium: (onSuccess: () => void) => void;
|
|
41
|
-
/** Screen-level gate: opens paywall if not premium */
|
|
42
|
-
requireScreen: () => void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const usePremiumGate = (): UsePremiumGateResult => {
|
|
46
|
-
const { isPremium, isLoading } = useSubscriptionStatus();
|
|
47
|
-
|
|
48
|
-
const requirePremium = useCallback(
|
|
49
|
-
(onSuccess: () => void) => {
|
|
50
|
-
if (isLoading) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (isPremium) {
|
|
55
|
-
onSuccess();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
paywallControl.open();
|
|
60
|
-
},
|
|
61
|
-
[isPremium, isLoading]
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const requireScreen = useCallback(() => {
|
|
65
|
-
if (!isLoading && !isPremium) {
|
|
66
|
-
paywallControl.open();
|
|
67
|
-
}
|
|
68
|
-
}, [isPremium, isLoading]);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
isPremium,
|
|
72
|
-
isLoading,
|
|
73
|
-
requirePremium,
|
|
74
|
-
requireScreen,
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* useSubscription Hook (Alias for usePremiumGate)
|
|
80
|
-
*
|
|
81
|
-
* Simpler name for premium-only apps.
|
|
82
|
-
* Same functionality as usePremiumGate.
|
|
83
|
-
*/
|
|
84
|
-
export const useSubscription = usePremiumGate;
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Saved Purchase Auto-Execution Hook
|
|
3
|
-
* Automatically executes saved purchase when user converts from anonymous to authenticated
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useEffect, useRef } from "react";
|
|
7
|
-
import {
|
|
8
|
-
useAuthStore,
|
|
9
|
-
selectUserId,
|
|
10
|
-
selectIsAnonymous,
|
|
11
|
-
} from "@umituz/react-native-auth";
|
|
12
|
-
import { getSavedPurchase, clearSavedPurchase } from "./useAuthAwarePurchase";
|
|
13
|
-
import { usePremium } from "./usePremium";
|
|
14
|
-
import { SubscriptionManager } from "../infrastructure/managers/SubscriptionManager";
|
|
15
|
-
import { usePurchaseLoadingStore } from "./stores";
|
|
16
|
-
|
|
17
|
-
export interface UseSavedPurchaseAutoExecutionParams {
|
|
18
|
-
onSuccess?: () => void;
|
|
19
|
-
onError?: (error: Error) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface UseSavedPurchaseAutoExecutionResult {
|
|
23
|
-
isExecuting: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const useSavedPurchaseAutoExecution = (
|
|
27
|
-
params?: UseSavedPurchaseAutoExecutionParams
|
|
28
|
-
): UseSavedPurchaseAutoExecutionResult => {
|
|
29
|
-
const { onSuccess, onError } = params ?? {};
|
|
30
|
-
|
|
31
|
-
const userId = useAuthStore(selectUserId);
|
|
32
|
-
const isAnonymous = useAuthStore(selectIsAnonymous);
|
|
33
|
-
|
|
34
|
-
const { purchasePackage } = usePremium();
|
|
35
|
-
const { startPurchase, endPurchase } = usePurchaseLoadingStore();
|
|
36
|
-
|
|
37
|
-
const prevIsAnonymousRef = useRef<boolean | undefined>(undefined);
|
|
38
|
-
const isExecutingRef = useRef(false);
|
|
39
|
-
const hasExecutedRef = useRef(false);
|
|
40
|
-
|
|
41
|
-
const purchasePackageRef = useRef(purchasePackage);
|
|
42
|
-
const onSuccessRef = useRef(onSuccess);
|
|
43
|
-
const onErrorRef = useRef(onError);
|
|
44
|
-
const startPurchaseRef = useRef(startPurchase);
|
|
45
|
-
const endPurchaseRef = useRef(endPurchase);
|
|
46
|
-
|
|
47
|
-
// Consolidate all ref updates into a single effect
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
purchasePackageRef.current = purchasePackage;
|
|
50
|
-
onSuccessRef.current = onSuccess;
|
|
51
|
-
onErrorRef.current = onError;
|
|
52
|
-
startPurchaseRef.current = startPurchase;
|
|
53
|
-
endPurchaseRef.current = endPurchase;
|
|
54
|
-
}, [purchasePackage, onSuccess, onError, startPurchase, endPurchase]);
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
const isAuthenticated = !!userId && !isAnonymous;
|
|
58
|
-
const prevIsAnonymous = prevIsAnonymousRef.current;
|
|
59
|
-
const savedPurchase = getSavedPurchase();
|
|
60
|
-
|
|
61
|
-
const wasAnonymous = prevIsAnonymous === true;
|
|
62
|
-
const becameAuthenticated = wasAnonymous && isAuthenticated;
|
|
63
|
-
|
|
64
|
-
const shouldLog = prevIsAnonymousRef.current !== isAnonymous;
|
|
65
|
-
|
|
66
|
-
if (typeof __DEV__ !== "undefined" && __DEV__ && shouldLog) {
|
|
67
|
-
console.log("[SavedPurchaseAutoExecution] Auth state check:", {
|
|
68
|
-
userId: userId?.slice(0, 8),
|
|
69
|
-
prevIsAnonymous,
|
|
70
|
-
isAnonymous,
|
|
71
|
-
isAuthenticated,
|
|
72
|
-
wasAnonymous,
|
|
73
|
-
becameAuthenticated,
|
|
74
|
-
hasSavedPurchase: !!savedPurchase,
|
|
75
|
-
savedProductId: savedPurchase?.pkg.product.identifier,
|
|
76
|
-
willExecute:
|
|
77
|
-
becameAuthenticated &&
|
|
78
|
-
!!savedPurchase &&
|
|
79
|
-
!isExecutingRef.current &&
|
|
80
|
-
!hasExecutedRef.current,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
becameAuthenticated &&
|
|
86
|
-
savedPurchase &&
|
|
87
|
-
!isExecutingRef.current &&
|
|
88
|
-
!hasExecutedRef.current
|
|
89
|
-
) {
|
|
90
|
-
hasExecutedRef.current = true;
|
|
91
|
-
isExecutingRef.current = true;
|
|
92
|
-
|
|
93
|
-
const executeFlow = async () => {
|
|
94
|
-
const currentUserId = userId;
|
|
95
|
-
if (!currentUserId) {
|
|
96
|
-
isExecutingRef.current = false;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const maxAttempts = 20;
|
|
101
|
-
const delayMs = 500;
|
|
102
|
-
|
|
103
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
104
|
-
const isReady = SubscriptionManager.isInitializedForUser(currentUserId);
|
|
105
|
-
|
|
106
|
-
if (isReady) {
|
|
107
|
-
const pkg = savedPurchase.pkg;
|
|
108
|
-
|
|
109
|
-
startPurchaseRef.current(pkg.product.identifier, "auto-execution");
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const success = await purchasePackageRef.current(pkg);
|
|
113
|
-
|
|
114
|
-
if (success) {
|
|
115
|
-
clearSavedPurchase();
|
|
116
|
-
if (onSuccessRef.current) {
|
|
117
|
-
onSuccessRef.current();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (onErrorRef.current && error instanceof Error) {
|
|
122
|
-
onErrorRef.current(error);
|
|
123
|
-
}
|
|
124
|
-
} finally {
|
|
125
|
-
endPurchaseRef.current();
|
|
126
|
-
isExecutingRef.current = false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
clearSavedPurchase();
|
|
136
|
-
isExecutingRef.current = false;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
executeFlow();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
prevIsAnonymousRef.current = isAnonymous;
|
|
143
|
-
}, [userId, isAnonymous]);
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
isExecuting: isExecutingRef.current,
|
|
147
|
-
};
|
|
148
|
-
};
|