@umituz/react-native-subscription 3.1.10 → 3.1.12
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/presentation/useCreditsRealTime.ts +10 -5
- package/src/domains/credits/utils/creditValidation.ts +5 -26
- package/src/domains/paywall/hooks/usePaywallActions.ts +21 -133
- package/src/domains/paywall/hooks/usePaywallActions.types.ts +16 -0
- package/src/domains/paywall/hooks/usePaywallPurchase.ts +78 -0
- package/src/domains/paywall/hooks/usePaywallRestore.ts +66 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +116 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +19 -237
- package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +55 -0
- package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +143 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +6 -3
- package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +2 -2
- package/src/domains/subscription/infrastructure/managers/packageHandlerFactory.ts +2 -2
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +2 -2
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.logic.ts +52 -0
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +15 -89
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.types.ts +59 -0
- package/src/domains/subscription/presentation/components/details/CreditRow.tsx +9 -0
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.tsx +23 -0
- package/src/domains/subscription/presentation/components/states/FeedbackState.tsx +36 -0
- package/src/domains/subscription/presentation/components/states/InitializingState.tsx +47 -0
- package/src/domains/subscription/presentation/components/states/OnboardingState.tsx +27 -0
- package/src/domains/subscription/presentation/components/states/PaywallState.tsx +66 -0
- package/src/domains/subscription/presentation/components/states/ReadyState.tsx +51 -0
- package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +8 -2
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +119 -103
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +7 -0
- package/src/domains/wallet/presentation/components/TransactionItem.tsx +11 -0
- package/src/index.components.ts +1 -1
- package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -2
- package/src/shared/presentation/hooks/useFirestoreRealTime.ts +22 -6
- package/src/shared/utils/errors/errorAssertions.ts +35 -0
- package/src/shared/utils/errors/errorConversion.ts +73 -0
- package/src/shared/utils/errors/errorTypeGuards.ts +27 -0
- package/src/shared/utils/errors/errorWrappers.ts +54 -0
- package/src/shared/utils/errors/index.ts +19 -0
- package/src/shared/utils/errors/serviceErrors.ts +36 -0
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.states.tsx +0 -187
- package/src/shared/utils/errorUtils.ts +0 -195
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Components for ManagedSubscriptionFlow
|
|
3
|
-
* Separated for better maintainability
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import type { PurchasesPackage } from "react-native-purchases";
|
|
8
|
-
import type { UserCredits } from "../../../credits/core/Credits";
|
|
9
|
-
import { SplashScreen } from "@umituz/react-native-design-system/molecules";
|
|
10
|
-
import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
|
|
11
|
-
import type { ManagedSubscriptionFlowProps } from "./ManagedSubscriptionFlow";
|
|
12
|
-
import { PaywallScreen } from "../../../paywall/components/PaywallScreen";
|
|
13
|
-
import { PaywallFeedbackScreen } from "./feedback/PaywallFeedbackScreen";
|
|
14
|
-
import { usePaywallFeedbackSubmit } from "../../../../presentation/hooks/feedback/useFeedbackSubmit";
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// INITIALIZING STATE
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
interface InitializingStateProps {
|
|
21
|
-
tokens: any;
|
|
22
|
-
splash?: ManagedSubscriptionFlowProps["splash"];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const InitializingState: React.FC<InitializingStateProps> = ({ tokens, splash }) => (
|
|
26
|
-
<SplashScreen
|
|
27
|
-
appName={splash?.appName || "Loading..."}
|
|
28
|
-
tagline={splash?.tagline || "Please wait while we set things up"}
|
|
29
|
-
colors={tokens.colors}
|
|
30
|
-
/>
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// ONBOARDING STATE
|
|
35
|
-
// ============================================================================
|
|
36
|
-
|
|
37
|
-
interface OnboardingStateProps {
|
|
38
|
-
config: ManagedSubscriptionFlowProps["onboarding"];
|
|
39
|
-
onComplete: () => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const OnboardingState: React.FC<OnboardingStateProps> = ({ config, onComplete }) => (
|
|
43
|
-
<OnboardingScreen
|
|
44
|
-
slides={config.slides}
|
|
45
|
-
onComplete={onComplete}
|
|
46
|
-
showSkipButton={config.showSkipButton ?? true}
|
|
47
|
-
showBackButton={config.showBackButton ?? true}
|
|
48
|
-
showProgressBar={config.showProgressBar ?? true}
|
|
49
|
-
themeColors={config.themeColors}
|
|
50
|
-
translations={config.translations}
|
|
51
|
-
/>
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// PAYWALL STATE
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
interface PaywallStateProps {
|
|
59
|
-
config: ManagedSubscriptionFlowProps["paywall"];
|
|
60
|
-
packages: PurchasesPackage[];
|
|
61
|
-
isPremium: boolean;
|
|
62
|
-
credits: UserCredits | null;
|
|
63
|
-
isSyncing: boolean;
|
|
64
|
-
onPurchase: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
65
|
-
onRestore: () => Promise<boolean>;
|
|
66
|
-
onClose: (purchased: boolean) => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const PaywallState: React.FC<PaywallStateProps> = ({
|
|
70
|
-
config,
|
|
71
|
-
packages,
|
|
72
|
-
isPremium,
|
|
73
|
-
credits,
|
|
74
|
-
isSyncing,
|
|
75
|
-
onPurchase,
|
|
76
|
-
onRestore,
|
|
77
|
-
onClose,
|
|
78
|
-
}) => {
|
|
79
|
-
const [purchaseSuccessful, setPurchaseSuccessful] = React.useState(false);
|
|
80
|
-
|
|
81
|
-
const handlePurchase = async (pkg: PurchasesPackage) => {
|
|
82
|
-
const result = await onPurchase(pkg);
|
|
83
|
-
if (result) {
|
|
84
|
-
setPurchaseSuccessful(true);
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const handleClose = () => {
|
|
90
|
-
onClose(purchaseSuccessful);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<PaywallScreen
|
|
95
|
-
translations={config.translations}
|
|
96
|
-
legalUrls={config.legalUrls}
|
|
97
|
-
features={config.features}
|
|
98
|
-
bestValueIdentifier={config.bestValueIdentifier}
|
|
99
|
-
creditsLabel={config.creditsLabel}
|
|
100
|
-
heroImage={config.heroImage}
|
|
101
|
-
source="onboarding"
|
|
102
|
-
packages={packages}
|
|
103
|
-
isPremium={isPremium}
|
|
104
|
-
credits={credits}
|
|
105
|
-
isSyncing={isSyncing}
|
|
106
|
-
onPurchase={handlePurchase}
|
|
107
|
-
onRestore={onRestore}
|
|
108
|
-
onClose={handleClose}
|
|
109
|
-
/>
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// ============================================================================
|
|
114
|
-
// FEEDBACK STATE
|
|
115
|
-
// ============================================================================
|
|
116
|
-
|
|
117
|
-
interface FeedbackStateProps {
|
|
118
|
-
config: ManagedSubscriptionFlowProps["feedback"];
|
|
119
|
-
onClose: () => void;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export const FeedbackState: React.FC<FeedbackStateProps> = ({ config, onClose }) => {
|
|
123
|
-
const { submit: internalSubmit } = usePaywallFeedbackSubmit();
|
|
124
|
-
|
|
125
|
-
const handleSubmit = async (data: { reason: string; otherText?: string }) => {
|
|
126
|
-
if (config.onSubmit) {
|
|
127
|
-
await config.onSubmit(data);
|
|
128
|
-
} else {
|
|
129
|
-
const description = data.otherText ? `${data.reason}: ${data.otherText}` : data.reason;
|
|
130
|
-
await internalSubmit(description);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<PaywallFeedbackScreen
|
|
136
|
-
onClose={onClose}
|
|
137
|
-
onSubmit={handleSubmit}
|
|
138
|
-
translations={config.translations}
|
|
139
|
-
/>
|
|
140
|
-
);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// READY STATE (APP CONTENT)
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
interface ReadyStateProps {
|
|
148
|
-
children: React.ReactNode;
|
|
149
|
-
offline?: ManagedSubscriptionFlowProps["offline"];
|
|
150
|
-
feedbackConfig: ManagedSubscriptionFlowProps["feedback"];
|
|
151
|
-
showFeedback: boolean;
|
|
152
|
-
tokens: any;
|
|
153
|
-
onFeedbackClose: () => void;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const ReadyState: React.FC<ReadyStateProps> = ({
|
|
157
|
-
children,
|
|
158
|
-
offline,
|
|
159
|
-
feedbackConfig,
|
|
160
|
-
showFeedback,
|
|
161
|
-
tokens,
|
|
162
|
-
onFeedbackClose,
|
|
163
|
-
}) => {
|
|
164
|
-
const { OfflineBanner } = require("@umituz/react-native-design-system/offline");
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<>
|
|
168
|
-
{children}
|
|
169
|
-
|
|
170
|
-
{offline && (
|
|
171
|
-
<OfflineBanner
|
|
172
|
-
visible={offline.isOffline}
|
|
173
|
-
message={offline.message}
|
|
174
|
-
backgroundColor={offline.backgroundColor || tokens.colors.error}
|
|
175
|
-
position={offline.position || "top"}
|
|
176
|
-
/>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
{showFeedback && (
|
|
180
|
-
<FeedbackState
|
|
181
|
-
config={feedbackConfig}
|
|
182
|
-
onClose={onFeedbackClose}
|
|
183
|
-
/>
|
|
184
|
-
)}
|
|
185
|
-
</>
|
|
186
|
-
);
|
|
187
|
-
};
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized error handling utilities.
|
|
3
|
-
*
|
|
4
|
-
* Benefits:
|
|
5
|
-
* - Removes 45+ duplicated error handling blocks
|
|
6
|
-
* - Consistent error logging everywhere
|
|
7
|
-
* - Easier debugging with better error context
|
|
8
|
-
* - Type-safe error handling
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { logError } from "./logger";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Safely convert unknown error to Error object.
|
|
15
|
-
* Useful when catching errors from external APIs.
|
|
16
|
-
*/
|
|
17
|
-
export function toError(error: unknown): Error {
|
|
18
|
-
if (error instanceof Error) {
|
|
19
|
-
return error;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof error === "string") {
|
|
23
|
-
return new Error(error);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (error === null || error === undefined) {
|
|
27
|
-
return new Error("Unknown error occurred");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
return new Error(JSON.stringify(error));
|
|
32
|
-
} catch {
|
|
33
|
-
return new Error(String(error));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Create an error with a code and optional cause.
|
|
39
|
-
* Wraps the BaseError pattern for convenience.
|
|
40
|
-
*/
|
|
41
|
-
export function createError(
|
|
42
|
-
message: string,
|
|
43
|
-
code: string,
|
|
44
|
-
cause?: Error
|
|
45
|
-
): Error {
|
|
46
|
-
const error = new Error(message);
|
|
47
|
-
error.name = code;
|
|
48
|
-
|
|
49
|
-
if (cause) {
|
|
50
|
-
// @ts-ignore - adding cause property
|
|
51
|
-
error.cause = cause;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return error;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Log an error with consistent formatting.
|
|
59
|
-
* Only logs in __DEV__ mode.
|
|
60
|
-
*/
|
|
61
|
-
export function logAndReturnError(
|
|
62
|
-
tag: string,
|
|
63
|
-
message: string,
|
|
64
|
-
error: unknown,
|
|
65
|
-
context?: Record<string, unknown>
|
|
66
|
-
): Error {
|
|
67
|
-
const normalizedError = toError(error);
|
|
68
|
-
|
|
69
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
70
|
-
logError(tag, message, normalizedError, {
|
|
71
|
-
...context,
|
|
72
|
-
originalError: error,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return normalizedError;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Type guard to check if value is an Error.
|
|
81
|
-
*/
|
|
82
|
-
export function isError(value: unknown): value is Error {
|
|
83
|
-
return value instanceof Error;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Type guard to check if error has a code property.
|
|
88
|
-
*/
|
|
89
|
-
export function isErrorWithCode(error: Error): error is Error & { code: string } {
|
|
90
|
-
return "code" in error && typeof error.code === "string";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Type guard to check if error has a cause property.
|
|
95
|
-
*/
|
|
96
|
-
export function isErrorWithCause(error: Error): error is Error & { cause: Error } {
|
|
97
|
-
return "cause" in error && error.cause instanceof Error;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Wrap an async function with error handling.
|
|
102
|
-
* Returns a Result type with success/error states.
|
|
103
|
-
*/
|
|
104
|
-
export async function tryAsync<T>(
|
|
105
|
-
fn: () => Promise<T>,
|
|
106
|
-
context: { tag: string; operation: string }
|
|
107
|
-
): Promise<{ success: true; data: T } | { success: false; error: Error }> {
|
|
108
|
-
try {
|
|
109
|
-
const data = await fn();
|
|
110
|
-
return { success: true, data };
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const normalizedError = logAndReturnError(
|
|
113
|
-
context.tag,
|
|
114
|
-
`Failed to ${context.operation}`,
|
|
115
|
-
error
|
|
116
|
-
);
|
|
117
|
-
return { success: false, error: normalizedError };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Wrap a synchronous function with error handling.
|
|
123
|
-
* Returns a Result type with success/error states.
|
|
124
|
-
*/
|
|
125
|
-
export function trySync<T>(
|
|
126
|
-
fn: () => T,
|
|
127
|
-
context: { tag: string; operation: string }
|
|
128
|
-
): { success: true; data: T } | { success: false; error: Error } {
|
|
129
|
-
try {
|
|
130
|
-
const data = fn();
|
|
131
|
-
return { success: true, data };
|
|
132
|
-
} catch (error) {
|
|
133
|
-
const normalizedError = logAndReturnError(
|
|
134
|
-
context.tag,
|
|
135
|
-
`Failed to ${context.operation}`,
|
|
136
|
-
error
|
|
137
|
-
);
|
|
138
|
-
return { success: false, error: normalizedError };
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Assert that a condition is true, throw error otherwise.
|
|
144
|
-
* Useful for validation and runtime checks.
|
|
145
|
-
*/
|
|
146
|
-
export function assert(
|
|
147
|
-
condition: boolean,
|
|
148
|
-
message: string,
|
|
149
|
-
code: string = "ASSERTION_ERROR"
|
|
150
|
-
): asserts condition {
|
|
151
|
-
if (!condition) {
|
|
152
|
-
throw createError(message, code);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Assert that a value is not null/undefined.
|
|
158
|
-
* Throws error if value is null/undefined, returns value otherwise.
|
|
159
|
-
* Useful for type narrowing.
|
|
160
|
-
*/
|
|
161
|
-
export function assertNotNil<T>(
|
|
162
|
-
value: T | null | undefined,
|
|
163
|
-
message: string = "Value should not be null or undefined"
|
|
164
|
-
): T {
|
|
165
|
-
assert(value !== null && value !== undefined, message, "NOT_NIL_ERROR");
|
|
166
|
-
return value;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Create a Firestore-specific error with consistent formatting.
|
|
171
|
-
*/
|
|
172
|
-
export function createFirestoreError(
|
|
173
|
-
operation: string,
|
|
174
|
-
error: unknown
|
|
175
|
-
): Error {
|
|
176
|
-
return createError(
|
|
177
|
-
`Firestore ${operation} failed`,
|
|
178
|
-
"FIRESTORE_ERROR",
|
|
179
|
-
toError(error)
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create a RevenueCat-specific error with consistent formatting.
|
|
185
|
-
*/
|
|
186
|
-
export function createRevenueCatError(
|
|
187
|
-
operation: string,
|
|
188
|
-
error: unknown
|
|
189
|
-
): Error {
|
|
190
|
-
return createError(
|
|
191
|
-
`RevenueCat ${operation} failed`,
|
|
192
|
-
"REVENUECAT_ERROR",
|
|
193
|
-
toError(error)
|
|
194
|
-
);
|
|
195
|
-
}
|