@umituz/react-native-subscription 2.44.0 → 2.45.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Status Change Sync Handler
3
+ * Handles premium status changes (expire, sync metadata, recovery)
4
+ */
5
+
6
+ import { getCreditsRepository } from "../../../credits/infrastructure/CreditsRepositoryManager";
7
+ import type { PremiumStatusChangedEvent } from "../../core/SubscriptionEvents";
8
+ import { UserIdResolver } from "./UserIdResolver";
9
+ import { CreditDocumentOperations } from "./CreditDocumentOperations";
10
+ import { PurchaseSyncHandler } from "./PurchaseSyncHandler";
11
+
12
+ export class StatusChangeSyncHandler {
13
+ constructor(
14
+ private userIdResolver: UserIdResolver,
15
+ private creditOps: CreditDocumentOperations,
16
+ private purchaseHandler: PurchaseSyncHandler
17
+ ) {}
18
+
19
+ async processStatusChange(event: PremiumStatusChangedEvent): Promise<void> {
20
+ // If purchase is in progress, only do recovery sync
21
+ if (this.purchaseHandler.isProcessing()) {
22
+ if (__DEV__) {
23
+ console.log("[StatusChangeSyncHandler] Purchase in progress - running recovery only");
24
+ }
25
+ if (event.isPremium && event.productId) {
26
+ const creditsUserId = await this.userIdResolver.resolveCreditsUserId(event.userId);
27
+ await this.creditOps.syncPremiumStatus(creditsUserId, event);
28
+ }
29
+ return;
30
+ }
31
+
32
+ const creditsUserId = await this.userIdResolver.resolveCreditsUserId(event.userId);
33
+
34
+ // Expired subscription
35
+ if (!event.isPremium && event.productId) {
36
+ await this.creditOps.expireSubscription(creditsUserId);
37
+ return;
38
+ }
39
+
40
+ // No product ID - check if credits doc exists and expire if needed
41
+ if (!event.isPremium && !event.productId) {
42
+ const hasDoc = await getCreditsRepository().creditsDocumentExists(creditsUserId);
43
+ if (hasDoc) {
44
+ await this.creditOps.expireSubscription(creditsUserId);
45
+ }
46
+ return;
47
+ }
48
+
49
+ // No product ID and is premium - nothing to do
50
+ if (!event.productId) {
51
+ return;
52
+ }
53
+
54
+ // Sync premium status (with recovery if needed)
55
+ await this.creditOps.syncPremiumStatus(creditsUserId, event);
56
+ }
57
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Sync Processor Logger
3
+ * Centralized logging for subscription sync operations
4
+ */
5
+
6
+ import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../../shared/infrastructure/SubscriptionEventBus";
7
+ import type { PurchaseCompletedEvent, RenewalDetectedEvent, PremiumStatusChangedEvent } from "../../core/SubscriptionEvents";
8
+
9
+ export type SyncPhase = 'purchase' | 'renewal' | 'status_change';
10
+
11
+ export class SyncProcessorLogger {
12
+ emitSyncStatus(phase: SyncPhase, status: 'syncing' | 'success' | 'error', data: {
13
+ userId: string;
14
+ productId: string;
15
+ error?: string;
16
+ }) {
17
+ subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.SYNC_STATUS_CHANGED, {
18
+ status,
19
+ phase,
20
+ userId: data.userId,
21
+ productId: data.productId,
22
+ error: data.error,
23
+ });
24
+ }
25
+
26
+ logPurchaseStart(event: PurchaseCompletedEvent) {
27
+ if (__DEV__) {
28
+ console.log('[SubscriptionSyncProcessor] 🔵 PURCHASE START', {
29
+ userId: event.userId,
30
+ productId: event.productId,
31
+ source: event.source,
32
+ packageType: event.packageType,
33
+ timestamp: new Date().toISOString(),
34
+ });
35
+ }
36
+ }
37
+
38
+ logPurchaseSuccess(userId: string, productId: string) {
39
+ if (__DEV__) {
40
+ console.log('[SubscriptionSyncProcessor] 🟢 PURCHASE SUCCESS', {
41
+ userId,
42
+ productId,
43
+ timestamp: new Date().toISOString(),
44
+ });
45
+ }
46
+ }
47
+
48
+ logPurchaseError(userId: string, productId: string, error: string) {
49
+ console.error('[SubscriptionSyncProcessor] 🔴 PURCHASE FAILED', {
50
+ userId,
51
+ productId,
52
+ error,
53
+ timestamp: new Date().toISOString(),
54
+ });
55
+ }
56
+
57
+ logRenewalStart(event: RenewalDetectedEvent) {
58
+ if (__DEV__) {
59
+ console.log('[SubscriptionSyncProcessor] 🔵 RENEWAL START', {
60
+ userId: event.userId,
61
+ productId: event.productId,
62
+ newExpirationDate: event.newExpirationDate,
63
+ timestamp: new Date().toISOString(),
64
+ });
65
+ }
66
+ }
67
+
68
+ logRenewalSuccess(userId: string, productId: string) {
69
+ if (__DEV__) {
70
+ console.log('[SubscriptionSyncProcessor] 🟢 RENEWAL SUCCESS', {
71
+ userId,
72
+ productId,
73
+ timestamp: new Date().toISOString(),
74
+ });
75
+ }
76
+ }
77
+
78
+ logRenewalError(userId: string, productId: string, error: string) {
79
+ console.error('[SubscriptionSyncProcessor] 🔴 RENEWAL FAILED', {
80
+ userId,
81
+ productId,
82
+ error,
83
+ timestamp: new Date().toISOString(),
84
+ });
85
+ }
86
+
87
+ logStatusChangeStart(event: PremiumStatusChangedEvent) {
88
+ if (__DEV__) {
89
+ console.log('[SubscriptionSyncProcessor] 🔵 STATUS CHANGE START', {
90
+ userId: event.userId,
91
+ isPremium: event.isPremium,
92
+ productId: event.productId,
93
+ willRenew: event.willRenew,
94
+ expirationDate: event.expirationDate,
95
+ timestamp: new Date().toISOString(),
96
+ });
97
+ }
98
+ }
99
+
100
+ logStatusChangeSuccess(userId: string, isPremium: boolean, productId?: string) {
101
+ if (__DEV__) {
102
+ console.log('[SubscriptionSyncProcessor] 🟢 STATUS CHANGE SUCCESS', {
103
+ userId,
104
+ isPremium,
105
+ productId,
106
+ timestamp: new Date().toISOString(),
107
+ });
108
+ }
109
+ }
110
+
111
+ logStatusChangeError(userId: string, isPremium: boolean, productId: string | undefined, error: string) {
112
+ console.error('[SubscriptionSyncProcessor] 🔴 STATUS CHANGE FAILED', {
113
+ userId,
114
+ isPremium,
115
+ productId,
116
+ error,
117
+ timestamp: new Date().toISOString(),
118
+ });
119
+ }
120
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * User ID Resolver
3
+ * Handles resolution of RevenueCat user ID to credits user ID
4
+ */
5
+
6
+ export class UserIdResolver {
7
+ constructor(private getAnonymousUserId: () => Promise<string>) {}
8
+
9
+ async resolveCreditsUserId(revenueCatUserId: string | null | undefined): Promise<string> {
10
+ // Try revenueCatUserId first
11
+ const trimmed = revenueCatUserId?.trim();
12
+ if (this.isValidUserId(trimmed)) {
13
+ return trimmed;
14
+ }
15
+
16
+ // Fallback to anonymous user ID
17
+ console.warn("[UserIdResolver] revenueCatUserId is empty/null, falling back to anonymousUserId");
18
+ const anonymousId = await this.getAnonymousUserId();
19
+ const trimmedAnonymous = anonymousId?.trim();
20
+
21
+ if (!this.isValidUserId(trimmedAnonymous)) {
22
+ throw new Error("[UserIdResolver] Cannot resolve credits userId: both revenueCatUserId and anonymousUserId are empty");
23
+ }
24
+
25
+ return trimmedAnonymous;
26
+ }
27
+
28
+ private isValidUserId(userId: string | undefined): boolean {
29
+ return !!userId && userId.length > 0 && userId !== 'undefined' && userId !== 'null';
30
+ }
31
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * State Components for ManagedSubscriptionFlow
3
+ * Separated for better maintainability
4
+ */
5
+
6
+ import React from "react";
7
+ import type { NavigationProp } from "@react-navigation/native";
8
+ import { SplashScreen } from "@umituz/react-native-design-system/molecules";
9
+ import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
10
+ import { useSubscriptionFlowStore } from "../useSubscriptionFlow";
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: any[];
61
+ isPremium: boolean;
62
+ credits: number | null;
63
+ isSyncing: boolean;
64
+ onPurchase: (pkgId: string) => Promise<any>;
65
+ onRestore: () => Promise<any>;
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 (pkgId: string) => {
82
+ const result = await onPurchase(pkgId);
83
+ if (result?.success) {
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,27 +1,26 @@
1
1
  /**
2
2
  * ManagedSubscriptionFlow
3
3
  *
4
- * State machine-based flow orchestration.
5
- * No complex if/else - clean state transitions.
4
+ * Clean state machine-based flow orchestration.
5
+ * All state components separated to individual files.
6
6
  */
7
7
 
8
- import React, { useEffect, useCallback } from "react";
8
+ import React, { useEffect } from "react";
9
9
  import type { NavigationProp } from "@react-navigation/native";
10
10
  import type { ImageSourcePropType } from "react-native";
11
- import { View } from "react-native";
12
- import { SplashScreen, useSplashFlow } from "@umituz/react-native-design-system/molecules";
13
- import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
14
- import { OfflineBanner } from "@umituz/react-native-design-system/offline";
15
11
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
16
12
  import { usePremiumStatus } from "../../presentation/usePremiumStatus";
17
13
  import { usePremiumPackages } from "../../presentation/usePremiumPackages";
18
14
  import { usePremiumActions } from "../../presentation/usePremiumActions";
19
15
  import { useSubscriptionFlowStore, SubscriptionFlowStatus } from "../useSubscriptionFlow";
20
- import { PaywallFeedbackScreen } from "./feedback/PaywallFeedbackScreen";
21
16
  import type { PaywallFeedbackTranslations } from "./feedback/PaywallFeedbackScreen.types";
22
17
  import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../../../paywall/entities/types";
23
- import { usePaywallFeedbackSubmit } from "../../../../presentation/hooks/feedback/useFeedbackSubmit";
24
- import { PaywallScreen } from "../../../paywall/components/PaywallScreen";
18
+ import {
19
+ InitializingState,
20
+ OnboardingState,
21
+ PaywallState,
22
+ ReadyState,
23
+ } from "./ManagedSubscriptionFlow.states";
25
24
 
26
25
  export interface ManagedSubscriptionFlowProps {
27
26
  children: React.ReactNode;
@@ -79,123 +78,8 @@ import {
79
78
  useSubscriptionFlowStatus
80
79
  } from "../providers/SubscriptionFlowProvider";
81
80
 
82
- // ============================================================================
83
- // STATE MACHINE COMPONENTS
84
- // ============================================================================
85
-
86
- interface StateComponentProps {
87
- status: SubscriptionFlowStatus;
88
- tokens: any;
89
- onboardingConfig: ManagedSubscriptionFlowProps["onboarding"];
90
- paywallConfig: ManagedSubscriptionFlowProps["paywall"];
91
- feedbackConfig: ManagedSubscriptionFlowProps["feedback"];
92
- isPremium: boolean;
93
- packages: any[];
94
- credits: number | null;
95
- isSyncing: boolean;
96
- onPurchasePackage: (pkgId: string) => Promise<any>;
97
- onRestorePurchase: () => Promise<any>;
98
- navigation: NavigationProp<any>;
99
- }
100
-
101
- const InitializingState: React.FC<{ tokens: any; splash?: ManagedSubscriptionFlowProps["splash"] }> = ({ tokens, splash }) => (
102
- <SplashScreen
103
- appName={splash?.appName || "Loading..."}
104
- tagline={splash?.tagline || "Please wait while we set things up"}
105
- colors={tokens.colors}
106
- />
107
- );
108
-
109
- const OnboardingState: React.FC<{
110
- config: ManagedSubscriptionFlowProps["onboarding"];
111
- onComplete: () => void;
112
- }> = ({ config, onComplete }) => (
113
- <OnboardingScreen
114
- slides={config.slides}
115
- onComplete={onComplete}
116
- showSkipButton={config.showSkipButton ?? true}
117
- showBackButton={config.showBackButton ?? true}
118
- showProgressBar={config.showProgressBar ?? true}
119
- themeColors={config.themeColors}
120
- translations={config.translations}
121
- />
122
- );
123
-
124
- const PaywallState: React.FC<{
125
- config: ManagedSubscriptionFlowProps["paywall"];
126
- packages: any[];
127
- isPremium: boolean;
128
- credits: number | null;
129
- isSyncing: boolean;
130
- onPurchase: (pkgId: string) => Promise<any>;
131
- onRestore: () => Promise<any>;
132
- onClose: (purchased: boolean) => void;
133
- }> = ({ config, packages, isPremium, credits, isSyncing, onPurchase, onRestore, onClose }) => {
134
- const [purchaseSuccessful, setPurchaseSuccessful] = React.useState(false);
135
-
136
- const handlePurchase = async (pkgId: string) => {
137
- const result = await onPurchase(pkgId);
138
- if (result?.success) {
139
- setPurchaseSuccessful(true);
140
- }
141
- return result;
142
- };
143
-
144
- const handleClose = () => {
145
- onClose(purchaseSuccessful);
146
- };
147
-
148
- return (
149
- <PaywallScreen
150
- translations={config.translations}
151
- legalUrls={config.legalUrls}
152
- features={config.features}
153
- bestValueIdentifier={config.bestValueIdentifier}
154
- creditsLabel={config.creditsLabel}
155
- heroImage={config.heroImage}
156
- source="onboarding"
157
- packages={packages}
158
- isPremium={isPremium}
159
- credits={credits}
160
- isSyncing={isSyncing}
161
- onPurchase={handlePurchase}
162
- onRestore={onRestore}
163
- onClose={handleClose}
164
- />
165
- );
166
- };
167
-
168
- const FeedbackState: React.FC<{
169
- config: ManagedSubscriptionFlowProps["feedback"];
170
- onClose: () => void;
171
- }> = ({ config, onClose }) => {
172
- const { submit: internalSubmit } = usePaywallFeedbackSubmit();
173
-
174
- const handleSubmit = async (data: { reason: string; otherText?: string }) => {
175
- if (config.onSubmit) {
176
- await config.onSubmit(data);
177
- } else {
178
- const description = data.otherText ? `${data.reason}: ${data.otherText}` : data.reason;
179
- await internalSubmit(description);
180
- }
181
- };
182
-
183
- return (
184
- <PaywallFeedbackScreen
185
- onClose={onClose}
186
- onSubmit={handleSubmit}
187
- translations={config.translations}
188
- />
189
- );
190
- };
191
-
192
- // ============================================================================
193
- // MAIN COMPONENT
194
- // ============================================================================
195
-
196
- const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
81
+ const ManagedSubscriptionFlowInner: React.FC<ManagedSubscriptionFlowProps> = ({
197
82
  children,
198
- navigation,
199
83
  islocalizationReady,
200
84
  splash,
201
85
  onboarding,
@@ -205,9 +89,6 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
205
89
  }) => {
206
90
  const tokens = useAppDesignTokens();
207
91
  const status = useSubscriptionFlowStatus();
208
- const { isInitialized: isSplashComplete } = useSplashFlow({
209
- duration: splash?.duration || 0,
210
- });
211
92
 
212
93
  // Premium hooks
213
94
  const { isPremium, isSyncing, credits, isLoading: isPremiumLoading } = usePremiumStatus();
@@ -220,32 +101,26 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
220
101
  const completePaywall = useSubscriptionFlowStore((s) => s.completePaywall);
221
102
  const hideFeedback = useSubscriptionFlowStore((s) => s.hideFeedback);
222
103
  const showFeedbackScreen = useSubscriptionFlowStore((s) => s.showFeedbackScreen);
223
-
224
104
  const showFeedback = useSubscriptionFlowStore((s) => s.showFeedback);
225
105
 
226
106
  // ========================================================================
227
107
  // STATE TRANSITIONS
228
108
  // ========================================================================
229
109
 
230
- // CHECK_PREMIUM state transition logic
231
110
  useEffect(() => {
232
111
  if (status === SubscriptionFlowStatus.CHECK_PREMIUM && !isPremiumLoading) {
233
112
  const paywallShown = useSubscriptionFlowStore.getState().paywallShown;
234
113
 
235
114
  if (isPremium) {
236
- // User is premium, go to ready
237
115
  completePaywall(true);
238
116
  } else if (!paywallShown) {
239
- // User not premium and paywall not shown, show paywall
240
117
  showPaywall();
241
118
  } else {
242
- // Paywall already shown, go to ready
243
119
  completePaywall(false);
244
120
  }
245
121
  }
246
122
  }, [status, isPremium, isPremiumLoading, showPaywall, completePaywall]);
247
123
 
248
- // Show feedback when needed
249
124
  useEffect(() => {
250
125
  if (status === SubscriptionFlowStatus.READY && showFeedback) {
251
126
  showFeedbackScreen();
@@ -253,21 +128,18 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
253
128
  }, [status, showFeedback, showFeedbackScreen]);
254
129
 
255
130
  // ========================================================================
256
- // RENDER
131
+ // RENDER BY STATE
257
132
  // ========================================================================
258
133
 
259
- // Wait for localization
260
134
  if (!islocalizationReady || status === SubscriptionFlowStatus.INITIALIZING) {
261
135
  return <InitializingState tokens={tokens} splash={splash} />;
262
136
  }
263
137
 
264
- // Render by state
265
138
  switch (status) {
266
139
  case SubscriptionFlowStatus.ONBOARDING:
267
140
  return <OnboardingState config={onboarding} onComplete={completeOnboarding} />;
268
141
 
269
142
  case SubscriptionFlowStatus.CHECK_PREMIUM:
270
- // Show loading while checking premium
271
143
  return <InitializingState tokens={tokens} splash={splash} />;
272
144
 
273
145
  case SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL:
@@ -286,31 +158,20 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
286
158
 
287
159
  case SubscriptionFlowStatus.READY:
288
160
  return (
289
- <>
290
- {children}
291
-
292
- {offline && (
293
- <OfflineBanner
294
- visible={offline.isOffline}
295
- message={offline.message}
296
- backgroundColor={offline.backgroundColor || tokens.colors.error}
297
- position={offline.position || "top"}
298
- />
299
- )}
300
-
301
- {showFeedback && (
302
- <FeedbackState
303
- config={feedback}
304
- onClose={hideFeedback}
305
- />
306
- )}
307
- </>
161
+ <ReadyState
162
+ children={children}
163
+ offline={offline}
164
+ feedbackConfig={feedback}
165
+ showFeedback={showFeedback}
166
+ tokens={tokens}
167
+ onFeedbackClose={hideFeedback}
168
+ />
308
169
  );
309
170
 
310
171
  default:
311
172
  return <InitializingState tokens={tokens} splash={splash} />;
312
173
  }
313
- });
174
+ };
314
175
 
315
176
  ManagedSubscriptionFlowInner.displayName = "ManagedSubscriptionFlowInner";
316
177