@umituz/react-native-subscription 2.2.8 → 2.2.11

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.2.8",
3
+ "version": "2.2.11",
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",
@@ -58,4 +58,4 @@
58
58
  "README.md",
59
59
  "LICENSE"
60
60
  ]
61
- }
61
+ }
@@ -7,7 +7,7 @@ import React from "react";
7
7
  import { View, StyleSheet, TouchableOpacity } from "react-native";
8
8
  import { AtomicText } from "@umituz/react-native-design-system-atoms";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
- import type { CreditsPackage } from "../../domain/entities/CreditsPackage";
10
+ import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
11
11
 
12
12
  interface CreditsPackageCardProps {
13
13
  package: CreditsPackage;
@@ -10,7 +10,7 @@ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
10
  import { useLocalization } from "@umituz/react-native-localization";
11
11
  import { CreditsPackageCard } from "./CreditsPackageCard";
12
12
  import { PaywallLegalFooter } from "./PaywallLegalFooter";
13
- import type { CreditsPackage } from "../../domain/entities/CreditsPackage";
13
+ import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
14
14
 
15
15
  interface CreditsTabContentProps {
16
16
  packages: CreditsPackage[];
@@ -42,7 +42,7 @@ export const CreditsTabContent: React.FC<CreditsTabContentProps> = React.memo(
42
42
  const { t } = useLocalization();
43
43
 
44
44
  const needsCredits = requiredCredits && requiredCredits > currentCredits;
45
-
45
+
46
46
  const displayPurchaseButtonText = purchaseButtonText ||
47
47
  t("paywall.purchase", { defaultValue: "Purchase" });
48
48
  const displayProcessingText = processingText ||
@@ -9,13 +9,13 @@ import { SafeAreaView } from "react-native-safe-area-context";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
10
  import { useLocalization } from "@umituz/react-native-localization";
11
11
  import type { PurchasesPackage } from "react-native-purchases";
12
- import { usePaywall } from "../hooks/usePaywall";
12
+ import { usePaywall } from "../../hooks/usePaywall";
13
13
  import { PaywallHeader } from "./PaywallHeader";
14
14
  import { PaywallTabBar } from "./PaywallTabBar";
15
15
  import { CreditsTabContent } from "./CreditsTabContent";
16
16
  import { SubscriptionTabContent } from "./SubscriptionTabContent";
17
- import type { PaywallTabType } from "../../domain/entities/PaywallTab";
18
- import type { CreditsPackage } from "../../domain/entities/CreditsPackage";
17
+ import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
18
+ import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
19
19
 
20
20
  interface PaywallModalProps {
21
21
  visible: boolean;
@@ -27,10 +27,16 @@ interface PaywallModalProps {
27
27
  requiredCredits?: number;
28
28
  onCreditsPurchase: (packageId: string) => Promise<void>;
29
29
  onSubscriptionPurchase: (pkg: PurchasesPackage) => Promise<void>;
30
+ onRestore?: () => Promise<void>;
30
31
  subscriptionFeatures?: Array<{ icon: string; text: string }>;
31
32
  isLoading?: boolean;
32
33
  title?: string;
33
34
  subtitle?: string;
35
+ privacyUrl?: string;
36
+ termsUrl?: string;
37
+ privacyText?: string;
38
+ termsOfServiceText?: string;
39
+ restoreButtonText?: string;
34
40
  }
35
41
 
36
42
  export const PaywallModal: React.FC<PaywallModalProps> = React.memo(
@@ -44,10 +50,16 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo(
44
50
  requiredCredits,
45
51
  onCreditsPurchase,
46
52
  onSubscriptionPurchase,
53
+ onRestore,
47
54
  subscriptionFeatures = [],
48
55
  isLoading = false,
49
56
  title,
50
57
  subtitle,
58
+ privacyUrl,
59
+ termsUrl,
60
+ privacyText,
61
+ termsOfServiceText,
62
+ restoreButtonText,
51
63
  }) => {
52
64
  const tokens = useAppDesignTokens();
53
65
  const { t } = useLocalization();
@@ -101,12 +113,12 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo(
101
113
  onClose={onClose}
102
114
  />
103
115
 
104
- <PaywallTabBar
105
- activeTab={activeTab}
106
- onTabChange={handleTabChange}
107
- creditsLabel={t("paywall.tabs.credits", { defaultValue: "Credits" })}
108
- subscriptionLabel={t("paywall.tabs.subscription", { defaultValue: "Subscription" })}
109
- />
116
+ <PaywallTabBar
117
+ activeTab={activeTab}
118
+ onTabChange={handleTabChange}
119
+ creditsLabel={t("paywall.tabs.credits", { defaultValue: "Credits" })}
120
+ subscriptionLabel={t("paywall.tabs.subscription", { defaultValue: "Subscription" })}
121
+ />
110
122
 
111
123
  {activeTab === "credits" ? (
112
124
  <CreditsTabContent
@@ -128,6 +140,12 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo(
128
140
  features={subscriptionFeatures}
129
141
  isLoading={isLoading}
130
142
  purchaseButtonText={t("paywall.subscribe", { defaultValue: "Subscribe" })}
143
+ onRestore={onRestore}
144
+ privacyUrl={privacyUrl}
145
+ termsUrl={termsUrl}
146
+ privacyText={privacyText}
147
+ termsOfServiceText={termsOfServiceText}
148
+ restoreButtonText={restoreButtonText}
131
149
  />
132
150
  )}
133
151
  </SafeAreaView>
@@ -7,7 +7,7 @@ import React from "react";
7
7
  import { View, TouchableOpacity, StyleSheet } from "react-native";
8
8
  import { AtomicText } from "@umituz/react-native-design-system-atoms";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
- import type { PaywallTabType } from "../../domain/entities/PaywallTab";
10
+ import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
11
11
 
12
12
  interface PaywallTabBarProps {
13
13
  activeTab: PaywallTabType;
@@ -95,8 +95,13 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo(
95
95
 
96
96
  if (!visible) return null;
97
97
 
98
+ const isFullScreen = variant === "fullscreen";
99
+
98
100
  const Content = (
99
- <SafeAreaView edges={["top", "bottom"]} style={styles.safeArea}>
101
+ <SafeAreaView
102
+ edges={["top", "bottom"]}
103
+ style={isFullScreen ? styles.safeAreaFullScreen : styles.safeAreaBottomSheet}
104
+ >
100
105
  <View style={styles.header}>
101
106
  <TouchableOpacity style={styles.closeButton} onPress={onClose}>
102
107
  <AtomicText style={[styles.closeIcon, { color: tokens.colors.textSecondary }]}>
@@ -119,7 +124,10 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo(
119
124
  )}
120
125
  </View>
121
126
 
122
- <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
127
+ <ScrollView
128
+ style={isFullScreen ? styles.scrollViewFullScreen : styles.scrollViewBottomSheet}
129
+ contentContainerStyle={styles.scrollContent}
130
+ >
123
131
  <SubscriptionPackageList
124
132
  packages={packages}
125
133
  isLoading={isLoading}
@@ -186,13 +194,19 @@ const styles = StyleSheet.create({
186
194
  overlay: { flex: 1, justifyContent: "flex-end" },
187
195
  backdrop: { ...StyleSheet.absoluteFillObject, backgroundColor: "rgba(0, 0, 0, 0.5)" },
188
196
  bottomSheetContent: { borderTopLeftRadius: 24, borderTopRightRadius: 24, maxHeight: "90%" },
189
- safeArea: { flex: 1, paddingTop: 16 },
197
+
198
+ safeAreaFullScreen: { flex: 1, paddingTop: 16 },
199
+ safeAreaBottomSheet: { paddingTop: 16 },
200
+
190
201
  header: { alignItems: "center", paddingHorizontal: 24, paddingBottom: 20 },
191
202
  closeButton: { position: "absolute", top: 0, right: 16, padding: 8, zIndex: 1 },
192
203
  closeIcon: { fontSize: 28, fontWeight: "300" },
193
204
  title: { marginBottom: 8, textAlign: "center" },
194
205
  subtitle: { textAlign: "center" },
195
- scrollView: { flex: 1 },
206
+
207
+ scrollViewFullScreen: { flex: 1 },
208
+ scrollViewBottomSheet: { maxHeight: 400 },
209
+
196
210
  scrollContent: { paddingHorizontal: 24, paddingBottom: 16 },
197
211
  featuresSection: { borderRadius: 16, padding: 16, marginTop: 20 },
198
212
  });
@@ -8,7 +8,7 @@ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
8
  import type { PurchasesPackage } from "react-native-purchases";
9
9
  import { AtomicText } from "@umituz/react-native-design-system-atoms";
10
10
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
11
- import { formatPrice } from "@umituz/react-native-subscription";
11
+ import { formatPrice } from "../../../utils/priceUtils";
12
12
  import { useLocalization } from "@umituz/react-native-localization";
13
13
 
14
14
  interface SubscriptionPlanCardProps {
@@ -5,23 +5,28 @@
5
5
 
6
6
  import React, { useMemo } from "react";
7
7
  import { View, StyleSheet, ScrollView } from "react-native";
8
- import { AtomicButton } from "@umituz/react-native-design-system-atoms";
9
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
9
  import { useLocalization } from "@umituz/react-native-localization";
11
10
  import type { PurchasesPackage } from "react-native-purchases";
12
- import { SubscriptionPlanCard } from "./SubscriptionPlanCard";
13
11
  import { PaywallFeaturesList } from "./PaywallFeaturesList";
14
- import { PaywallLegalFooter } from "./PaywallLegalFooter";
12
+ import { SubscriptionPackageList } from "./SubscriptionPackageList";
13
+ import { SubscriptionFooter } from "./SubscriptionFooter";
15
14
 
16
15
  interface SubscriptionTabContentProps {
17
16
  packages: PurchasesPackage[];
18
17
  selectedPackage: PurchasesPackage | null;
19
18
  onSelectPackage: (pkg: PurchasesPackage) => void;
20
19
  onPurchase: () => void;
20
+ onRestore?: () => void;
21
21
  features?: Array<{ icon: string; text: string }>;
22
22
  isLoading?: boolean;
23
23
  purchaseButtonText?: string;
24
24
  processingText?: string;
25
+ restoreButtonText?: string;
26
+ privacyUrl?: string;
27
+ termsUrl?: string;
28
+ privacyText?: string;
29
+ termsOfServiceText?: string;
25
30
  }
26
31
 
27
32
  const isYearlyPackage = (pkg: PurchasesPackage): boolean => {
@@ -46,26 +51,27 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
46
51
  selectedPackage,
47
52
  onSelectPackage,
48
53
  onPurchase,
54
+ onRestore,
49
55
  features = [],
50
56
  isLoading = false,
51
57
  purchaseButtonText,
52
58
  processingText,
59
+ restoreButtonText = "Restore Purchases",
60
+ privacyUrl,
61
+ termsUrl,
62
+ privacyText,
63
+ termsOfServiceText,
53
64
  }) => {
54
65
  const tokens = useAppDesignTokens();
55
66
  const { t } = useLocalization();
56
67
 
57
- const displayPurchaseButtonText = purchaseButtonText ||
58
- t("paywall.subscribe", { defaultValue: "Subscribe" });
59
- const displayProcessingText = processingText ||
60
- t("paywall.processing", { defaultValue: "Processing..." });
68
+ const displayPurchaseButtonText =
69
+ purchaseButtonText || t("paywall.subscribe", { defaultValue: "Subscribe" });
70
+ const displayProcessingText =
71
+ processingText || t("paywall.processing", { defaultValue: "Processing..." });
61
72
 
62
73
  const sortedPackages = useMemo(() => sortPackages(packages), [packages]);
63
74
 
64
- const firstYearlyIndex = useMemo(
65
- () => sortedPackages.findIndex(isYearlyPackage),
66
- [sortedPackages],
67
- );
68
-
69
75
  return (
70
76
  <View style={styles.container}>
71
77
  <ScrollView
@@ -73,20 +79,14 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
73
79
  contentContainerStyle={styles.scrollContent}
74
80
  showsVerticalScrollIndicator={false}
75
81
  >
76
- <View style={styles.plansContainer}>
77
- {sortedPackages.map((pkg, index) => (
78
- <SubscriptionPlanCard
79
- key={pkg.product.identifier}
80
- package={pkg}
81
- isSelected={
82
- selectedPackage?.product.identifier ===
83
- pkg.product.identifier
84
- }
85
- onSelect={() => onSelectPackage(pkg)}
86
- isBestValue={index === firstYearlyIndex}
87
- />
88
- ))}
89
- </View>
82
+ <SubscriptionPackageList
83
+ packages={sortedPackages}
84
+ isLoading={isLoading}
85
+ selectedPkg={selectedPackage}
86
+ onSelect={onSelectPackage}
87
+ loadingText={t("paywall.loading", { defaultValue: "Loading..." })}
88
+ emptyText={t("paywall.empty", { defaultValue: "No packages" })}
89
+ />
90
90
 
91
91
  {features.length > 0 && (
92
92
  <View
@@ -100,18 +100,25 @@ export const SubscriptionTabContent: React.FC<SubscriptionTabContentProps> =
100
100
  )}
101
101
  </ScrollView>
102
102
 
103
- <View style={styles.footer}>
104
- <AtomicButton
105
- title={isLoading ? displayProcessingText : displayPurchaseButtonText}
106
- onPress={onPurchase}
107
- disabled={!selectedPackage || isLoading}
108
- />
109
- </View>
110
-
111
- <PaywallLegalFooter />
103
+ <SubscriptionFooter
104
+ isProcessing={false} // Tab content usually delegated processing to external state, currently no isProcessing prop passed. Assuming false or controlled by isLoading.
105
+ isLoading={isLoading}
106
+ processingText={displayProcessingText}
107
+ purchaseButtonText={displayPurchaseButtonText}
108
+ hasPackages={packages.length > 0}
109
+ selectedPkg={selectedPackage}
110
+ restoreButtonText={restoreButtonText}
111
+ showRestoreButton={!!onRestore}
112
+ onPurchase={onPurchase}
113
+ onRestore={onRestore || (() => { })}
114
+ privacyUrl={privacyUrl}
115
+ termsUrl={termsUrl}
116
+ privacyText={privacyText}
117
+ termsOfServiceText={termsOfServiceText}
118
+ />
112
119
  </View>
113
120
  );
114
- },
121
+ }
115
122
  );
116
123
 
117
124
  SubscriptionTabContent.displayName = "SubscriptionTabContent";
@@ -127,16 +134,9 @@ const styles = StyleSheet.create({
127
134
  paddingHorizontal: 24,
128
135
  paddingBottom: 16,
129
136
  },
130
- plansContainer: {
131
- gap: 12,
132
- marginBottom: 20,
133
- },
134
137
  featuresSection: {
135
138
  borderRadius: 16,
136
139
  padding: 16,
137
- },
138
- footer: {
139
- padding: 24,
140
- paddingTop: 16,
140
+ marginTop: 20,
141
141
  },
142
142
  });
@@ -0,0 +1,54 @@
1
+ import { useState, useCallback } from "react";
2
+ import type { PurchasesPackage } from "react-native-purchases";
3
+ import type { PaywallTabType } from "../../domain/entities/paywall/PaywallTab";
4
+
5
+ interface UsePaywallProps {
6
+ initialTab?: PaywallTabType;
7
+ onCreditsPurchase: (packageId: string) => Promise<void>;
8
+ onSubscriptionPurchase: (pkg: PurchasesPackage) => Promise<void>;
9
+ }
10
+
11
+ export const usePaywall = ({
12
+ initialTab = "credits",
13
+ onCreditsPurchase,
14
+ onSubscriptionPurchase,
15
+ }: UsePaywallProps) => {
16
+ const [activeTab, setActiveTab] = useState<PaywallTabType>(initialTab);
17
+ const [selectedCreditsPackageId, setSelectedCreditsPackageId] = useState<string | null>(null);
18
+ const [selectedSubscriptionPkg, setSelectedSubscriptionPkg] = useState<PurchasesPackage | null>(null);
19
+
20
+ const handleTabChange = useCallback((tab: PaywallTabType) => {
21
+ setActiveTab(tab);
22
+ }, []);
23
+
24
+ const handleCreditsPackageSelect = useCallback((packageId: string) => {
25
+ setSelectedCreditsPackageId(packageId);
26
+ }, []);
27
+
28
+ const handleSubscriptionPackageSelect = useCallback((pkg: PurchasesPackage) => {
29
+ setSelectedSubscriptionPkg(pkg);
30
+ }, []);
31
+
32
+ const handleCreditsPurchase = useCallback(async () => {
33
+ if (selectedCreditsPackageId) {
34
+ await onCreditsPurchase(selectedCreditsPackageId);
35
+ }
36
+ }, [selectedCreditsPackageId, onCreditsPurchase]);
37
+
38
+ const handleSubscriptionPurchase = useCallback(async () => {
39
+ if (selectedSubscriptionPkg) {
40
+ await onSubscriptionPurchase(selectedSubscriptionPkg);
41
+ }
42
+ }, [selectedSubscriptionPkg, onSubscriptionPurchase]);
43
+
44
+ return {
45
+ activeTab,
46
+ selectedCreditsPackageId,
47
+ selectedSubscriptionPkg,
48
+ handleTabChange,
49
+ handleCreditsPackageSelect,
50
+ handleSubscriptionPackageSelect,
51
+ handleCreditsPurchase,
52
+ handleSubscriptionPurchase,
53
+ };
54
+ };