@umituz/react-native-subscription 2.43.21 → 2.44.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.43.21",
3
+ "version": "2.44.0",
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",
@@ -1,6 +1,14 @@
1
- import React, { useState, useEffect, useCallback } from "react";
1
+ /**
2
+ * ManagedSubscriptionFlow
3
+ *
4
+ * State machine-based flow orchestration.
5
+ * No complex if/else - clean state transitions.
6
+ */
7
+
8
+ import React, { useEffect, useCallback } from "react";
2
9
  import type { NavigationProp } from "@react-navigation/native";
3
10
  import type { ImageSourcePropType } from "react-native";
11
+ import { View } from "react-native";
4
12
  import { SplashScreen, useSplashFlow } from "@umituz/react-native-design-system/molecules";
5
13
  import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
6
14
  import { OfflineBanner } from "@umituz/react-native-design-system/offline";
@@ -8,10 +16,10 @@ import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
8
16
  import { usePremiumStatus } from "../../presentation/usePremiumStatus";
9
17
  import { usePremiumPackages } from "../../presentation/usePremiumPackages";
10
18
  import { usePremiumActions } from "../../presentation/usePremiumActions";
11
- import { usePaywallOrchestrator } from "../../../paywall/hooks/usePaywallOrchestrator";
12
- import { PaywallFeedbackScreen } from "../../../subscription/presentation/components/feedback/PaywallFeedbackScreen";
13
- import { PaywallFeedbackTranslations } from "../../../subscription/presentation/components/feedback/PaywallFeedbackScreen.types";
14
- import { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../../../paywall/entities/types";
19
+ import { useSubscriptionFlowStore, SubscriptionFlowStatus } from "../useSubscriptionFlow";
20
+ import { PaywallFeedbackScreen } from "./feedback/PaywallFeedbackScreen";
21
+ import type { PaywallFeedbackTranslations } from "./feedback/PaywallFeedbackScreen.types";
22
+ import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../../../paywall/entities/types";
15
23
  import { usePaywallFeedbackSubmit } from "../../../../presentation/hooks/feedback/useFeedbackSubmit";
16
24
  import { PaywallScreen } from "../../../paywall/components/PaywallScreen";
17
25
 
@@ -66,19 +74,124 @@ export interface ManagedSubscriptionFlowProps {
66
74
  };
67
75
  }
68
76
 
69
- /**
70
- * ManagedSubscriptionFlow
71
- *
72
- * A high-level layout component that orchestrates the entire application flow:
73
- * Splash -> Onboarding -> [Managed Paywall Screens] -> Main Application Stack.
74
- *
75
- * Use this to reduce AppNavigator boilerplate to nearly zero.
76
- */
77
77
  import {
78
78
  SubscriptionFlowProvider,
79
79
  useSubscriptionFlowStatus
80
80
  } from "../providers/SubscriptionFlowProvider";
81
- import { useSubscriptionFlowStore, SubscriptionFlowStatus } from "../useSubscriptionFlow";
81
+
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
+ // ============================================================================
82
195
 
83
196
  const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
84
197
  children,
@@ -96,165 +209,111 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
96
209
  duration: splash?.duration || 0,
97
210
  });
98
211
 
99
- // Hooks for paywall (called at component level, not inside conditional render)
100
- const { isPremium, isSyncing, credits } = usePremiumStatus();
212
+ // Premium hooks
213
+ const { isPremium, isSyncing, credits, isLoading: isPremiumLoading } = usePremiumStatus();
101
214
  const { packages } = usePremiumPackages();
102
- const { purchasePackage: originalPurchasePackage, restorePurchase } = usePremiumActions();
103
- const { closePostOnboardingPaywall } = useSubscriptionFlowStore();
215
+ const { purchasePackage, restorePurchase } = usePremiumActions();
104
216
 
105
- // Track if purchase was successful to avoid showing feedback
106
- const [purchaseSuccessful, setPurchaseSuccessful] = useState(false);
217
+ // Store actions
218
+ const completeOnboarding = useSubscriptionFlowStore((s) => s.completeOnboarding);
219
+ const showPaywall = useSubscriptionFlowStore((s) => s.showPaywall);
220
+ const completePaywall = useSubscriptionFlowStore((s) => s.completePaywall);
221
+ const hideFeedback = useSubscriptionFlowStore((s) => s.hideFeedback);
222
+ const showFeedbackScreen = useSubscriptionFlowStore((s) => s.showFeedbackScreen);
107
223
 
108
- // Wrap purchasePackage to track success
109
- const purchasePackage = useCallback(async (pkgId: string) => {
110
- const result = await originalPurchasePackage(pkgId);
111
- if (result?.success) {
112
- setPurchaseSuccessful(true);
113
- }
114
- return result;
115
- }, [originalPurchasePackage]);
224
+ const showFeedback = useSubscriptionFlowStore((s) => s.showFeedback);
116
225
 
117
- // Wrap onClose to pass purchase status
118
- const handleClose = useCallback(() => {
119
- closePostOnboardingPaywall({ purchased: purchaseSuccessful });
120
- }, [closePostOnboardingPaywall, purchaseSuccessful]);
226
+ // ========================================================================
227
+ // STATE TRANSITIONS
228
+ // ========================================================================
121
229
 
122
- const [isNavReady, setIsNavReady] = useState(false);
123
-
124
- // Mark navigation tree as ready after splash and localization
230
+ // CHECK_PREMIUM state transition logic
125
231
  useEffect(() => {
126
- if (isSplashComplete && islocalizationReady) {
127
- const timer = setTimeout(() => setIsNavReady(true), 500);
128
- return () => clearTimeout(timer);
129
- }
130
- }, [isSplashComplete, islocalizationReady]);
131
-
132
- const {
133
- flowState,
134
- setShowFeedback,
135
- completeOnboarding
136
- } = usePaywallOrchestrator({
137
- navigation,
138
- isNavReady,
139
- isLocalizationReady: islocalizationReady,
140
- translations: paywall.translations,
141
- features: paywall.features,
142
- legalUrls: paywall.legalUrls,
143
- heroImage: paywall.heroImage,
144
- bestValueIdentifier: paywall.bestValueIdentifier,
145
- creditsLabel: paywall.creditsLabel,
146
- disableNavigation: true, // Paywall is rendered inline, don't navigate
147
- });
148
-
149
- const { submit: internalSubmit } = usePaywallFeedbackSubmit();
232
+ if (status === SubscriptionFlowStatus.CHECK_PREMIUM && !isPremiumLoading) {
233
+ const paywallShown = useSubscriptionFlowStore.getState().paywallShown;
150
234
 
151
- const handleFeedbackSubmit = async (data: { reason: string; otherText?: string }) => {
152
- if (feedback.onSubmit) {
153
- await feedback.onSubmit(data);
154
- } else {
155
- const description = data.otherText ? `${data.reason}: ${data.otherText}` : data.reason;
156
- await internalSubmit(description);
235
+ if (isPremium) {
236
+ // User is premium, go to ready
237
+ completePaywall(true);
238
+ } else if (!paywallShown) {
239
+ // User not premium and paywall not shown, show paywall
240
+ showPaywall();
241
+ } else {
242
+ // Paywall already shown, go to ready
243
+ completePaywall(false);
244
+ }
157
245
  }
158
- };
246
+ }, [status, isPremium, isPremiumLoading, showPaywall, completePaywall]);
159
247
 
160
- // 1. Loading / Initialization View
161
- if (status === SubscriptionFlowStatus.INITIALIZING || !islocalizationReady) {
162
- if (__DEV__) {
163
- console.log('[ManagedSubscriptionFlow] ⏳ Rendering Initialization state', {
164
- status,
165
- islocalizationReady,
166
- hasSplashConfig: !!splash,
167
- isSplashComplete
168
- });
248
+ // Show feedback when needed
249
+ useEffect(() => {
250
+ if (status === SubscriptionFlowStatus.READY && showFeedback) {
251
+ showFeedbackScreen();
169
252
  }
253
+ }, [status, showFeedback, showFeedbackScreen]);
170
254
 
171
- // Even if no splash config provided, we should show a basic splash to avoid white screen
172
- return (
173
- <SplashScreen
174
- appName={splash?.appName || "Loading..."}
175
- tagline={splash?.tagline || "Please wait while we set things up"}
176
- colors={tokens.colors}
177
- />
178
- );
179
- }
255
+ // ========================================================================
256
+ // RENDER
257
+ // ========================================================================
180
258
 
181
- if (__DEV__) {
182
- console.log('[ManagedSubscriptionFlow] 🔄 Rendering Main state', {
183
- status,
184
- isSplashComplete,
185
- islocalizationReady,
186
- showFeedback: flowState.showFeedback
187
- });
259
+ // Wait for localization
260
+ if (!islocalizationReady || status === SubscriptionFlowStatus.INITIALIZING) {
261
+ return <InitializingState tokens={tokens} splash={splash} />;
188
262
  }
189
263
 
190
- // 2. Onboarding View
191
- if (status === SubscriptionFlowStatus.ONBOARDING) {
192
- return (
193
- <OnboardingScreen
194
- slides={onboarding.slides}
195
- onComplete={completeOnboarding}
196
- showSkipButton={onboarding.showSkipButton ?? true}
197
- showBackButton={onboarding.showBackButton ?? true}
198
- showProgressBar={onboarding.showProgressBar ?? true}
199
- themeColors={onboarding.themeColors}
200
- translations={onboarding.translations}
201
- />
202
- );
203
- }
204
-
205
- // 2.5. Post-Onboarding Paywall View
206
- if (status === SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL) {
207
- if (__DEV__) {
208
- console.log('[ManagedSubscriptionFlow] 💳 Rendering Post-Onboarding Paywall', {
209
- hasPackages: flowState.showPostOnboardingPaywall
210
- });
211
- }
264
+ // Render by state
265
+ switch (status) {
266
+ case SubscriptionFlowStatus.ONBOARDING:
267
+ return <OnboardingState config={onboarding} onComplete={completeOnboarding} />;
212
268
 
213
- return (
214
- <PaywallScreen
215
- translations={paywall.translations}
216
- legalUrls={paywall.legalUrls}
217
- features={paywall.features}
218
- bestValueIdentifier={paywall.bestValueIdentifier}
219
- creditsLabel={paywall.creditsLabel}
220
- heroImage={paywall.heroImage}
221
- source="onboarding"
222
- packages={packages}
223
- isPremium={isPremium}
224
- credits={credits}
225
- isSyncing={isSyncing}
226
- onPurchase={purchasePackage}
227
- onRestore={restorePurchase}
228
- onClose={handleClose}
229
- />
230
- );
231
- }
269
+ case SubscriptionFlowStatus.CHECK_PREMIUM:
270
+ // Show loading while checking premium
271
+ return <InitializingState tokens={tokens} splash={splash} />;
232
272
 
233
- // 3. Application Content + Overlays
234
- return (
235
- <>
236
- {children}
237
-
238
- {offline && (
239
- <OfflineBanner
240
- visible={offline.isOffline}
241
- message={offline.message}
242
- backgroundColor={offline.backgroundColor || tokens.colors.error}
243
- position={offline.position || "top"}
273
+ case SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL:
274
+ return (
275
+ <PaywallState
276
+ config={paywall}
277
+ packages={packages}
278
+ isPremium={isPremium}
279
+ credits={credits}
280
+ isSyncing={isSyncing}
281
+ onPurchase={purchasePackage}
282
+ onRestore={restorePurchase}
283
+ onClose={(purchased) => completePaywall(purchased)}
244
284
  />
245
- )}
285
+ );
246
286
 
247
- {flowState.showFeedback && (
248
- <PaywallFeedbackScreen
249
- onClose={() => setShowFeedback(false)}
250
- onSubmit={handleFeedbackSubmit}
251
- translations={feedback.translations}
252
- />
253
- )}
254
- </>
255
- );
287
+ case SubscriptionFlowStatus.READY:
288
+ 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
+ </>
308
+ );
309
+
310
+ default:
311
+ return <InitializingState tokens={tokens} splash={splash} />;
312
+ }
256
313
  });
257
314
 
315
+ ManagedSubscriptionFlowInner.displayName = "ManagedSubscriptionFlowInner";
316
+
258
317
  export const ManagedSubscriptionFlow: React.FC<ManagedSubscriptionFlowProps> = (props) => {
259
318
  return (
260
319
  <SubscriptionFlowProvider>
@@ -1,7 +1,8 @@
1
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.
2
+ * Subscription Flow State Machine
3
+ *
4
+ * Single source of truth for app flow state.
5
+ * Clean state transitions without complex if/else logic.
5
6
  */
6
7
 
7
8
  import { createStore } from "@umituz/react-native-design-system/storage";
@@ -10,9 +11,9 @@ import { subscriptionEventBus, FLOW_EVENTS } from "../../../shared/infrastructur
10
11
  export enum SubscriptionFlowStatus {
11
12
  INITIALIZING = "INITIALIZING",
12
13
  ONBOARDING = "ONBOARDING",
13
- PAYWALL = "PAYWALL",
14
- READY = "READY",
14
+ CHECK_PREMIUM = "CHECK_PREMIUM",
15
15
  POST_ONBOARDING_PAYWALL = "POST_ONBOARDING_PAYWALL",
16
+ READY = "READY",
16
17
  }
17
18
 
18
19
  export enum SyncStatus {
@@ -23,28 +24,46 @@ export enum SyncStatus {
23
24
  }
24
25
 
25
26
  export interface SubscriptionFlowState {
27
+ // Flow state
26
28
  status: SubscriptionFlowStatus;
29
+
30
+ // Sync state
27
31
  syncStatus: SyncStatus;
28
32
  syncError: string | null;
29
- isInitialized: boolean;
33
+
34
+ // Onboarding state
30
35
  isOnboardingComplete: boolean;
31
- showPostOnboardingPaywall: boolean;
32
- showFeedback: boolean;
36
+
37
+ // Paywall state
33
38
  paywallShown: boolean;
39
+
40
+ // Feedback state
41
+ showFeedback: boolean;
42
+
43
+ // Auth modal state
34
44
  isAuthModalOpen: boolean;
45
+
46
+ // Initialization flag
47
+ isInitialized: boolean;
35
48
  }
36
49
 
37
50
  export interface SubscriptionFlowActions {
38
- completeOnboarding: () => Promise<void>;
39
- closePostOnboardingPaywall: (params?: { purchased: boolean }) => Promise<void>;
40
- closeFeedback: () => void;
51
+ // Flow actions
52
+ completeOnboarding: () => void;
53
+ checkPremiumStatus: () => void;
54
+ showPaywall: () => void;
55
+ completePaywall: (purchased: boolean) => void;
56
+ showFeedbackScreen: () => void;
57
+ hideFeedback: () => void;
58
+
59
+ // Auth actions
41
60
  setAuthModalOpen: (open: boolean) => void;
42
- markPaywallShown: () => Promise<void>;
43
- setShowFeedback: (show: boolean) => void;
44
- resetFlow: () => Promise<void>;
45
- setInitialized: (initialized: boolean) => void;
46
- setStatus: (status: SubscriptionFlowStatus) => void;
61
+
62
+ // Sync actions
47
63
  setSyncStatus: (status: SyncStatus, error?: string | null) => void;
64
+
65
+ // Reset
66
+ resetFlow: () => void;
48
67
  }
49
68
 
50
69
  export type SubscriptionFlowStore = SubscriptionFlowState & SubscriptionFlowActions;
@@ -53,73 +72,103 @@ const initialState: SubscriptionFlowState = {
53
72
  status: SubscriptionFlowStatus.INITIALIZING,
54
73
  syncStatus: SyncStatus.IDLE,
55
74
  syncError: null,
56
- isInitialized: false,
57
75
  isOnboardingComplete: false,
58
- showPostOnboardingPaywall: false,
59
- showFeedback: false,
60
76
  paywallShown: false,
77
+ showFeedback: false,
61
78
  isAuthModalOpen: false,
79
+ isInitialized: false,
62
80
  };
63
81
 
82
+ /**
83
+ * State transition rules:
84
+ *
85
+ * INITIALIZING -> ONBOARDING (first launch)
86
+ * INITIALIZING -> CHECK_PREMIUM (onboarding already done)
87
+ *
88
+ * ONBOARDING -> CHECK_PREMIUM (onboarding completed)
89
+ *
90
+ * CHECK_PREMIUM -> READY (user is premium)
91
+ * CHECK_PREMIUM -> POST_ONBOARDING_PAYWALL (user not premium, paywall not shown)
92
+ * CHECK_PREMIUM -> READY (user not premium but paywall already shown)
93
+ *
94
+ * POST_ONBOARDING_PAYWALL -> READY (paywall closed)
95
+ *
96
+ * READY -> READY (stays ready, shows overlays when needed)
97
+ */
64
98
  export const useSubscriptionFlowStore = createStore<SubscriptionFlowState, SubscriptionFlowActions>({
65
99
  name: "subscription-flow-storage",
66
100
  initialState,
67
- persist: false,
101
+ persist: true,
68
102
  onRehydrate: (state) => {
69
103
  if (!state.isInitialized) {
70
104
  state.setInitialized(true);
105
+
106
+ // First time: show onboarding
107
+ state.setStatus(SubscriptionFlowStatus.INITIALIZING);
108
+ } else if (state.isOnboardingComplete) {
109
+ // Onboarding done, check premium status
110
+ state.setStatus(SubscriptionFlowStatus.CHECK_PREMIUM);
111
+ } else {
112
+ // Show onboarding
113
+ state.setStatus(SubscriptionFlowStatus.ONBOARDING);
71
114
  }
72
115
  },
73
116
  actions: (set) => ({
74
- completeOnboarding: async () => {
117
+ setInitialized: (initialized: boolean) => set({ isInitialized: initialized }),
118
+ setStatus: (status: SubscriptionFlowStatus) => set({ status }),
119
+
120
+ completeOnboarding: () => {
75
121
  set({
76
122
  isOnboardingComplete: true,
77
- showPostOnboardingPaywall: true,
78
- status: SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL,
123
+ status: SubscriptionFlowStatus.CHECK_PREMIUM,
79
124
  });
80
125
  subscriptionEventBus.emit(FLOW_EVENTS.ONBOARDING_COMPLETED, { timestamp: Date.now() });
81
126
  },
82
- closePostOnboardingPaywall: async (params?: { purchased: boolean }) => {
83
- const purchased = params?.purchased ?? false;
127
+
128
+ checkPremiumStatus: () => {
129
+ // This is a transient state - the component will check isPremium
130
+ // and transition accordingly
131
+ set({ status: SubscriptionFlowStatus.CHECK_PREMIUM });
132
+ },
133
+
134
+ showPaywall: () => {
84
135
  set({
85
- showPostOnboardingPaywall: false,
136
+ status: SubscriptionFlowStatus.POST_ONBOARDING_PAYWALL,
86
137
  paywallShown: true,
138
+ });
139
+ subscriptionEventBus.emit(FLOW_EVENTS.PAYWALL_SHOWN, { timestamp: Date.now() });
140
+ },
141
+
142
+ completePaywall: (purchased: boolean) => {
143
+ set({
87
144
  status: SubscriptionFlowStatus.READY,
88
- showFeedback: !purchased, // Only show feedback if NOT purchased
145
+ paywallShown: true,
146
+ showFeedback: !purchased, // Only show feedback if not purchased
89
147
  });
90
148
  subscriptionEventBus.emit(FLOW_EVENTS.PAYWALL_CLOSED, {
91
149
  timestamp: Date.now(),
92
150
  purchased
93
151
  });
94
152
  },
95
- closeFeedback: () => set({ showFeedback: false }),
153
+
154
+ showFeedbackScreen: () => set({ showFeedback: true }),
155
+ hideFeedback: () => set({ showFeedback: false }),
156
+
96
157
  setAuthModalOpen: (open: boolean) => set({ isAuthModalOpen: open }),
97
- setShowFeedback: (show: boolean) => set({ showFeedback: show }),
98
- markPaywallShown: async () => {
99
- set({ paywallShown: true });
100
- subscriptionEventBus.emit(FLOW_EVENTS.PAYWALL_SHOWN, { timestamp: Date.now() });
101
- },
102
- setInitialized: (initialized: boolean) => set((state) => {
103
- if (state.isInitialized === initialized) return state;
104
- return { isInitialized: initialized };
105
- }),
106
- setStatus: (status: SubscriptionFlowStatus) => set((state) => {
107
- if (state.status === status) return state;
108
- return { status };
109
- }),
110
- setSyncStatus: (syncStatus: SyncStatus, syncError: string | null = null) =>
158
+
159
+ setSyncStatus: (syncStatus: SyncStatus, syncError: string | null = null) =>
111
160
  set({ syncStatus, syncError }),
112
- resetFlow: async () => {
161
+
162
+ resetFlow: () => {
113
163
  set({
114
164
  status: SubscriptionFlowStatus.INITIALIZING,
115
165
  syncStatus: SyncStatus.IDLE,
116
166
  syncError: null,
117
- isInitialized: false, // Reset isInitialized to allow fresh initialization
118
167
  isOnboardingComplete: false,
119
- showPostOnboardingPaywall: false,
120
- showFeedback: false,
121
168
  paywallShown: false,
169
+ showFeedback: false,
122
170
  isAuthModalOpen: false,
171
+ isInitialized: false,
123
172
  });
124
173
  },
125
174
  }),