@umituz/react-native-subscription 2.10.2 → 2.10.3

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.10.2",
3
+ "version": "2.10.3",
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",
package/src/index.ts CHANGED
@@ -190,21 +190,15 @@ export {
190
190
  } from "./infrastructure/repositories/CreditsRepository";
191
191
 
192
192
  // =============================================================================
193
- // CREDITS SYSTEM - Context & Provider
193
+ // CREDITS SYSTEM - Configuration (Module-Level Provider)
194
194
  // =============================================================================
195
195
 
196
196
  export {
197
- CreditsContext,
198
- useCreditsContext,
199
- useCreditsConfig,
200
- useCreditsRepository,
201
- type CreditsContextValue,
202
- } from "./presentation/context/CreditsContext";
203
-
204
- export {
205
- CreditsProvider,
206
- type CreditsProviderProps,
207
- } from "./presentation/providers/CreditsProvider";
197
+ configureCreditsRepository,
198
+ getCreditsRepository,
199
+ getCreditsConfig,
200
+ resetCreditsRepository,
201
+ } from "./infrastructure/repositories/CreditsRepositoryProvider";
208
202
 
209
203
  // =============================================================================
210
204
  // CREDITS SYSTEM - Hooks
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Credits Repository Provider
3
+ * Module-level singleton for credits repository configuration
4
+ * Replaces Context API with a simpler, testable approach
5
+ */
6
+
7
+ import type { CreditsConfig } from "../../domain/entities/Credits";
8
+ import { DEFAULT_CREDITS_CONFIG } from "../../domain/entities/Credits";
9
+ import type { CreditsRepository } from "./CreditsRepository";
10
+ import { createCreditsRepository } from "./CreditsRepository";
11
+
12
+ let globalRepository: CreditsRepository | null = null;
13
+ let globalConfig: CreditsConfig = DEFAULT_CREDITS_CONFIG;
14
+
15
+ /**
16
+ * Configure credits repository for the application
17
+ * Must be called once during app initialization
18
+ */
19
+ export function configureCreditsRepository(config: Partial<CreditsConfig>): void {
20
+ globalConfig = {
21
+ ...DEFAULT_CREDITS_CONFIG,
22
+ ...config,
23
+ };
24
+ globalRepository = createCreditsRepository(globalConfig);
25
+ }
26
+
27
+ /**
28
+ * Get the configured credits repository
29
+ * Throws if repository not configured
30
+ */
31
+ export function getCreditsRepository(): CreditsRepository {
32
+ if (!globalRepository) {
33
+ throw new Error(
34
+ "CreditsRepository not configured. Call configureCreditsRepository() first."
35
+ );
36
+ }
37
+ return globalRepository;
38
+ }
39
+
40
+ /**
41
+ * Get the current credits configuration
42
+ */
43
+ export function getCreditsConfig(): CreditsConfig {
44
+ return globalConfig;
45
+ }
46
+
47
+ /**
48
+ * Reset repository (for testing)
49
+ */
50
+ export function resetCreditsRepository(): void {
51
+ globalRepository = null;
52
+ globalConfig = DEFAULT_CREDITS_CONFIG;
53
+ }
@@ -28,11 +28,8 @@ export const createPaywallFeedbackStyles = (
28
28
  backgroundColor: tokens.colors.surface,
29
29
  borderRadius: 24,
30
30
  padding: 24,
31
- shadowColor: "#000",
32
- shadowOffset: { width: 0, height: 8 },
33
- shadowOpacity: 0.25,
34
- shadowRadius: 16,
35
- elevation: 8,
31
+ borderWidth: 1,
32
+ borderColor: tokens.colors.border,
36
33
  },
37
34
  header: {
38
35
  alignItems: "center",
@@ -5,8 +5,7 @@
5
5
 
6
6
  import React, { useEffect } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
- import { useAppDesignTokens, BaseModal, useResponsive } from "@umituz/react-native-design-system";
9
- import { useLocalization } from "@umituz/react-native-localization";
8
+ import { BaseModal, useResponsive } from "@umituz/react-native-design-system";
10
9
  import type { PurchasesPackage } from "react-native-purchases";
11
10
  import { usePaywall } from "../../hooks/usePaywall";
12
11
  import { PaywallHeader } from "./PaywallHeader";
@@ -35,8 +34,12 @@ export interface PaywallModalProps {
35
34
  onRestore?: () => Promise<void>;
36
35
  subscriptionFeatures?: Array<{ icon: string; text: string }>;
37
36
  isLoading?: boolean;
38
- title?: string;
39
- subtitle?: string;
37
+ title: string;
38
+ subtitle: string;
39
+ creditsTabLabel: string;
40
+ subscriptionTabLabel: string;
41
+ purchaseButtonText: string;
42
+ subscribeButtonText: string;
40
43
  privacyUrl?: string;
41
44
  termsUrl?: string;
42
45
  privacyText?: string;
@@ -60,6 +63,10 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
60
63
  isLoading = false,
61
64
  title,
62
65
  subtitle,
66
+ creditsTabLabel,
67
+ subscriptionTabLabel,
68
+ purchaseButtonText,
69
+ subscribeButtonText,
63
70
  privacyUrl,
64
71
  termsUrl,
65
72
  privacyText,
@@ -67,8 +74,6 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
67
74
  restoreButtonText,
68
75
  } = props;
69
76
 
70
- const tokens = useAppDesignTokens();
71
- const { t } = useLocalization();
72
77
  const { modalLayout } = useResponsive();
73
78
 
74
79
  const {
@@ -86,9 +91,6 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
86
91
  onSubscriptionPurchase,
87
92
  });
88
93
 
89
- const displayTitle = title || t("paywall.title");
90
- const displaySubtitle = subtitle || t("paywall.subtitle");
91
-
92
94
  useEffect(() => {
93
95
  if (__DEV__) {
94
96
  console.log("[PaywallModal] State:", {
@@ -105,8 +107,8 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
105
107
  <BaseModal visible={visible} onClose={onClose}>
106
108
  <View style={styles.container}>
107
109
  <PaywallHeader
108
- title={displayTitle}
109
- subtitle={displaySubtitle}
110
+ title={title}
111
+ subtitle={subtitle}
110
112
  onClose={onClose}
111
113
  variant="fullscreen"
112
114
  />
@@ -114,8 +116,8 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
114
116
  <PaywallTabBar
115
117
  activeTab={activeTab}
116
118
  onTabChange={handleTabChange}
117
- creditsLabel={t("paywall.tabs.credits")}
118
- subscriptionLabel={t("paywall.tabs.subscription")}
119
+ creditsLabel={creditsTabLabel}
120
+ subscriptionLabel={subscriptionTabLabel}
119
121
  />
120
122
 
121
123
  <View style={styles.tabContent}>
@@ -128,7 +130,7 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
128
130
  currentCredits={currentCredits}
129
131
  requiredCredits={requiredCredits}
130
132
  isLoading={isLoading}
131
- purchaseButtonText={t("paywall.purchase")}
133
+ purchaseButtonText={purchaseButtonText}
132
134
  />
133
135
  ) : (
134
136
  <SubscriptionTabContent
@@ -138,7 +140,7 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
138
140
  onPurchase={handleSubscriptionPurchase}
139
141
  features={subscriptionFeatures}
140
142
  isLoading={isLoading}
141
- purchaseButtonText={t("paywall.subscribe")}
143
+ purchaseButtonText={subscribeButtonText}
142
144
  onRestore={onRestore}
143
145
  privacyUrl={privacyUrl}
144
146
  termsUrl={termsUrl}
@@ -20,15 +20,15 @@ export interface SubscriptionModalProps {
20
20
  packages: PurchasesPackage[];
21
21
  onPurchase: (pkg: PurchasesPackage) => Promise<boolean>;
22
22
  onRestore: () => Promise<boolean>;
23
- title?: string;
23
+ title: string;
24
24
  subtitle?: string;
25
25
  features?: Array<{ icon: string; text: string }>;
26
26
  isLoading?: boolean;
27
- purchaseButtonText?: string;
28
- restoreButtonText?: string;
29
- loadingText?: string;
30
- emptyText?: string;
31
- processingText?: string;
27
+ purchaseButtonText: string;
28
+ restoreButtonText: string;
29
+ loadingText: string;
30
+ emptyText: string;
31
+ processingText: string;
32
32
  privacyUrl?: string;
33
33
  termsUrl?: string;
34
34
  privacyText?: string;
@@ -43,15 +43,15 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
43
43
  packages,
44
44
  onPurchase,
45
45
  onRestore,
46
- title = "Go Premium",
46
+ title,
47
47
  subtitle,
48
48
  features = [],
49
49
  isLoading = false,
50
- purchaseButtonText = "Subscribe",
51
- restoreButtonText = "Restore Purchases",
52
- loadingText = "Loading...",
53
- emptyText = "No packages",
54
- processingText = "Processing...",
50
+ purchaseButtonText,
51
+ restoreButtonText,
52
+ loadingText,
53
+ emptyText,
54
+ processingText,
55
55
  privacyUrl,
56
56
  termsUrl,
57
57
  privacyText,
@@ -6,7 +6,6 @@
6
6
  import React, { useMemo } from "react";
7
7
  import { View, StyleSheet, ScrollView } from "react-native";
8
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
- import { useLocalization } from "@umituz/react-native-localization";
10
9
  import type { PurchasesPackage } from "react-native-purchases";
11
10
  import { PaywallFeaturesList } from "./PaywallFeaturesList";
12
11
  import { SubscriptionPackageList } from "./SubscriptionPackageList";
@@ -20,9 +19,11 @@ interface SubscriptionTabContentProps {
20
19
  onRestore?: () => void;
21
20
  features?: Array<{ icon: string; text: string }>;
22
21
  isLoading?: boolean;
23
- purchaseButtonText?: string;
24
- processingText?: string;
25
- restoreButtonText?: string;
22
+ purchaseButtonText: string;
23
+ processingText: string;
24
+ restoreButtonText: string;
25
+ loadingText: string;
26
+ emptyText: string;
26
27
  privacyUrl?: string;
27
28
  termsUrl?: string;
28
29
  privacyText?: string;
@@ -57,20 +58,14 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
57
58
  purchaseButtonText,
58
59
  processingText,
59
60
  restoreButtonText,
61
+ loadingText,
62
+ emptyText,
60
63
  privacyUrl,
61
64
  termsUrl,
62
65
  privacyText,
63
66
  termsOfServiceText,
64
67
  }) => {
65
68
  const tokens = useAppDesignTokens();
66
- const { t } = useLocalization();
67
-
68
- const displayPurchaseButtonText =
69
- purchaseButtonText || t("paywall.subscribe");
70
- const displayProcessingText =
71
- processingText || t("paywall.processing");
72
- const displayRestoreButtonText =
73
- restoreButtonText || t("paywall.restore");
74
69
 
75
70
  const sortedPackages = useMemo(() => sortPackages(packages), [packages]);
76
71
 
@@ -86,8 +81,8 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
86
81
  isLoading={isLoading}
87
82
  selectedPkg={selectedPackage}
88
83
  onSelect={onSelectPackage}
89
- loadingText={t("paywall.loading")}
90
- emptyText={t("paywall.empty")}
84
+ loadingText={loadingText}
85
+ emptyText={emptyText}
91
86
  />
92
87
 
93
88
  {features.length > 0 && (
@@ -103,13 +98,13 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
103
98
  </ScrollView>
104
99
 
105
100
  <SubscriptionFooter
106
- isProcessing={false} // Tab content usually delegated processing to external state, currently no isProcessing prop passed. Assuming false or controlled by isLoading.
101
+ isProcessing={false}
107
102
  isLoading={isLoading}
108
- processingText={displayProcessingText}
109
- purchaseButtonText={displayPurchaseButtonText}
103
+ processingText={processingText}
104
+ purchaseButtonText={purchaseButtonText}
110
105
  hasPackages={packages.length > 0}
111
106
  selectedPkg={selectedPackage}
112
- restoreButtonText={displayRestoreButtonText}
107
+ restoreButtonText={restoreButtonText}
113
108
  showRestoreButton={!!onRestore}
114
109
  onPurchase={onPurchase}
115
110
  onRestore={onRestore || (() => { })}
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * useCreditChecker Hook
3
3
  *
4
- * Provides credit checking utilities using context repository.
4
+ * Provides credit checking utilities using module-level repository.
5
5
  */
6
6
 
7
7
  import { useMemo } from "react";
8
8
  import type { CreditType } from "../../domain/entities/Credits";
9
- import { useCreditsRepository } from "../context/CreditsContext";
9
+ import { getCreditsRepository } from "../../infrastructure/repositories/CreditsRepositoryProvider";
10
10
  import {
11
11
  createCreditChecker,
12
12
  type CreditCheckResult,
@@ -30,11 +30,11 @@ export interface UseCreditCheckerResult {
30
30
  export const useCreditChecker = ({
31
31
  getCreditType,
32
32
  }: UseCreditCheckerParams): UseCreditCheckerResult => {
33
- const repository = useCreditsRepository();
33
+ const repository = getCreditsRepository();
34
34
 
35
35
  const checker = useMemo(
36
36
  () => createCreditChecker({ repository, getCreditType }),
37
- [repository, getCreditType]
37
+ [getCreditType]
38
38
  );
39
39
 
40
40
  return checker;
@@ -2,15 +2,15 @@
2
2
  * useCredits Hook
3
3
  *
4
4
  * TanStack Query hook for fetching user credits.
5
- * Generic and reusable - uses config from CreditsProvider.
5
+ * Generic and reusable - uses config from module-level provider.
6
6
  */
7
7
 
8
8
  import { useQuery } from "@tanstack/react-query";
9
9
  import type { UserCredits, CreditType } from "../../domain/entities/Credits";
10
10
  import {
11
- useCreditsRepository,
12
- useCreditsConfig,
13
- } from "../context/CreditsContext";
11
+ getCreditsRepository,
12
+ getCreditsConfig,
13
+ } from "../../infrastructure/repositories/CreditsRepositoryProvider";
14
14
 
15
15
  const CACHE_CONFIG = {
16
16
  staleTime: 30 * 1000,
@@ -42,8 +42,8 @@ export const useCredits = ({
42
42
  userId,
43
43
  enabled = true,
44
44
  }: UseCreditsParams): UseCreditsResult => {
45
- const repository = useCreditsRepository();
46
- const config = useCreditsConfig();
45
+ const repository = getCreditsRepository();
46
+ const config = getCreditsConfig();
47
47
 
48
48
  const { data, isLoading, error, refetch } = useQuery({
49
49
  queryKey: creditsQueryKeys.user(userId ?? ""),
@@ -2,12 +2,12 @@
2
2
  * useDeductCredit Hook
3
3
  *
4
4
  * TanStack Query mutation hook for deducting credits.
5
- * Generic and reusable - uses config from CreditsProvider.
5
+ * Generic and reusable - uses module-level repository.
6
6
  */
7
7
 
8
8
  import { useMutation, useQueryClient } from "@tanstack/react-query";
9
9
  import type { CreditType, UserCredits } from "../../domain/entities/Credits";
10
- import { useCreditsRepository } from "../context/CreditsContext";
10
+ import { getCreditsRepository } from "../../infrastructure/repositories/CreditsRepositoryProvider";
11
11
  import { creditsQueryKeys } from "./useCredits";
12
12
 
13
13
  export interface UseDeductCreditParams {
@@ -24,7 +24,7 @@ export const useDeductCredit = ({
24
24
  userId,
25
25
  onCreditsExhausted,
26
26
  }: UseDeductCreditParams): UseDeductCreditResult => {
27
- const repository = useCreditsRepository();
27
+ const repository = getCreditsRepository();
28
28
  const queryClient = useQueryClient();
29
29
 
30
30
  const mutation = useMutation({
@@ -113,7 +113,7 @@ export interface UseInitializeCreditsResult {
113
113
  export const useInitializeCredits = ({
114
114
  userId,
115
115
  }: UseInitializeCreditsParams): UseInitializeCreditsResult => {
116
- const repository = useCreditsRepository();
116
+ const repository = getCreditsRepository();
117
117
  const queryClient = useQueryClient();
118
118
 
119
119
  const mutation = useMutation({
@@ -1,42 +0,0 @@
1
- /**
2
- * Credits Context
3
- *
4
- * React context for credits configuration.
5
- * Allows main app to provide credit limits and collection name.
6
- */
7
-
8
- import { createContext, useContext } from "react";
9
- import type { CreditsConfig } from "../../domain/entities/Credits";
10
- import { DEFAULT_CREDITS_CONFIG } from "../../domain/entities/Credits";
11
- import type { CreditsRepository } from "../../infrastructure/repositories/CreditsRepository";
12
-
13
- export interface CreditsContextValue {
14
- config: CreditsConfig;
15
- repository: CreditsRepository | null;
16
- }
17
-
18
- export const CreditsContext = createContext<CreditsContextValue>({
19
- config: DEFAULT_CREDITS_CONFIG,
20
- repository: null,
21
- });
22
-
23
- export const useCreditsContext = (): CreditsContextValue => {
24
- const context = useContext(CreditsContext);
25
- if (!context.repository) {
26
- throw new Error("CreditsProvider must be used to provide credits config");
27
- }
28
- return context;
29
- };
30
-
31
- export const useCreditsConfig = (): CreditsConfig => {
32
- const { config } = useContext(CreditsContext);
33
- return config;
34
- };
35
-
36
- export const useCreditsRepository = (): CreditsRepository => {
37
- const { repository } = useContext(CreditsContext);
38
- if (!repository) {
39
- throw new Error("CreditsProvider must be used to provide repository");
40
- }
41
- return repository;
42
- };
@@ -1,38 +0,0 @@
1
- /**
2
- * Credits Provider
3
- *
4
- * React provider for credits configuration.
5
- * Main app uses this to configure credit limits.
6
- */
7
-
8
- import React, { useMemo } from "react";
9
- import type { CreditsConfig } from "../../domain/entities/Credits";
10
- import { DEFAULT_CREDITS_CONFIG } from "../../domain/entities/Credits";
11
- import {
12
- CreditsContext,
13
- type CreditsContextValue,
14
- } from "../context/CreditsContext";
15
- import { createCreditsRepository } from "../../infrastructure/repositories/CreditsRepository";
16
-
17
- export interface CreditsProviderProps {
18
- children: React.ReactNode;
19
- config?: Partial<CreditsConfig>;
20
- }
21
-
22
- export const CreditsProvider: React.FC<CreditsProviderProps> = ({
23
- children,
24
- config,
25
- }) => {
26
- const value = useMemo<CreditsContextValue>(() => {
27
- const mergedConfig: CreditsConfig = {
28
- ...DEFAULT_CREDITS_CONFIG,
29
- ...config,
30
- };
31
- const repository = createCreditsRepository(mergedConfig);
32
- return { config: mergedConfig, repository };
33
- }, [config]);
34
-
35
- return (
36
- <CreditsContext.Provider value={value}>{children}</CreditsContext.Provider>
37
- );
38
- };