@umituz/react-native-subscription 2.27.4 → 2.27.6

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.27.4",
3
+ "version": "2.27.6",
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",
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * Fetches user credits with TanStack Query best practices.
5
5
  * Uses status-based state management for reliable loading detection.
6
- * Auto-initializes free credits for registered users only.
6
+ * Free credits initialization is delegated to useFreeCreditsInit hook.
7
7
  */
8
8
 
9
9
  import { useQuery } from "@umituz/react-native-design-system";
10
- import { useCallback, useMemo, useEffect, useState } from "react";
10
+ import { useCallback, useMemo } from "react";
11
11
  import {
12
12
  useAuthStore,
13
13
  selectUserId,
@@ -19,6 +19,7 @@ import {
19
19
  getCreditsConfig,
20
20
  isCreditsRepositoryConfigured,
21
21
  } from "../../infrastructure/repositories/CreditsRepositoryProvider";
22
+ import { useFreeCreditsInit } from "./useFreeCreditsInit";
22
23
 
23
24
  declare const __DEV__: boolean;
24
25
 
@@ -27,8 +28,6 @@ export const creditsQueryKeys = {
27
28
  user: (userId: string) => ["credits", userId] as const,
28
29
  };
29
30
 
30
- const freeCreditsInitAttempted = new Set<string>();
31
-
32
31
  export type CreditsLoadStatus = "idle" | "loading" | "initializing" | "ready" | "error";
33
32
 
34
33
  export interface UseCreditsResult {
@@ -59,7 +58,6 @@ export const useCredits = (): UseCreditsResult => {
59
58
  const userId = useAuthStore(selectUserId);
60
59
  const isAnonymous = useAuthStore(selectIsAnonymous);
61
60
  const isRegisteredUser = !!userId && !isAnonymous;
62
- const [isInitializingFreeCredits, setIsInitializingFreeCredits] = useState(false);
63
61
 
64
62
  const isConfigured = isCreditsRepositoryConfigured();
65
63
  const config = getCreditsConfig();
@@ -96,47 +94,18 @@ export const useCredits = (): UseCreditsResult => {
96
94
  });
97
95
 
98
96
  const credits = data ?? null;
99
- const freeCredits = config.freeCredits ?? 0;
100
- const autoInit = config.autoInitializeFreeCredits !== false && freeCredits > 0;
101
97
  const querySuccess = status === "success";
102
-
103
- useEffect(() => {
104
- const shouldInitFreeCredits =
105
- querySuccess &&
106
- userId &&
107
- isRegisteredUser &&
108
- isConfigured &&
109
- !credits &&
110
- autoInit &&
111
- !freeCreditsInitAttempted.has(userId);
112
-
113
- if (shouldInitFreeCredits) {
114
- freeCreditsInitAttempted.add(userId);
115
- setIsInitializingFreeCredits(true);
116
-
117
- if (typeof __DEV__ !== "undefined" && __DEV__) {
118
- console.log("[useCredits] Initializing free credits:", userId.slice(0, 8));
119
- }
120
-
121
- const repository = getCreditsRepository();
122
- repository.initializeFreeCredits(userId).then((result) => {
123
- setIsInitializingFreeCredits(false);
124
-
125
- if (result.success) {
126
- if (typeof __DEV__ !== "undefined" && __DEV__) {
127
- console.log("[useCredits] Free credits initialized:", result.data?.credits);
128
- }
129
- refetch();
130
- } else if (typeof __DEV__ !== "undefined" && __DEV__) {
131
- console.warn("[useCredits] Free credits init failed:", result.error?.message);
132
- }
133
- });
134
- } else if (querySuccess && userId && isAnonymous && !credits && autoInit) {
135
- if (typeof __DEV__ !== "undefined" && __DEV__) {
136
- console.log("[useCredits] Skipping free credits - anonymous user must register first");
137
- }
138
- }
139
- }, [querySuccess, userId, isRegisteredUser, isAnonymous, isConfigured, credits, autoInit, refetch]);
98
+ const hasCreditsData = (credits?.credits ?? 0) > 0;
99
+
100
+ // Delegate free credits initialization to dedicated hook
101
+ const { isInitializing, needsInit } = useFreeCreditsInit({
102
+ userId,
103
+ isRegisteredUser,
104
+ isAnonymous,
105
+ hasCredits: hasCreditsData,
106
+ querySuccess,
107
+ onInitComplete: refetch,
108
+ });
140
109
 
141
110
  const derivedValues = useMemo(() => {
142
111
  const has = (credits?.credits ?? 0) > 0;
@@ -149,7 +118,12 @@ export const useCredits = (): UseCreditsResult => {
149
118
  [credits]
150
119
  );
151
120
 
152
- const loadStatus = deriveLoadStatus(status, isInitializingFreeCredits, queryEnabled);
121
+ // Include needsInit in initializing state for accurate loading detection
122
+ const loadStatus = deriveLoadStatus(
123
+ status,
124
+ isInitializing || needsInit,
125
+ queryEnabled
126
+ );
153
127
  const isCreditsLoaded = loadStatus === "ready";
154
128
  const isLoading = loadStatus === "loading" || loadStatus === "initializing";
155
129
 
@@ -0,0 +1,87 @@
1
+ /**
2
+ * useFreeCreditsInit Hook
3
+ *
4
+ * Handles free credits initialization for newly registered users.
5
+ * Separated from useCredits for better maintainability and testability.
6
+ */
7
+
8
+ import { useState, useEffect, useCallback } from "react";
9
+ import {
10
+ getCreditsRepository,
11
+ getCreditsConfig,
12
+ isCreditsRepositoryConfigured,
13
+ } from "../../infrastructure/repositories/CreditsRepositoryProvider";
14
+
15
+ declare const __DEV__: boolean;
16
+
17
+ const freeCreditsInitAttempted = new Set<string>();
18
+
19
+ export interface UseFreeCreditsInitParams {
20
+ userId: string | null | undefined;
21
+ isRegisteredUser: boolean;
22
+ isAnonymous: boolean;
23
+ hasCredits: boolean;
24
+ querySuccess: boolean;
25
+ onInitComplete: () => void;
26
+ }
27
+
28
+ export interface UseFreeCreditsInitResult {
29
+ isInitializing: boolean;
30
+ needsInit: boolean;
31
+ }
32
+
33
+ export function useFreeCreditsInit(params: UseFreeCreditsInitParams): UseFreeCreditsInitResult {
34
+ const { userId, isRegisteredUser, isAnonymous, hasCredits, querySuccess, onInitComplete } = params;
35
+ const [isInitializing, setIsInitializing] = useState(false);
36
+
37
+ const isConfigured = isCreditsRepositoryConfigured();
38
+ const config = getCreditsConfig();
39
+ const freeCredits = config.freeCredits ?? 0;
40
+ const autoInit = config.autoInitializeFreeCredits !== false && freeCredits > 0;
41
+
42
+ const needsInit =
43
+ querySuccess &&
44
+ !!userId &&
45
+ isRegisteredUser &&
46
+ isConfigured &&
47
+ !hasCredits &&
48
+ autoInit &&
49
+ !freeCreditsInitAttempted.has(userId);
50
+
51
+ const initializeFreeCredits = useCallback(async (uid: string) => {
52
+ freeCreditsInitAttempted.add(uid);
53
+ setIsInitializing(true);
54
+
55
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
56
+ console.log("[useFreeCreditsInit] Initializing free credits:", uid.slice(0, 8));
57
+ }
58
+
59
+ const repository = getCreditsRepository();
60
+ const result = await repository.initializeFreeCredits(uid);
61
+ setIsInitializing(false);
62
+
63
+ if (result.success) {
64
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
65
+ console.log("[useFreeCreditsInit] Free credits initialized:", result.data?.credits);
66
+ }
67
+ onInitComplete();
68
+ } else if (typeof __DEV__ !== "undefined" && __DEV__) {
69
+ console.warn("[useFreeCreditsInit] Free credits init failed:", result.error?.message);
70
+ }
71
+ }, [onInitComplete]);
72
+
73
+ useEffect(() => {
74
+ if (needsInit && userId) {
75
+ initializeFreeCredits(userId);
76
+ } else if (querySuccess && userId && isAnonymous && !hasCredits && autoInit) {
77
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
78
+ console.log("[useFreeCreditsInit] Skipping - anonymous user must register first");
79
+ }
80
+ }
81
+ }, [needsInit, userId, querySuccess, isAnonymous, hasCredits, autoInit, initializeFreeCredits]);
82
+
83
+ return {
84
+ isInitializing,
85
+ needsInit,
86
+ };
87
+ }