@umituz/react-native-subscription 2.14.82 → 2.14.83
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/PaywallContainer.tsx +26 -159
- package/src/domains/paywall/components/PaywallFeatures.tsx +25 -0
- package/src/domains/paywall/components/PaywallFooter.tsx +46 -93
- package/src/domains/paywall/components/PaywallModal.tsx +14 -99
- package/src/domains/paywall/hooks/usePaywallActions.ts +63 -0
- package/src/index.ts +23 -461
- package/src/infrastructure/repositories/CreditsRepository.ts +43 -177
- package/src/infrastructure/services/SubscriptionInitializer.ts +32 -186
- package/src/presentation/hooks/index.ts +23 -0
- package/src/presentation/hooks/useDeductCredit.ts +22 -148
- package/src/presentation/hooks/useInitializeCredits.ts +57 -0
- package/src/presentation/hooks/usePremiumWithCredits.ts +1 -1
- package/src/revenuecat/index.ts +12 -0
- package/src/utils/index.ts +15 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useDeductCredit Hook
|
|
3
|
-
*
|
|
4
3
|
* TanStack Query mutation hook for deducting credits.
|
|
5
|
-
* Generic and reusable - uses module-level repository.
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
6
|
import { useCallback } from "react";
|
|
@@ -17,9 +15,7 @@ export interface UseDeductCreditParams {
|
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
export interface UseDeductCreditResult {
|
|
20
|
-
/** Deduct a single credit */
|
|
21
18
|
deductCredit: (creditType: CreditType) => Promise<boolean>;
|
|
22
|
-
/** Deduct multiple credits (loops internally) */
|
|
23
19
|
deductCredits: (cost: number, creditType?: CreditType) => Promise<boolean>;
|
|
24
20
|
isDeducting: boolean;
|
|
25
21
|
}
|
|
@@ -33,169 +29,47 @@ export const useDeductCredit = ({
|
|
|
33
29
|
|
|
34
30
|
const mutation = useMutation({
|
|
35
31
|
mutationFn: async (creditType: CreditType) => {
|
|
36
|
-
if (!userId)
|
|
37
|
-
throw new Error("User not authenticated");
|
|
38
|
-
}
|
|
32
|
+
if (!userId) throw new Error("User not authenticated");
|
|
39
33
|
return repository.deductCredit(userId, creditType);
|
|
40
34
|
},
|
|
41
35
|
onMutate: async (creditType: CreditType) => {
|
|
42
36
|
if (!userId) return;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
await queryClient.cancelQueries({ queryKey: creditsQueryKeys.user(userId) });
|
|
38
|
+
const previousCredits = queryClient.getQueryData<UserCredits>(creditsQueryKeys.user(userId));
|
|
39
|
+
queryClient.setQueryData<UserCredits | null>(creditsQueryKeys.user(userId), (old) => {
|
|
40
|
+
if (!old) return old;
|
|
41
|
+
const field = creditType === "text" ? "textCredits" : "imageCredits";
|
|
42
|
+
return { ...old, [field]: Math.max(0, old[field] - 1), lastUpdatedAt: new Date() };
|
|
46
43
|
});
|
|
47
|
-
|
|
48
|
-
const previousCredits = queryClient.getQueryData<UserCredits>(
|
|
49
|
-
creditsQueryKeys.user(userId)
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
queryClient.setQueryData<UserCredits | null>(
|
|
53
|
-
creditsQueryKeys.user(userId),
|
|
54
|
-
(old) => {
|
|
55
|
-
if (!old) return old;
|
|
56
|
-
const fieldName =
|
|
57
|
-
creditType === "text" ? "textCredits" : "imageCredits";
|
|
58
|
-
return {
|
|
59
|
-
...old,
|
|
60
|
-
[fieldName]: Math.max(0, old[fieldName] - 1),
|
|
61
|
-
lastUpdatedAt: new Date(),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
);
|
|
65
|
-
|
|
66
44
|
return { previousCredits };
|
|
67
45
|
},
|
|
68
|
-
onError: (_err,
|
|
46
|
+
onError: (_err, _type, context) => {
|
|
69
47
|
if (userId && context?.previousCredits) {
|
|
70
|
-
queryClient.setQueryData(
|
|
71
|
-
creditsQueryKeys.user(userId),
|
|
72
|
-
context.previousCredits
|
|
73
|
-
);
|
|
48
|
+
queryClient.setQueryData(creditsQueryKeys.user(userId), context.previousCredits);
|
|
74
49
|
}
|
|
75
50
|
},
|
|
76
51
|
onSettled: () => {
|
|
77
|
-
if (userId) {
|
|
78
|
-
queryClient.invalidateQueries({
|
|
79
|
-
queryKey: creditsQueryKeys.user(userId),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
52
|
+
if (userId) queryClient.invalidateQueries({ queryKey: creditsQueryKeys.user(userId) });
|
|
82
53
|
},
|
|
83
54
|
});
|
|
84
55
|
|
|
85
|
-
const deductCredit = useCallback(async (
|
|
56
|
+
const deductCredit = useCallback(async (type: CreditType): Promise<boolean> => {
|
|
86
57
|
try {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (result.error?.code === "CREDITS_EXHAUSTED") {
|
|
91
|
-
onCreditsExhausted?.();
|
|
92
|
-
}
|
|
58
|
+
const res = await mutation.mutateAsync(type);
|
|
59
|
+
if (!res.success) {
|
|
60
|
+
if (res.error?.code === "CREDITS_EXHAUSTED") onCreditsExhausted?.();
|
|
93
61
|
return false;
|
|
94
62
|
}
|
|
95
|
-
|
|
96
63
|
return true;
|
|
97
|
-
} catch {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
64
|
+
} catch { return false; }
|
|
100
65
|
}, [mutation, onCreditsExhausted]);
|
|
101
66
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (let i = 0; i < cost; i++) {
|
|
109
|
-
const success = await deductCredit(creditType);
|
|
110
|
-
if (!success) return false;
|
|
111
|
-
}
|
|
112
|
-
return true;
|
|
113
|
-
},
|
|
114
|
-
[deductCredit],
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
deductCredit,
|
|
119
|
-
deductCredits,
|
|
120
|
-
isDeducting: mutation.isPending,
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export interface UseInitializeCreditsParams {
|
|
125
|
-
userId: string | undefined;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface InitializeCreditsOptions {
|
|
129
|
-
purchaseId?: string;
|
|
130
|
-
productId?: string;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface UseInitializeCreditsResult {
|
|
134
|
-
initializeCredits: (options?: InitializeCreditsOptions) => Promise<boolean>;
|
|
135
|
-
isInitializing: boolean;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export const useInitializeCredits = ({
|
|
139
|
-
userId,
|
|
140
|
-
}: UseInitializeCreditsParams): UseInitializeCreditsResult => {
|
|
141
|
-
const repository = getCreditsRepository();
|
|
142
|
-
const queryClient = useQueryClient();
|
|
143
|
-
|
|
144
|
-
const mutation = useMutation({
|
|
145
|
-
mutationFn: async (options?: InitializeCreditsOptions) => {
|
|
146
|
-
if (!userId) {
|
|
147
|
-
throw new Error("User not authenticated");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (__DEV__) {
|
|
151
|
-
console.log("[useInitializeCredits] Initializing credits:", {
|
|
152
|
-
userId,
|
|
153
|
-
purchaseId: options?.purchaseId,
|
|
154
|
-
productId: options?.productId,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return repository.initializeCredits(
|
|
159
|
-
userId,
|
|
160
|
-
options?.purchaseId,
|
|
161
|
-
options?.productId
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
onSuccess: (result) => {
|
|
165
|
-
if (userId && result.success && result.data) {
|
|
166
|
-
if (__DEV__) {
|
|
167
|
-
console.log("[useInitializeCredits] Success, updating cache:", result.data);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Set the data immediately for optimistic UI
|
|
171
|
-
queryClient.setQueryData(creditsQueryKeys.user(userId), result.data);
|
|
172
|
-
// Also invalidate to ensure all subscribers get the update
|
|
173
|
-
queryClient.invalidateQueries({
|
|
174
|
-
queryKey: creditsQueryKeys.user(userId),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
onError: (error) => {
|
|
179
|
-
if (__DEV__) {
|
|
180
|
-
console.error("[useInitializeCredits] Error:", error);
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const initializeCredits = useCallback(
|
|
186
|
-
async (options?: InitializeCreditsOptions): Promise<boolean> => {
|
|
187
|
-
try {
|
|
188
|
-
const result = await mutation.mutateAsync(options);
|
|
189
|
-
return result.success;
|
|
190
|
-
} catch {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
[mutation]
|
|
195
|
-
);
|
|
67
|
+
const deductCredits = useCallback(async (cost: number, type: CreditType = "image"): Promise<boolean> => {
|
|
68
|
+
for (let i = 0; i < cost; i++) {
|
|
69
|
+
if (!(await deductCredit(type))) return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}, [deductCredit]);
|
|
196
73
|
|
|
197
|
-
return {
|
|
198
|
-
initializeCredits,
|
|
199
|
-
isInitializing: mutation.isPending,
|
|
200
|
-
};
|
|
74
|
+
return { deductCredit, deductCredits, isDeducting: mutation.isPending };
|
|
201
75
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useInitializeCredits Hook
|
|
3
|
+
* TanStack Query mutation hook for initializing credits after purchase.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { getCreditsRepository } from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
9
|
+
import { creditsQueryKeys } from "./useCredits";
|
|
10
|
+
|
|
11
|
+
declare const __DEV__: boolean;
|
|
12
|
+
|
|
13
|
+
export interface UseInitializeCreditsParams {
|
|
14
|
+
userId: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface InitializeCreditsOptions {
|
|
18
|
+
purchaseId?: string;
|
|
19
|
+
productId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseInitializeCreditsResult {
|
|
23
|
+
initializeCredits: (options?: InitializeCreditsOptions) => Promise<boolean>;
|
|
24
|
+
isInitializing: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useInitializeCredits = ({
|
|
28
|
+
userId,
|
|
29
|
+
}: UseInitializeCreditsParams): UseInitializeCreditsResult => {
|
|
30
|
+
const repository = getCreditsRepository();
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
|
|
33
|
+
const mutation = useMutation({
|
|
34
|
+
mutationFn: async (options?: InitializeCreditsOptions) => {
|
|
35
|
+
if (!userId) throw new Error("User not authenticated");
|
|
36
|
+
if (__DEV__) console.log("[useInitializeCredits] Initializing:", { userId, ...options });
|
|
37
|
+
return repository.initializeCredits(userId, options?.purchaseId, options?.productId);
|
|
38
|
+
},
|
|
39
|
+
onSuccess: (result) => {
|
|
40
|
+
if (userId && result.success && result.data) {
|
|
41
|
+
if (__DEV__) console.log("[useInitializeCredits] Success:", result.data);
|
|
42
|
+
queryClient.setQueryData(creditsQueryKeys.user(userId), result.data);
|
|
43
|
+
queryClient.invalidateQueries({ queryKey: creditsQueryKeys.user(userId) });
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
onError: (error) => { if (__DEV__) console.error("[useInitializeCredits] Error:", error); },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const initializeCredits = useCallback(async (opts?: InitializeCreditsOptions): Promise<boolean> => {
|
|
50
|
+
try {
|
|
51
|
+
const res = await mutation.mutateAsync(opts);
|
|
52
|
+
return res.success;
|
|
53
|
+
} catch { return false; }
|
|
54
|
+
}, [mutation]);
|
|
55
|
+
|
|
56
|
+
return { initializeCredits, isInitializing: mutation.isPending };
|
|
57
|
+
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useEffect, useCallback } from "react";
|
|
9
9
|
import { useCredits, type UseCreditsResult } from "./useCredits";
|
|
10
|
-
import { useInitializeCredits } from "./
|
|
10
|
+
import { useInitializeCredits } from "./useInitializeCredits";
|
|
11
11
|
|
|
12
12
|
export interface UsePremiumWithCreditsParams {
|
|
13
13
|
userId: string | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./domain/errors/RevenueCatError";
|
|
2
|
+
export * from "./domain/value-objects/RevenueCatConfig";
|
|
3
|
+
export * from "./domain/types/RevenueCatTypes";
|
|
4
|
+
export * from "./domain/constants/RevenueCatConstants";
|
|
5
|
+
export * from "./application/ports/IRevenueCatService";
|
|
6
|
+
export * from "./infrastructure/services/RevenueCatService";
|
|
7
|
+
export * from "./infrastructure/managers/SubscriptionManager";
|
|
8
|
+
export * from "./infrastructure/handlers/PackageHandler";
|
|
9
|
+
export * from "./presentation/hooks/useRevenueCat";
|
|
10
|
+
export * from "./presentation/hooks/useCustomerInfo";
|
|
11
|
+
export * from "./presentation/hooks/usePaywallFlow";
|
|
12
|
+
export * from "./presentation/hooks/useSubscriptionQueries";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./aiCreditHelpers";
|
|
2
|
+
export * from "./authUtils";
|
|
3
|
+
export * from "./creditChecker";
|
|
4
|
+
export * from "./creditMapper";
|
|
5
|
+
export * from "./dateValidationUtils";
|
|
6
|
+
export * from "./packageFilter";
|
|
7
|
+
export * from "./packagePeriodUtils";
|
|
8
|
+
export * from "./packageTypeDetector";
|
|
9
|
+
export * from "./premiumAsyncUtils";
|
|
10
|
+
export * from "./premiumStatusUtils";
|
|
11
|
+
export * from "./priceUtils";
|
|
12
|
+
export * from "./tierUtils";
|
|
13
|
+
export * from "./types";
|
|
14
|
+
export * from "./userTierUtils";
|
|
15
|
+
export * from "./validation";
|